MCP server

ReplyLayer ships a Model Context Protocol (MCP) server so an agent host can call the platform as tools instead of raw HTTP. The same tool registry backs two transports, so the tool set, names, and behavior are identical either way:

  • Hosted — a streamable-HTTP endpoint at POST /v1/mcp on the API. No install; authenticate with your API key as a Bearer token.
  • stdio — the replylayer-mcp package (npm), run locally as a subprocess by your MCP client. Reads the API key from the environment or stored credentials.

Both expose the same 44 tools. Every tool call returns an MCP CallToolResult; on failure the result carries isError: true and a structured JSON envelope you branch on (see The result envelope).

Connect over hosted HTTP

The hosted server is stateless: every POST /v1/mcp request is independent — there is no session id, and GET/DELETE are not supported (they return 405 with an Allow: POST header). Authenticate every request with Authorization: Bearer <api-key>; a missing or malformed Bearer token returns 401.

A representative MCP client config:

{
  "mcpServers": {
    "replylayer": {
      "type": "streamable-http",
      "url": "https://api.replylayer.ai/v1/mcp",
      "headers": { "Authorization": "Bearer rly_live_..." }
    }
  }
}

The hosted server does no implicit retry. If the API returns a rate-limit or transient error, the structured error is surfaced to your tool result immediately rather than the server sleeping through a Retry-After. Your agent owns the retry decision — read details.retry_after / details.reset_at and decide (see Errors).

Connect over stdio

Run the published replylayer-mcp package as a subprocess. It resolves the API key from REPLYLAYER_API_KEY, or from credentials stored by replylayer-mcp init (written under ~/.replylayer/). REPLYLAYER_API_URL optionally overrides the API base (defaults to the public API).

{
  "mcpServers": {
    "replylayer": {
      "command": "npx",
      "args": ["-y", "replylayer-mcp"],
      "env": { "REPLYLAYER_API_KEY": "rly_live_..." }
    }
  }
}

Like the hosted transport, the stdio server does no implicit retry — a tool call fails fast and returns the structured error so the agent decides retry-vs-abort.

The result envelope

Every tool returns an MCP CallToolResult. On success, content[0].text is the tool's JSON result (pretty-printed). On failure, isError: true and content[0].text is a JSON string with this shape:

{
  "error": "human-readable message",
  "code": "STABLE_ERROR_CODE",
  "details": { "...": "structured fields, when present" },
  "hint": ["next-step line", "..."]
}

Branch on code, not on the prose in error:

  • code is the stable machine-readable error code. The full catalog lives in Errors.
  • details carries structured fields when the API provides them — e.g. a rate-limit error carries daily_limit, sends_remaining, and reset_at; a capability/denial error carries reason_axis and a cheapest_next_step.
  • hint is an array of plain-text next-step lines (the same guidance the CLI prints). It is advisory; the machine contract is code + details.

Tool-side precondition failures (for example a capability probe that shows the server cannot honor a has_attachment filter → SEARCH_OPERATOR_UNSUPPORTED) use the same { error, code, details? } envelope, so you branch on code uniformly whether the failure came from the API or from the tool itself.

Send tools return isError on a resolved send

send_email, reply_to_message, and send_draft can return isError: true even when the HTTP call succeeded — because a send the scanner refused, or one held by an infrastructure fault, is a failed tool call your host must not mark done. The flag is derived from the governed email-effect on the send result:

email_effect.effect_statusisErrorMeaning
blockedtrueContent refused, terminal — edit or escalate; do not retry as-is.
held_infrastructuretrueTransient infrastructure hold — retryable. Retry the same send later; do not rewrite the content.
held_for_reviewfalseAccepted into governance / review — releasable, not a failure.
sentfalseDispatched.

An unrecognized or future effect_status fails closed to isError: true — an older server must not report an outcome it cannot interpret as success. For what each effect status means and how holds resolve, see Send gates. send_draft additionally surfaces its send-time conflicts (rescan rejection, already-sent) as isError JSON keyed on code, with the API details flattened to the JSON root.

