New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

replylayer-mcp

Package Overview
Dependencies
Maintainers
1
Versions
19
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

replylayer-mcp

MCP server for ReplyLayer — safe email for AI agents

Source
npmnpm
Version
0.12.0
Version published
Weekly downloads
100
-91.6%
Maintainers
1
Weekly downloads
 
Created
Source

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.12.0. 44 tools. The hosted server (POST /v1/mcp on api.replylayer.ai) reports the same identity and injects the same server-level workflow primer as this binary — both read the version from this package, so neither can drift from it.

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-mcp

Credentials

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 init

For 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 an outcome — branch on the outcome, not on success/failure. The discriminator is email_effect.effect_status: sent | held_for_review | held_infrastructure | blocked (the Governed Email Effect contract). Treat an effect_status you do not recognize as held, never as sent. Where email_effect is absent, fall back to the top-level status: sent | quarantined | pending_review | blocked. sent means ReplyLayer accepted the message for sending — it is not proof the recipient received it.

Those send responses also carry the why inline: scan (the scanner verdict — inspect scan.verdict, scan.findings, and scan.findings[].agent_instructions) and hold_context (the hold reason plus agent_instructions on held sends — a typed policy cause, a policy change to the scanner's decision, or a genuine scan-explained hold; null on sent/terminal outcomes and normally on infrastructure holds). Call read_message for full detail. A finding with failure_class: "inference_error" is an infrastructure error, not a content judgment — retry later, 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.

Idempotent sends

send_email and reply_to_message take an optional idempotency_key. Pick one stable key per send or reply intent and reuse that exact key for every retry of that intent: a replay returns the original outcome instead of sending a second message. The record is permanent. On IDEMPOTENT_REQUEST_IN_FLIGHT, retry the same key after the indicated delay. On IDEMPOTENT_REQUEST_NOT_PROVEN_SENT, stop and report it for investigation — the message was not re-sent, and minting a new key could duplicate it.

create_draft's idempotency_key is a different thing: it is valid only alongside send_at (scheduled sends) and replays for 24 h, not permanently.

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 — wraps PATCH /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 — wraps PATCH /v1/mailboxes/:id/threads/:thread_id/star. Input: mailbox, thread_id, starred: boolean.
  • get_attachment_preview — wraps GET /v1/messages/:id/attachments/:idx/preview. Returns derived text preview only (PDF, Office, text/plain, text/csv). Surfaces 404 ATTACHMENT_PREVIEW_NOT_AVAILABLE as an isError result when the derivative is not ready, failed, or unavailable. Returns structuredContent alongside the text block.
  • add_inbound_allowlist_bulk — wraps POST /v1/mailboxes/:id/inbound-allowlist/bulk. Input: mailbox, emails: string[] (max 1000). Returns the full partial-success shape: added, already_existed, invalid (with per-entry reason), and counts.
  • add_suppressions_bulk — wraps POST /v1/suppressions/bulk. Input: emails: string[] (max 1000). Same partial-success shape.
  • add_inbound_blocklist_bulk — wraps POST /v1/inbound-blocklist/bulk. Input: emails: string[] (max 1000). Same partial-success shape.
  • remove_suppression — policy-explanation stub. Always returns isError with code SUPPRESSION_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_message timeout — the tool reflects the real server cap: max 30 s per call (not 120 s). For longer waits, call the tool again.

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.

Hosted vs local attachments

send_email, reply_to_message, create_draft and update_draft take an attachments array. What that array means depends on which server you are talking to.

Local server (this package, npx -y replylayer-mcp) — attachments are paths on your own machine. The server reads each file, uploads it, waits for its content scan, and references the resulting handles on the send. This is the normal way to attach files.

Hosted server (POST /v1/mcp on api.replylayer.ai) — the hosted server never reads files. It runs in ReplyLayer's own infrastructure, so a path in attachments would name a file on our server, not yours. Supplying a non-empty attachments array is refused before any file is opened, with a stable code:

{
  "error": "The hosted MCP server cannot read files from your machine. To attach files, run the local MCP server (npx -y replylayer-mcp) or upload through the REST API / SDK and send from there; hosted attachment handles are not yet available on MCP.",
  "code": "HOSTED_LOCAL_ATTACHMENTS_UNSUPPORTED",
  "details": { "attachments": 1 }
}

details reports only how many paths you sent; the paths themselves are never echoed back.

To attach a file on the hosted server, pick one of:

  • Run the local MCP server (npx -y replylayer-mcp) and use attachments as documented above; or
  • Upload through the REST API or an SDK — POST /v1/attachments returns a handle id, and POST /v1/messages accepts attachment_ids. Send from there.

Two details worth knowing:

  • An empty attachments: [] is not a rejection — it behaves exactly like omitting the field.
  • A same-key idempotent retry still replays. If an earlier send from the local server carried real paths and you retry it against the hosted server with the same idempotency_key, the replay probe runs first and returns the original result. The attachments parameter therefore stays in the hosted schema (so such a retry still parses) — only its description changes, and it is refused on a probe miss.

Hosted attachment handles on MCP are not available yet; when they land, they will be an explicit handle-id parameter rather than a path.

Changelog

  • unreleased — the hosted MCP server now injects the same server-level workflow primer as this binary and advertises the real package version; both transports read the version from package.json. The primer branches on email_effect.effect_status (with the legacy top-level status as a fallback for older servers) and carries the immediate-send idempotency rules. Additive public API: replylayer-mcp/tools also exports SERVER_INSTRUCTIONS and MCP_PACKAGE_VERSION.
  • 0.12.0 — the hosted MCP server now refuses local attachments paths before any filesystem access (HOSTED_LOCAL_ATTACHMENTS_UNSUPPORTED); see Hosted vs local attachments. Additive public API: the tool registrars exported from replylayer-mcp/tools accept an optional third RegisterToolOptions argument ({ transport?: 'local' | 'hosted' }, default 'local'), so every existing two-argument call is unchanged.

See also

  • Public CLI repo (install & package trust): https://github.com/replylayer/rly
  • The agent SKILL.md, the CLI ↔ MCP ↔ REST verb-parity map, and ENDPOINTS.md (the REST API contract) will be published with the hosted docs site when it is available.

Keywords

email

FAQs

Package last updated on 13 Sep 2026

Related posts