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/mcpon the API. No install; authenticate with your API key as a Bearer token. - stdio — the
replylayer-mcppackage (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:
codeis the stable machine-readable error code. The full catalog lives in Errors.detailscarries structured fields when the API provides them — e.g. a rate-limit error carriesdaily_limit,sends_remaining, andreset_at; a capability/denial error carriesreason_axisand acheapest_next_step.hintis an array of plain-text next-step lines (the same guidance the CLI prints). It is advisory; the machine contract iscode+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_status | isError | Meaning |
|---|---|---|
blocked | true | Content refused, terminal — edit or escalate; do not retry as-is. |
held_infrastructure | true | Transient infrastructure hold — retryable. Retry the same send later; do not rewrite the content. |
held_for_review | false | Accepted into governance / review — releasable, not a failure. |
sent | false | Dispatched. |
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
| Tool | Purpose | Annotations |
|---|---|---|
send_email † | Send 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_message † | Reply to an inbound message from the mailbox that received it, threading correctly. | destructive, openWorld |
list_messages † | List / search messages in a mailbox (search, sender, date, direction, status, starred, has_attachment); page older via before. | readOnly |
read_message † | Read one message by ID: plaintext body projection, attachment metadata/status, scan results. Side-effect-free (does not advance read state). | readOnly |
wait_for_message † | Long-poll a mailbox; returns when a new message arrives or the timeout expires. | readOnly |
get_thread † | Read a full thread as one ordered conversation (oldest → newest, safe view). | readOnly |
list_threads † | List conversation threads (most-recent first) with participants, unread_count, last_message_at. | readOnly |
mark_message_read | Mark one inbound message read (outbound / deleted / firewall-blocked rows are a 200 no-op). | idempotent |
mark_thread_read | Bulk-mark every visible inbound unread message in a thread read; returns marked_count. | idempotent |
star_message | Star or unstar a message. | idempotent |
star_thread | Star or unstar a thread. | idempotent |
delete_message | Permanently 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_preview † | Get 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
| Tool | Purpose | Annotations |
|---|---|---|
list_mailboxes † | List account mailboxes (name, full address, status). | readOnly |
get_account_usage | Get account usage, limits, and storage-quota breakdown. | readOnly |
get_agent_quota | Get the send-budget quota (sends today, effective daily limit, remaining, reset time, bound mailboxes); works with agent-scoped keys. | readOnly |
get_link_scanning_status | Read malicious link scanning (URL reputation) activation status. Enabling it is a human/admin action, not an agent one. | readOnly |
Drafts & scheduled send
| Tool | Purpose | Annotations |
|---|---|---|
create_draft | Create a draft (the scanner runs synchronously and attaches a verdict); pass send_at to schedule dispatch. | — |
update_draft | Update a draft and re-scan; set/clear sub-address and send_at fields; clear_send_at cancels a scheduled send. | — |
get_draft | Read one draft by ID (body, scan results, schedule + sub-address fields). | readOnly |
list_drafts | List drafts in a mailbox. | readOnly |
delete_draft | Soft-delete a draft (hides it from list_drafts; cancels any schedule). | destructive |
send_draft † | Send a draft; the scanner re-runs authoritatively at send time. Distinct send-time conflicts surface as isError JSON. | destructive, openWorld |
Recipients
| Tool | Purpose | Annotations |
|---|---|---|
list_recipients | List recipients with confirmation status. | readOnly |
add_recipient | Add 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
| Tool | Purpose | Annotations |
|---|---|---|
list_suppressions | List do-not-contact entries (system-added + customer-added; exact email or @domain). | readOnly |
add_suppression | Add an exact email or @domain to the do-not-contact list. One-way on MCP — removal is not exposed. | idempotent |
add_suppressions_bulk | Bulk-add up to 1000 do-not-contact entries; returns a partial-success added / already_existed / invalid response. | idempotent |
remove_suppression | Always 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)
| Tool | Purpose | Annotations |
|---|---|---|
list_allowlist | List 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_attempts | Inspect sends rejected by the recipient allowlist (aggregated summary by default; aggregate=false for raw history). | readOnly |
Inbound firewall
| Tool | Purpose | Annotations |
|---|---|---|
list_inbound_blocklist | List the account-wide inbound sender blocklist. | readOnly |
add_inbound_blocklist | Add an exact email or @domain to the inbound blocklist (one-way on MCP). | idempotent |
add_inbound_blocklist_bulk | Bulk-add up to 1000 inbound blocklist entries; partial-success response. | idempotent |
list_inbound_allowlist | List a mailbox's inbound sender allowlist. | readOnly |
add_inbound_allowlist_entry | Add a sender (email or @domain) to a mailbox's inbound allowlist. | idempotent |
add_inbound_allowlist_bulk | Bulk-add up to 1000 inbound allowlist entries; partial-success response. | idempotent |
list_inbound_firewall_blocked_attempts | List incoming senders the inbound firewall rejected (aggregated by default; aggregate=false for raw history). | readOnly |
release_firewall_blocked_message | Release 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
| Tool | Purpose | Annotations |
|---|---|---|
release_quarantined_message | Release a quarantined inbound message back to available. Refuses outbound rows. Mailbox-bound keys are limited to their mailbox. | — |
block_quarantined_message | Block a quarantined inbound message (terminal, no delivery). Refuses outbound rows. | destructive |
report_and_block | Block a held inbound message and add its resolved sender to the account-wide inbound blocklist (idempotent). Refuses outbound rows. | destructive |
approve_review | Approve a pending_review message and dispatch it. Admin keys + dashboard sessions only — agent keys get 403. | destructive, openWorld |
deny_review | Deny 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.