replylayer-mcp
v0.11.0
Published
MCP server for ReplyLayer — safe email for AI agents
Maintainers
Readme
replylayer-mcp
The MCP server for ReplyLayer — safe email for AI agents. It exposes ReplyLayer's email surface (send, reply, read, monitor, release) as MCP tools so an MCP client (Claude Desktop, Cursor, …) can drive transactional and operational email with built-in security scanning. Not a bulk or marketing tool.
Server identity: replylayer v0.11.0. 44 tools.
Install
Published to npm as replylayer-mcp. Most MCP clients launch it via npx (no
global install needed) — see Client config. To run or
install it directly:
npx -y replylayer-mcp # or: npm install -g replylayer-mcpCredentials
Auth is a single ReplyLayer API key. The server resolves it from the
REPLYLAYER_API_KEY environment variable first, then from the stored credentials file
at ~/.replylayer/credentials. Production (https://api.replylayer.ai) is the default
endpoint; REPLYLAYER_API_URL is a testing-only override.
Set up the stored credential interactively (writes ~/.replylayer/credentials,
mode 0600):
replylayer-mcp init # or, without a global install: npx -y replylayer-mcp initFor CI / automation, skip init and pass REPLYLAYER_API_KEY in the environment.
A new key is inert until a human completes a one-time email-verification bootstrap (invite-gated signup + a 6-digit code). The server runs with a key a human has already provisioned and verified.
Client config (copy-paste)
Claude Desktop (claude_desktop_config.json) or Cursor (mcpServers) — launch via
npx so there's no global install or absolute path to manage:
{
"mcpServers": {
"replylayer": {
"command": "npx",
"args": ["-y", "replylayer-mcp"],
"env": {
"REPLYLAYER_API_KEY": "rly_live_k3m9p2qx7vn4hjd0.uZ8Qb1vK3mN0pR7sT2wX9yA4cF6gH8jL1nP3rT5vW7z"
}
}
}
}If you ran replylayer-mcp init to store the key, the env block is optional. With a
global install (npm i -g replylayer-mcp), use "command": "replylayer-mcp" and drop
the args.
The directional safety model
Every message is security-scanned, and scanning is directional:
- Outbound text that resembles prompt-injection is your agent's own authored content — delivered clean. The secrets scanner is outbound-only.
- Inbound prompt-injection is quarantined.
The send tools (send_email, reply_to_message, send_draft) return a status:
sent | quarantined | blocked | pending_review. Branch on status, not on
success/failure. Those send responses now carry the why inline: scan (the scanner
verdict — inspect scan.verdict, scan.findings, and scan.findings[].agent_instructions)
and hold_context (the policy/HITL reason when a clean scan is held by your mailbox
policy). Call read_message for full detail. A finding with
failure_class: "inference_error" is an infrastructure error, not a content judgment —
retry or release, do not rewrite.
Recovery: inbound quarantined → release_quarantined_message; firewall_blocked →
release_firewall_blocked_message; pending_review needs human approval (agent keys
cannot approve — escalate). RATE_LIMITED → if details.reset_at is present it is the
daily send quota (back off until then); otherwise it is a short-window limit — wait for
details.retry_after (or the time in the error message), then retry.
RECIPIENT_SUPPRESSED / BILLING_* / EMAIL_NOT_VERIFIED → escalate, do not retry.
This server does no implicit retry (neither on 5xx nor on a 429 Retry-After):
every tool call fails fast and returns the structured error, so the agent owns retry
timing and a tool call is never held open by a silent server-side wait.
Trusted instruction sources
By default every inbound read carries the standing untrusted-content contract:
agent_safety_context.untrusted_content: true plus a guidance string telling the
agent not to follow instructions embedded in the message body. Relaxing that on a given
read is entirely server-side and operator-configured — this server has no client-side
opt-in or env var for it. The server relaxes a read only when the customer has (a) put
the mailbox into instruction_trust_mode: 'enabled', (b) enabled this API key's
instruction-trust capability, and (c) designated that exact sender address (address-grain
only — no domain-wide trust) as a trusted instruction source, and the message itself
is verified_aligned (Mailgun-verified domain authenticity), clean-scanning, and
available from that sender. When all of that lines up, guidance is replaced with
trusted-instruction guidance (the agent may act on
that verified sender's own explicit requests in this message) and a thin
instruction_trust basis object is attached — untrusted_content stays true. This is
read-relaxation only: it never changes how an outbound send is gated.
Default is off end to end: no operator configuration → no relaxation → byte-identical legacy reads. There is currently no MCP tool to grant, list, or revoke trusted sources — see the note at the end of Tool surface.
Tool surface (44 tools)
Reads (no side effects): list_messages (search + filters + paging), read_message,
get_thread, list_threads, wait_for_message,
list_mailboxes, list_recipients, list_suppressions, list_allowlist,
list_allowlist_blocked_attempts, list_drafts, get_draft, list_inbound_blocklist,
list_inbound_allowlist, list_inbound_firewall_blocked_attempts, get_account_usage,
get_agent_quota, get_attachment_preview, get_link_scanning_status.
get_link_scanning_status reports whether malicious link scanning (URL reputation, backed by
Google Web Risk) is active for the account. Turning it on is an admin action (it enables an
account-wide sub-processor data flow), so the MCP surface is read-only here — enable it from the
dashboard, the SDK, or rly account link-scanning enable.
Sends (dispatch outbound mail): send_email, reply_to_message, send_draft.
The high-traffic tools — the 6 reads (list_messages, read_message, get_thread,
list_threads, list_mailboxes, wait_for_message) and the 3 sends (send_email,
reply_to_message, send_draft) — now return structuredContent (a typed object you can
consume without re-parsing the text blob) alongside the text block, with declared
outputSchemas. get_attachment_preview also returns structuredContent.
Drafts: create_draft, update_draft, delete_draft (plus send_draft above).
Inbound triage: release_quarantined_message, block_quarantined_message,
report_and_block (block + blocklist the sender, inbound-only),
release_firewall_blocked_message. Human-in-the-loop (admin-only; agent keys 403):
approve_review, deny_review.
Delete: delete_message — soft-deletes a message (any direction) and purges its
raw MIME; agent keys require the account delete policy to be enabled (else 403).
Policy / contacts: add_recipient, add_suppression, add_inbound_blocklist,
add_inbound_allowlist_entry, mark_message_read, mark_thread_read.
Star / unstar: star_message (toggle a message's starred flag),
star_thread (toggle a thread's starred flag).
Bulk policy mutations: add_inbound_allowlist_bulk (up to 1000 sender addresses for a
mailbox's inbound allowlist in one call), add_suppressions_bulk (up to 1000 addresses to
the account-wide do-not-contact list), add_inbound_blocklist_bulk (up to 1000 addresses
to the account-wide inbound blocklist).
Policy stub (intentionally unavailable): remove_suppression — always returns an
isError result explaining that suppression removal is a per-human admin decision; use
rly suppressions remove, an SDK admin key, or the dashboard instead.
Not yet a tool: trusted-instruction-source management (grant / list / revoke, plus the mailbox mode and per-key capability toggles — see above) has no MCP wrapper today. That includes the revoke endpoint, which — like the inbound-triage release tools — is unprivileged and agent-key-callable server-side; it just isn't exposed here yet. Manage trusted sources from the ReplyLayer dashboard (or the REST API directly) in the meantime.
Tool notes
star_message— wrapsPATCH /v1/messages/:id/star. Input:message_id,starred: boolean. Use one tool call to star or unstar (matches the SDK/REST{starred}shape — there are no separate star/unstar verbs).star_thread— wrapsPATCH /v1/mailboxes/:id/threads/:thread_id/star. Input:mailbox,thread_id,starred: boolean.get_attachment_preview— wrapsGET /v1/messages/:id/attachments/:idx/preview. Returns derived text preview only (PDF, Office, text/plain, text/csv). Surfaces404 ATTACHMENT_PREVIEW_NOT_AVAILABLEas anisErrorresult when the derivative is not ready, failed, or unavailable. ReturnsstructuredContentalongside the text block.add_inbound_allowlist_bulk— wrapsPOST /v1/mailboxes/:id/inbound-allowlist/bulk. Input:mailbox,emails: string[](max 1000). Returns the full partial-success shape:added,already_existed,invalid(with per-entryreason), andcounts.add_suppressions_bulk— wrapsPOST /v1/suppressions/bulk. Input:emails: string[](max 1000). Same partial-success shape.add_inbound_blocklist_bulk— wrapsPOST /v1/inbound-blocklist/bulk. Input:emails: string[](max 1000). Same partial-success shape.remove_suppression— policy-explanation stub. Always returnsisErrorwith codeSUPPRESSION_REMOVE_NOT_AVAILABLE. Improves agent UX by explaining why removal is not available via MCP rather than returning a generic unknown-tool error.wait_for_messagetimeout — the tool reflects the real server cap: max 30 s per call (not 120 s). For longer waits, call the tool again.
Browse & search
Inbox search and full thread reads are first-class on MCP: list_messages filters by
keyword search, sender, date range, direction, status (incl. the held-mail states
quarantined / pending_review for triage) and starred, and pages older via
before=<oldest message id from the previous page>. list_threads lists conversations
(with starred / has_inbound), and get_thread reads a full thread — pass mailbox
to disambiguate a thread key that resolves in more than one mailbox. list_messages also
supports has_attachment (true/false) — it probes /v1/health for the
messages.has_attachment_filter capability and returns a structured
SEARCH_OPERATOR_UNSUPPORTED on older servers that don't advertise it.
See also
- Public CLI repo (install & package trust): https://github.com/replylayer/rly
- The agent
SKILL.md, the CLI ↔ MCP ↔ REST verb-parity map, andENDPOINTS.md(the REST API contract) will be published with the hosted docs site when it is available.