Structured output

Only 10 of the 44 tools declare an outputSchema, and only those also emit structuredContent (a machine-parseable object) alongside the text block. The other 34 return their JSON as text content only — parse content[0].text.

Tools with a declared output schema + structuredContent:

send_email, reply_to_message, send_draft, list_messages, read_message, get_thread, list_threads, list_mailboxes, wait_for_message, get_attachment_preview.

This is intentional and uneven: several read-only tools (for example list_suppressions, list_allowlist, list_recipients, get_draft, get_account_usage, get_agent_quota) return text-only JSON despite being pure reads. Do not assume a read tool emits structuredContent — check for the tool in the list above, and otherwise parse the text block.

Server instructions differ by transport

The stdio server ships a server-level instructions primer — a short workflow guide (branch on message status, recovery paths for held mail, how to read a rate-limit error) that a host can inject into the model context at connect time.

The hosted /v1/mcp server does not currently send server instructions. A host connecting over hosted HTTP will not receive that primer and should rely on the per-tool description fields, which are self-contained. This is current behavior — plan for it rather than assuming the primer is present on hosted.

Tool registry (44 tools)

Tools marked declare an outputSchema and emit structuredContent; all others return text-only JSON. The Annotations column lists the MCP hints each tool declares (readOnly, destructive, idempotent, openWorld); a means no hint is declared. Annotations are advisory metadata for the host — the authoritative permission gates are enforced server-side.

Messages & threads

ToolPurposeAnnotations
send_emailSend a new email from a mailbox, or continue a thread with thread_id; returns status, scan verdict, and the governed email-effect.destructive, openWorld
reply_to_messageReply to an inbound message from the mailbox that received it, threading correctly.destructive, openWorld
list_messagesList / search messages in a mailbox (search, sender, date, direction, status, starred, has_attachment); page older via before.readOnly
read_messageRead one message by ID: plaintext body projection, attachment metadata/status, scan results. Side-effect-free (does not advance read state).readOnly
wait_for_messageLong-poll a mailbox; returns when a new message arrives or the timeout expires.readOnly
get_threadRead a full thread as one ordered conversation (oldest → newest, safe view).readOnly
list_threadsList conversation threads (most-recent first) with participants, unread_count, last_message_at.readOnly
mark_message_readMark one inbound message read (outbound / deleted / firewall-blocked rows are a 200 no-op).idempotent
mark_thread_readBulk-mark every visible inbound unread message in a thread read; returns marked_count.idempotent
star_messageStar or unstar a message.idempotent
star_threadStar or unstar a thread.idempotent
delete_messagePermanently delete a message (any direction, deletable state); purges raw bytes + derivatives. Refuses draft / scheduled / dispatching rows; agent keys require the account's agent-deletion policy.destructive
get_attachment_previewGet a derived text preview of an inbound attachment (text, CSV, PDF, Office); capped in length. Raw bytes are not available via MCP.readOnly

Mailboxes & account

ToolPurposeAnnotations
list_mailboxesList account mailboxes (name, full address, status).readOnly
get_account_usageGet account usage, limits, and storage-quota breakdown.readOnly
get_agent_quotaGet the send-budget quota (sends today, effective daily limit, remaining, reset time, bound mailboxes); works with agent-scoped keys.readOnly
get_link_scanning_statusRead malicious link scanning (URL reputation) activation status. Enabling it is a human/admin action, not an agent one.readOnly

Drafts & scheduled send

ToolPurposeAnnotations
create_draftCreate a draft (the scanner runs synchronously and attaches a verdict); pass send_at to schedule dispatch.
update_draftUpdate a draft and re-scan; set/clear sub-address and send_at fields; clear_send_at cancels a scheduled send.
get_draftRead one draft by ID (body, scan results, schedule + sub-address fields).readOnly
list_draftsList drafts in a mailbox.readOnly
delete_draftSoft-delete a draft (hides it from list_drafts; cancels any schedule).destructive
send_draftSend a draft; the scanner re-runs authoritatively at send time. Distinct send-time conflicts surface as isError JSON.destructive, openWorld

