Skip to content

MCP server

Available

The Emithook MCP server (@emithook/mcp) exposes the management API as Model Context Protocol tools, so an AI assistant (Claude, Cursor, an internal agent) can query and operate Emithook in natural language — "show failed deliveries to ep_razorpay in the last hour", "redrive the DLQ for that endpoint", "create an endpoint with the Shopify preset".

Every tool is a thin wrapper over the same scoped key (via @emithook/sdk) — there is no capability here the API doesn't have. If you are an agent, read the Glossary before calling tools: the arguments below use those exact entity names.

Connect

The server runs locally over stdio and talks to the API with your key:

jsonc
// add to your MCP client config (e.g. Claude Desktop)
{
  "mcpServers": {
    "emithook": {
      "command": "npx",
      "args": ["-y", "@emithook/mcp"],
      "env": {
        "EMITHOOK_API_KEY": "ek_live_…",
        "EMITHOOK_SCOPE": "read"
      }
    }
  }
}
Env varPurpose
EMITHOOK_API_KEYRequired. The scoped key the tools act under.
EMITHOOK_SCOPEOperator-declared scope (read | write | admin, default read) — caps which tools are exposed. Never widens the key; the API enforces the real scope regardless.
EMITHOOK_BASE_URLAPI base override (self-host).
EMITHOOK_INBOX_MONGO_URL + EMITHOOK_ORG_IDEnable the inbox tools (read the MX emails store for that org). EMITHOOK_INBOX_MONGO_DB optional. Absent ⇒ inbox tools report "inbox not configured".

Auth, scopes & safety

  • Read tools are safe by default. A read-scoped key can call them; they never mutate state.
  • Write tools require a write (or admin) key and an explicit confirmation: the registry appends a confirm boolean to every write tool's schema, so the call is a no-op until the client passes confirm: true. With a read scope the write tools aren't exposed at all.
  • Tools inherit the key's organization; an agent can never reach across orgs.
Tool classMin scopeConfirmation
list_*, get_*, search_*, summarize_inbox_thread, get_metrics, pull_eventsreadnone (safe)
send_webhook, send_app_event, send_email, replay_event, replay_request, redrive_dlq, create_endpoint, update_endpoint, delete_endpoint, create_destination, validate_destination, rotate_endpoint_secret, add_domain, update_domain, verify_domain, create_alias, update_alias, delete_aliaswriteconfirm: true required

Tool catalog

Query (read-safe)

ToolArgumentsReturns
list_eventsstatus? (delivered|failed|retrying|dlq), endpoint?, event_type?, since?, until?, cursor?, limit?A page of events.
get_eventidOne event with all its delivery attempts.
get_endpointidOne inbound endpoint (route, destinations, receive URL, status).
list_destinationstype?, validation?, cursor?, limit?Registry destinations.
list_domainsCustom domains with verification state + DNS records.
preflight_domaindomainWhat mail config a domain already has, before adopting it. Takes a domain name. blocking means add_domain will refuse without acknowledging the MX conflict. (For a domain we already host: verify_domain.)
list_ingest_domainsSelectable ingest domains (platform registry ∪ verified custom).
list_requestsendpoint?, outcome? (accepted|dropped_unknown|parked_inactive), since?, until?, cursor?, limit?Raw inbound requests the front door received (incl. dropped).
get_requestidOne captured inbound request in full (headers + body).
pull_eventsendpoint, cursor?, limit?Pull queued events for a firewalled endpoint consumer.
list_aliasescursor?, limit?Inbound-email aliases (address, routing, sender rules).
get_aliasidOne inbound-email alias by id.
get_metricskey (an endpoint or destination id)Pre-aggregated rollups for that id.

Diagnose (RCA — read-safe)

Each returns a verdict, the evidence it rests on, and next actions in both MCP and CLI form. They never mutate: a diagnosis may recommend replay_event, but you make that call. Every verdict uses the shared failure taxonomy, so a diagnosis and a report never disagree about the same event.

ToolArgumentsAnswers
diagnose_eventidWhy an event failed — every attempt, plus whether comparable recent events are failing too (isolated vs outage).
diagnose_requestidWhy an inbound request never became an event — signature, route match, endpoint state.
diagnose_endpointidWhy nothing is arriving. Catches no destinations bound, no verification secret, paused, or never received anything — all invisible from other views.
diagnose_destinationidValidation state and what it implies. Says so plainly when the destination is fine.
diagnose_domaindomainVerification state + expected DNS records; falls back to preflight for a domain we don't host.
diagnose_emailidSuppression, then the SES timeline — hard bounce vs soft vs complaint vs never-sent.

Email inbox (agent-native)

The headline of the MX engine: hand an agent its own address and let it reason over its mail. All read-safe; they read the MX emails store directly and activate only when EMITHOOK_INBOX_MONGO_URL + EMITHOOK_ORG_ID are set (otherwise they report "inbox not configured").

