Catalog›MCP servers›Communication›ReplyLayerRReplyLayerSafe email for AI agents: a mailbox with inbound scanning, outbound checks and human review. Website Open in the app JSON README (API)Security checkLimited check · sign-in requiredLast check Nov 11, 20264 days agoJSONNeeds sign-in, so its actions were not readSignals are measured facts, not a safety guarantee. Watch this toolREADMELoading documentation…# 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` v`0.15.1`. 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. The hosted sign-in endpoint (`POST /v1/mcp/oauth`) lists all of them except the four in `AGENT_UNAVAILABLE_TOOL_NAMES`: a sign-in connection is an agent key, which can never use those four, and its primer describes the connection instead of an API key. Its tools also carry OAuth linking metadata (`securitySchemes`, top-level and in `_meta`, and `_meta["mcp/www_authenticate"]` on a tool error caused by a revoked or expired token). ## Install Published to npm as **`replylayer-mcp`**. Most MCP clients launch it via `npx` (no global install needed) — see [Client config](#client-config-copy-paste). To run or install it directly: ```bash 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): ```bash 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 key only works once a human has finished the account's one-time signup verification: confirming the account's email address and then its mobile number (a 6-digit SMS code, texted once the email is verified). Web signup at <https://app.replylayer.ai/signup> walks through both before any key is created. A key returned by `rly signup` stays **inert** until `rly auth verify` and `rly auth verify-phone` succeed; until then calls fail with `EMAIL_NOT_VERIFIED` or `PHONE_NOT_VERIFIED`. 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: ```json { "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). A held send's `hold_context.review_url` is the dashboard page where the account owner approves or releases it (omitted when the server has no public dashboard URL); a held result leads with a `HELD FOR REVIEW — not sent` / `HELD — not sent` line telling the agent to tell the account owner (the person the agent acts for) where it waits — and by when, from `hold_context.review_expires_at` — and not to send it again. An infrastructure hold gets neither the link nor that line (retry later instead). 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 the account owner's approval in the dashboard (agent keys cannot approve — tell the account owner where, do not resend). `RATE_LIMITED` → if `details.reason` is `failed_authentication`, the API key is wrong or revoked: stop and ask a human to fix the key, do not retry; 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. `UNAUTHORIZED` / `API_KEY_REVOKED` → the key is wrong or revoked: stop and ask a human, do not 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). ## 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`. **Structured results.** Every tool except `remove_suppression` (which always returns an error) declares an `outputSchema` and returns `structuredContent` — a typed object you can consume without re-parsing the text blob — alongside the text block, on every transport. The text block is the same JSON. Each schema follows the API route's response schema and describes the result after the fields the tool leaves out (see [Tool notes](#tool-notes)): objects accept fields the API adds later, enum-like values are plain strings, and only the top-level fields the route always returns are required. A tool error (`isError: true`) carries no `structuredContent`, except a blocked or infrastructure-held send, which keeps its send result. **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](#trusted-instruction-sources)) 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 annotations Every tool declares a `title` (mirrored in `annotations.title`, which Anthropic's directory reads) and explicit boolean `readOnlyHint`, `destructiveHint` and `openWorldHint`; every tool that changes something also declares `idempotentHint`. One `tools/list` serves every client, so where client guidelines differ the stricter reading wins. The hints are advisory for the host. The API enforces the permission gates. | Group | Tools | read-only | destructive | open-world | idempotent | |---|---|---|---|---|---| | Reads | the 19 read tools listed above | true | false | false | not declared | | Sends | `send_email`, `reply_to_message`, `send_draft`, `approve_review` | false | true | true | false | | Scheduled sends | `create_draft`, `update_draft` (a `send_at` dispatches with no further call) | false | true | true | false | | Irreversible state changes | `deny_review`, `block_quarantined_message`, `release_quarantined_message`, `release_firewall_blocked_message` | false | true | false | false | | Draft delete | `delete_draft` (deleting a scheduled draft sends a `message.schedule_cancelled` webhook to the account's own endpoint) | false | true | true | false | | Irreversible, safe to repeat | `delete_message` (a re-delete succeeds), `report_and_block` (a re-report changes nothing further) | false | true | false | true | | List and marker writes | `add_suppression`, `add_suppressions_bulk`, `add_inbound_blocklist`, `add_inbound_blocklist_bulk`, `add_inbound_allowlist_entry`, `add_inbound_allowlist_bulk`, `mark_message_read`, `mark_thread_read`, `star_message`, `star_thread` | false | true | false | true | | Recipient write | `add_recipient` (emails the recipient a confirmation link; with `attest`, sends nothing but makes the person sendable) | false | true | true | false | | Stub | `remove_suppression` (always an error) | true | false | false | not declared | ### Tool notes - **`add_recipient`** — wraps `POST /v1/recipients` (free Sandbox accounts only; other tiers get `403 FORBIDDEN`, and on a paid plan the description tells the agent that a mailbox restricted to people the account owner approves needs the user to approve the person for that mailbox). Input: `email`, optional `attest: boolean`. Without `attest` the person is emailed a confirmation link and becomes sendable when they click it. With `attest: true` no email is sent and the person is sendable at once: the account vouches for them, spending one of its per-trial attestations (never refunded), and the result carries `attestation: "account_attested"` and a `complaint_notice`. The description tells the agent to attest only when the user has said they know the person. If someone added that way reports the account's email as spam, everyone added that way must confirm by link before they can be emailed again and the account loses its attestations; a report from a second person suspends the Sandbox account. An agent key, including every sign-in connection on `/v1/mcp/oauth`, is refused with `INSUFFICIENT_SCOPE` until the operator turns agent recipient writes on, and with `RECIPIENT_WRITE_AGENT_CONTAINED` when a bound mailbox only lets the agent email people the account owner approves; in both cases the hint says to ask the user to add the person (for this tool, `INSUFFICIENT_SCOPE` does not suggest an admin key). Attesting with none left is `TIER_LIMIT` with `attestations_remaining: 0` (`forfeited: true` after a complaint), or `attestations_limit: 0` while attestation is off. `RECIPIENT_WRITE_UNAVAILABLE` means retry once; if it fails again, stop and tell the user. A send refused with `SANDBOX_RECIPIENT_NOT_VERIFIED` gets a hint naming `add_recipient`. - **`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. - **`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. - **Fields left out of results** — on every transport the tools drop internal identifiers and bookkeeping from the API response before building the text block and `structuredContent` (`src/lib/trim-response.ts`): `reviewed_by_id` and six attachment bookkeeping fields (`read_message`, `get_thread`), `r2_objects_failed` (becomes `r2_objects_failed_count`, always present, `delete_message`), `added_by_actor_id` (the suppression, blocklist and allowlist tools), `actor_id` (`list_allowlist_blocked_attempts`), `warmup.velocity_gate_mode` (`get_agent_quota`), `attachment.hash` and `preview.extractor` (`get_attachment_preview`), and the scanner, PII-detector, instruction-trust and consent-bookkeeping fields (`list_mailboxes`). A vendor-named suppression `source` reads `provider_webhook` or `provider_sync`, and an unrecognised one reads `system`. A 400 validation error keeps only `instancePath` and `message` per `details.validation` item, and `SELF_HOSTED_DECRYPT_FAILED` drops `details.domain_id`. `next_cursor` is an opaque paging token; pass it back unchanged. The REST API, SDK and CLI are unchanged. The full table is in the [MCP guide](https://replylayer.ai/agents/mcp#fields-mcp-results-leave-out). - **`list_drafts`** — returns `has_more` beside `drafts`; pass the last draft's `id` as `before` for the next page. ### 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. ## 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: ```json { "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: 1. **Run the local MCP server** (`npx -y replylayer-mcp`) and use `attachments` as documented above; or 2. **Upload through the REST API or an SDK** — `POST /v1/attachments` returns a handle id, and `POST /v1/messages/send` 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 - **0.15.1** — Adds `mcpName` (`ai.replylayer/replylayer`) so the official MCP Registry can list this package. No tool, schema or behaviour change. - **0.15.0** — Moves to the MCP TypeScript SDK v2 (`@modelcontextprotocol/server`). The stdio binary and the hosted endpoints now serve the **2026-07-28** protocol revision (`server/discover`, which returns the same server instructions `initialize` does) alongside the 2025-era `initialize` handshake, from one server factory; a client that opens with `initialize` is served exactly as before. Tool names, descriptions, input and output schemas, annotations and results are unchanged (the tools stay on zod 3 through an adapter that reproduces the previous JSON Schema). Two SDK-generated error texts change: invalid tool arguments read `Input validation error: Invalid arguments for tool <name>: <field>: Required` (an `isError` result, as before), and calling a tool that is not registered is a JSON-RPC `-32602` "Tool <name> not found" error rather than an `isError` result. The `replylayer-mcp/tools` entry now exports `buildReplyLayerMcpServer` and a structural `ToolServer` in place of the SDK's `McpServer` type, so `registerAllTools` no longer depends on the SDK. Breaking for embedders of `replylayer-mcp/tools`; none for stdio users. - **0.14.4** — The top-level `releasable` field descriptions now say it is the draft-only flag (always false on sent or received messages) and point to `email_effect` for held messages; the server primer gains one sentence saying the same and that held outbound mail is released only by the account owner in the dashboard. No schema or behaviour change. - **0.14.3** — The `create_draft`, `reply_to_message` and `send_email` descriptions now link the ReplyLayer API reference they call (Anthropic directory requirement). No schema or behaviour change. - **0.14.2** — Every tool now sets `annotations.title` (Anthropic directory requirement). The top-level `title` is kept. No schema or behaviour change. - **0.14.1** — Corrected Sandbox complaint copy in the `add_recipient` description: a spam report from someone added with `attest` also revokes everyone added that way (they must confirm by link again), and only a report from a second person suspends the Sandbox account. No schema or behaviour change. - **0.14.0** — Output schemas on every tool (G7). Every tool that returned only a text block now also declares an `outputSchema` and returns matching `structuredContent` (the same JSON as the text block, which is unchanged); `remove_suppression`, which always returns an error, is the one tool without one, so every tool on the sign-in endpoint has one. Each schema follows the API route's response schema, after the fields the tool leaves out; objects are open, enum-like values are strings, and only the top-level fields the route always returns are required. The existing schemas gain the fields they were missing: `hold_context.review_url`, `review_expires_at`, `review_causes` and `agent_instructions`, the review fields and `firewall_block` on message reads, the trimmed attachment fields, and the mailbox fields `list_mailboxes` keeps. `get_attachment_preview` now requires `attachment` and `preview`. Also ships the held-send changes merged after 0.13.0 was published: the `HELD` line on a held send now tells the agent to tell the account owner where the send waits (`hold_context.review_url`) and by when (`hold_context.review_expires_at`), and not to send it again; `send_draft` now shapes its result like `send_email` and `reply_to_message` (a held draft send leads with the same line, and a blocked one is an `isError` result); the primer says the same. No tool name, input, annotation or description changes. Requires `@replylayer/sdk` 0.28.1. - **0.13.0** — Tool titles and complete annotations. Every tool declares a `title` and explicit boolean `readOnlyHint`, `destructiveHint` and `openWorldHint`, plus `idempotentHint` on every tool that changes something (see [Tool annotations](#tool-annotations)). `create_draft`, `update_draft`, both release tools, `add_recipient`, and the ten list and marker writes (`add_suppression*`, `add_inbound_blocklist*`, `add_inbound_allowlist*`, `mark_*_read`, `star_*`) are now destructive, so hosts that honour the hints ask before running them. `remove_suppression` is now read-only (it always returns an error). `create_draft` and `update_draft` say that no email is sent unless `send_at` is set. The masking guidance in the `idempotency_key` descriptions and the primer no longer names a specific host. `delete_message` and `report_and_block` declare `idempotentHint: true`. New `grant` registration option (`'any'` by default): the hosted sign-in endpoint registers with `grant: 'agent'`, which leaves out `AGENT_UNAVAILABLE_TOOL_NAMES` (`approve_review`, `deny_review`, `get_account_usage`, `remove_suppression`; the other 40 remain) and ships `OAUTH_SERVER_INSTRUCTIONS`, the primer with its API-key wording replaced by connection wording. A `grant: 'agent'` registration also takes `oauth: { scope, challenge }` (required) and adds OAuth linking metadata: every tool carries `securitySchemes` (`[{ type: 'oauth2', scopes: [scope] }]`) in `_meta` and, through a wrapped `tools/list` handler, top-level too, and a tool error caused by a 401 from the API carries `_meta["mcp/www_authenticate"]: [challenge]`, which a client that signs in per tool (ChatGPT) needs to re-link. The stdio server and `/v1/mcp` are unchanged by the grant. Apart from `add_recipient`'s new `attest` (below), no parameter changes. **Results leave out internal identifiers and bookkeeping on every transport** (see [Tool notes](#tool-notes)): `reviewed_by_id`, `added_by_actor_id`, `actor_id`, `r2_objects_failed` (now `r2_objects_failed_count`), `warmup.velocity_gate_mode`, `preview.extractor`, attachment `hash` and bookkeeping fields, and the mailbox scanner, PII-detector, instruction-trust and consent-bookkeeping fields; a vendor-named suppression `source` reads `provider_webhook` or `provider_sync`; 400 validation details keep only `instancePath` and `message`, and `SELF_HOSTED_DECRYPT_FAILED` drops `details.domain_id`. `get_attachment_preview`'s output schema no longer lists `attachment.hash` or `preview.extractor`, and `list_allowlist_blocked_attempts`'s description no longer names `actor_id`. `list_drafts` returns the API's `has_more` again. Sandbox recipient attestation: `add_recipient` gains an optional `attest: boolean` (sendable now, no confirmation email, spends one of the account's per-trial attestations) and a description that states the Sandbox-only scope, both branches, the complaint consequence and the restricted-mailbox refusal; it stays listed on the sign-in endpoint. It no longer says other accounts "can email anyone": they don't use this list, and on a paid plan a mailbox restricted to approved people still binds agent sends, so the agent asks the user to approve the person. The Sandbox-recipient hints likewise say pay-as-you-go credit removes the Sandbox recipient rule, not that it lets the agent email anyone. Tool errors gain hints for `SANDBOX_RECIPIENT_NOT_VERIFIED`, `RECIPIENT_WRITE_AGENT_CONTAINED`, `RECIPIENT_WRITE_UNAVAILABLE` and an attestation `TIER_LIMIT`. Requires `@replylayer/sdk` 0.28.0. - **0.12.3** — Idempotency keys must be literal. The `idempotency_key` descriptions on `send_email`, `reply_to_message` and `create_draft`, and the server-level primer, now say: pass the literal key in every call, and if your host masks fields ending in `_key` (OpenClaw does), retry from a new session with the full literal request instead of reusing a masked value. The API refuses a masked key (`***`, a value containing `…`, `__OPENCLAW_REDACTED__`, or a word such as `[REDACTED]`) with `400 IDEMPOTENCY_KEY_INVALID`. The hosted server already carries this text. No tool or schema change. The release also produces the Claude Desktop extension bundle. - **0.12.2** — API-key authentication failures. The server-level workflow primer (`SERVER_INSTRUCTIONS`) now tells the agent that a `RATE_LIMITED` whose `details.reason` is `failed_authentication` means the API key is wrong or revoked: stop and ask a human to fix the key, do not retry. It also names `UNAUTHORIZED` / `API_KEY_REVOKED` as stop-and-ask-a-human codes. Tool errors gain matching friendly hints for the failed-authentication `429` (checked before the daily-quota test) and for `API_KEY_REVOKED`. No tool or schema change. - **0.12.1** — documentation only; no tool, schema, or runtime change. The auth note no longer describes signup as invite-gated: it states the email and mobile-number verification a new account's key waits on. - **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](#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. The hosted MCP server also 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`. ## 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.DetailsTypeMCP serversTopicCommunicationPublisherai.replylayerSourceofficialCategoryToolsWhere it runsOnlineMCP endpointhttps://api.replylayer.ai/v1/mcpVersion0.15.1Proposed changes4Last updateOct 5, 20261 month agoProject statusActiveLanguageTypeScriptLicenseMITAddedOct 1, 20261 month agoUpdatedOct 2, 20261 month agoSource idai.replylayer/replylayerMoreMore mcp servers on CommunicationAll from ai.replylayerA–Z index