Recipients

ToolPurposeAnnotations
list_recipientsList recipients with confirmation status.readOnly
add_recipientAdd a recipient and send a confirmation email the recipient must click before you can send to them (required on sandbox-tier accounts).openWorld

Do-not-contact list

ToolPurposeAnnotations
list_suppressionsList do-not-contact entries (system-added + customer-added; exact email or @domain).readOnly
add_suppressionAdd an exact email or @domain to the do-not-contact list. One-way on MCP — removal is not exposed.idempotent
add_suppressions_bulkBulk-add up to 1000 do-not-contact entries; returns a partial-success added / already_existed / invalid response.idempotent
remove_suppressionAlways returns isError — removal is intentionally not available via MCP; use the CLI, an SDK admin key, or the dashboard.

Suppression removal is a per-human admin decision by design. See the Do-not-contact guide for the full add / list / remove contract.

Recipient allowlist (outbound)

ToolPurposeAnnotations
list_allowlistList a mailbox's recipient allowlist (only consulted in allowlist mode). Agents cannot mutate the allowlist via MCP — mutation is admin-only.readOnly
list_allowlist_blocked_attemptsInspect sends rejected by the recipient allowlist (aggregated summary by default; aggregate=false for raw history).readOnly

Inbound firewall

ToolPurposeAnnotations
list_inbound_blocklistList the account-wide inbound sender blocklist.readOnly
add_inbound_blocklistAdd an exact email or @domain to the inbound blocklist (one-way on MCP).idempotent
add_inbound_blocklist_bulkBulk-add up to 1000 inbound blocklist entries; partial-success response.idempotent
list_inbound_allowlistList a mailbox's inbound sender allowlist.readOnly
add_inbound_allowlist_entryAdd a sender (email or @domain) to a mailbox's inbound allowlist.idempotent
add_inbound_allowlist_bulkBulk-add up to 1000 inbound allowlist entries; partial-success response.idempotent
list_inbound_firewall_blocked_attemptsList incoming senders the inbound firewall rejected (aggregated by default; aggregate=false for raw history).readOnly
release_firewall_blocked_messageRelease a firewall-blocked message back into scanner processing (returns 202; re-read the message for the verdict). Mailbox-bound keys are limited to their mailbox.

Triage & review

ToolPurposeAnnotations
release_quarantined_messageRelease a quarantined inbound message back to available. Refuses outbound rows. Mailbox-bound keys are limited to their mailbox.
block_quarantined_messageBlock a quarantined inbound message (terminal, no delivery). Refuses outbound rows.destructive
report_and_blockBlock a held inbound message and add its resolved sender to the account-wide inbound blocklist (idempotent). Refuses outbound rows.destructive
approve_reviewApprove a pending_review message and dispatch it. Admin keys + dashboard sessions only — agent keys get 403.destructive, openWorld
deny_reviewDeny a pending_review message (terminal block, no dispatch). Admin keys + dashboard sessions only — agent keys get 403.destructive

Where contracts live

This page owns the MCP transport, tool registry, and result-envelope contract. For the platform contracts a tool references, see the owning page:

  • Message states, read semantics, and verdict vocabulary → Messages
  • What happens when a send is held, blocked, or contained → Send gates
  • Full error-code catalog → Errors
  • Attachment staging and preview lifecycle → Attachments
  • Tier and sandbox send limits → Limits
  • Per-field trust and untrusted-content handling → Security model
  • Webhook event catalog (dispatch, review, schedule signals) → Webhooks
  • CLI equivalents and exit codes → CLI

The replylayer-mcp package is published on npm.