ToolArgumentsReturns
list_inbox_messagesalias, cursor?, limit?An alias's messages, newest first — from/to, subject, received, auth result, attachments, parsed text body.
get_inbox_messagemessage_idOne email in full: headers + parsed body + SPF/DKIM/DMARC result, plus attachment metadata (filename, content type, size).
search_inbox_messagesquery, alias?, limit?Newest-first matches across subject / from / body, optionally within one alias.
summarize_inbox_threadalias, subjectThe alias's messages sharing that subject (Re:/Fwd: stripped), oldest first, for the model to summarize. Returns the thread — it does not itself summarize.

Binary payloads stay on the CLI

No MCP tool returns raw bytes (ADR-0025 §7) — a large .eml or attachment base64'd into a tool result costs more context than it's worth. get_inbox_message gives you the attachment metadata so you can decide; emithook aliases messages raw <alias> <id> and emithook aliases messages attachment <alias> <id> <index> fetch the bytes.

Outbound email (mail you sent)

Distinct from the inbox above: these read the transactional email your org sent. Both surfaces carry an explicit name so a tool list can't be misread — inbox tools say INBOUND, these say OUTBOUND (ADR-0025).

ToolArgumentsReturns
list_sent_emailsstatus?, since?, until?, cursor?, limit?A page of sent emails. Filter by bounced / complained / suppressed to find what didn't arrive.
get_sent_emailidOne sent email with its attempts and the full SES lifecycle timeline — the evidence for "why did this not arrive".
list_suppressionscursor?, limit?The org's do-not-send list. Check this first when mail "stops arriving" — a listed address is silently skipped by every send. reason is bounce/complaint (added by Emithook) or manual.

Operate (write — confirm: true required)

ToolArgumentsEffect
send_webhookdestination, event_type, payload, headers?, idempotency_key?Send one webhook to a destination id or HTTPS URL.
check_destinationtype, url?Probe reachability before creating a destination — takes a type + URL, no id. Any HTTP status counts as reachable. (For an existing destination: validate_destination { id }.)
update_destinationid, name?, status? (active|paused)Rename, or pause/resume. Pausing holds delivery — events park and drain on resume.
delete_destinationidDelete a destination. Fails while an endpoint still routes to it. Prefer status: paused to stop delivery reversibly.
add_suppressionaddress, idempotency_key?Add an address to the do-not-send list. Always recorded manual; bounce/complaint entries are added server-side and can't be created here.
delete_suppressionidRemove an address, re-enabling sending to it. If it was listed for a bounce or complaint this risks sending reputation — read the entry with list_suppressions first.
send_emailfrom, to[], subject, html?, text?, cc?[], bcc?[], reply_to?, attachments?[], idempotency_key?Send one transactional email. A 202 means accepted, not delivered — follow with get_sent_email. Each attachment is either inline base64 content or a public https url Emithook fetches — never both — up to 20 items and 10 MB decoded per request; a redirecting URL is refused, so pass the final one. See Attachments. send_email_batch is deliberately not exposed: one confirmation would authorise up to 100 sends. Use emithook email send-batch for scripted bulk work.
send_app_eventapp_id, event_type, payload, idempotency_key?Emit an event to an application id — fans out to its subscribed endpoints.
replay_eventidRe-deliver a single event (flagged webhook-replayed).
replay_requestidRe-deliver one captured inbound request through the router.
redrive_dlqendpoint?, since?Bulk-replay dead-lettered events.
create_endpointslug, path?, preset?, destinations?[]Create an inbound endpoint.
update_endpointid, slug?, path?, status? (active|paused), preset?, destinations?[], response_rules?Update an inbound endpoint (route, status, preset, destinations, responses).
delete_endpointidDelete an inbound endpoint.
create_destinationname, type, url?, config?Register an outbound destination (created pending validation).
validate_destinationidRun a live probe/test-send and update the destination's validation state.
rotate_endpoint_secretid, secretSet an endpoint's inbound verify secret (write-only, never echoed).
add_domaindomain, kind? (webhook|email|email-send)Add a custom domain — returns the DNS records to publish. email-send SENDS ONLY: no MX is published for it and no alias can route on it.
update_domainid, kind (email|email-send), acknowledge_mx_conflict?Convert a domain between receiving and send-only in place. Refused while an alias still receives on it. Converting to email publishes an MX that redirects the domain's mail and returns it to pending-dns.
verify_domainidRe-check a custom domain's DNS and flip it active when the records resolve.
create_aliaskind (platform|custom), local_part, domain_id?, destination_ids?[], allowed_senders?[], max_size_bytes?Create an inbound-email alias (address computed server-side).
update_aliasid, destination_ids?[], allowed_senders? (null clears), max_size_bytes? (null clears)Update an alias's routing/limits.
delete_aliasidDelete an inbound-email alias.

Example session

text
Agent: "What's failing for ep_razorpay in the last hour?"
 → get_metrics(key="ep_razorpay")
 → list_events(endpoint="ep_razorpay", status="failed", since="2026-06-22T05:00:00Z")
 → get_event(id="evt_01JX…")               # inspect the 5xx body

Agent: "Replay the dead-lettered ones."
 → redrive_dlq(endpoint="ep_razorpay", confirm=true)   # ⚠ write — needs confirm

See also

  • API conventions — the scopes, IDs and errors these tools share.
  • CLI — the same operations from a terminal.
  • Glossary — entity definitions used in every argument.

Emithook · a Finnoto product