MCP overview
POST /api/mcp exposes Mailroom over the Model Context Protocol,
so an AI client can design templates, manage contacts, and run campaigns and
sequences against your project.
It is the same platform behind the same credentials as /api/v1 —
an MCP client and an SDK client with the same key can do the same things, and are
subject to the same scopes and rate limits.
Transport
Streamable HTTP, stateless, with JSON responses (no long-lived SSE stream). Every request builds its own server instance, so there is no session to establish or resume — each call carries its own credentials.
Both GET and POST are accepted at /api/mcp.
Connecting
For a client that speaks HTTP MCP directly:
{
"mcpServers": {
"mailroom": {
"type": "http",
"url": "https://<your-mailroom-deployment>/api/mcp",
"headers": { "Authorization": "Bearer pk_live_…" }
}
}
}
Mailroom is deployed per installation — there is no shared host. Use whatever domain serves your instance.
For a stdio-only client, bridge it:
npx mcp-remote https://<your-mailroom-deployment>/api/mcp \
--header "Authorization: Bearer pk_live_…"
Authentication
Authorization: Bearer pk_live_…
The key is the tenancy: one key belongs to exactly one project, and every tool call is scoped to that project. There is no tenant header and no way for a call to reach another project's data.
Authentication happens before the MCP handshake. A missing, malformed,
revoked, or unknown key gets a real HTTP 401 with:
WWW-Authenticate: Bearer realm="mailroom"
so a client fails at connect time rather than discovering the problem on its
first tool call. A key belonging to a suspended project gets 403
project_suspended.
Create and revoke keys in the console under Settings → API keys.
Scopes
Tools enforce the same scopes as the REST endpoints they sit on top of. See Authentication for the full list and what each one covers. Roughly:
| Scope | Covers |
|---|---|
contacts:read | reading contacts |
contacts:write | upserting and unsubscribing contacts |
templates:write | saving, renaming, enabling templates |
campaigns:write | everything under campaigns |
sequences:write | sequences, enrollments, events |
send | sending a test email |
* | all of the above |
Read-only design tools (validating, rendering, listing) require a valid key but
no particular scope, mirroring the open GET routes on /api/v1.
A call missing its scope comes back as a tool error with code forbidden — the
connection stays up, so the model can adapt rather than fall over.
Result shape
Successful tool results are flat, matching the REST envelope:
{ "ok": true, "campaigns": [ … ] }
The same object is returned both as JSON text content and as structuredContent.
Failures set isError and carry a reason:
{ "ok": false, "error": "Missing scope: campaigns:write", "code": "forbidden" }
Domain outcomes that are not errors — an unknown template, an empty audience, a
campaign that can no longer be edited — come back as ordinary successful results
with a reason field, so the model can react to them instead of retrying a
failure. For example { "ok": true, "updated": false, "reason": "not_editable" }.
Rate limits
Calls are rate limited per project, on the same budget as /api/v1. Exceeding it
returns a tool error with code rate_limited and the retry delay in the message.
Resources
Beyond tools, the server exposes read-only resources the model can pull in as ground truth:
| URI | What it is |
|---|---|
mailroom://schema/email-document | JSON Schema for the EmailDocument tree |
mailroom://templates/registry | the project's template keys, types, and default subjects |
mailroom://examples/seeds | preset documents to use as references |
mailroom://brand/theme | the resolved brand render theme |
Next
See Tools for the full catalogue, including the reviewable campaign workflow that keeps an agent from sending before a human has looked.