MCP server
mailboxd serves the same operations as the HTTP API as MCP tools at /mcp, on the same port and behind the same bearer token. There is no stdio transport. For clients that only speak stdio, use the @psyb0t/mailbox bridge.
Client setup
Most MCP hosts accept a .mcp.json entry like this:
{
"mcpServers": {
"mailbox": {
"transport": "streamable-http",
"url": "http://localhost:8000/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN_HERE"
}
}
}
}
Give an agent the narrowest token that does its job, usually read, draft and organize on the mailboxes it handles.
Transport
The endpoint speaks MCP streamable HTTP in stateless mode. Each POST carries its own JSON-RPC request and the answer comes back as a server-sent event stream. The server issues no Mcp-Session-Id, keeps nothing between requests, and answers DELETE /mcp with 405. Requests without a valid token get the HTTP 401 before they reach MCP. Both /mcp and /mcp/ work.
How the tool set works
There is one flat tool set, the same size however many mailboxes you configure. Every tool that works on one mailbox takes a mailbox argument: the mailbox name, or its IMAP or SMTP username. Start with mailboxes to see what the token can use. For reads across accounts, use inbox.
What a token sees depends on its scopes:
tools/listreturns only the tools the token’s scopes allow.- Without
delete,delete_messagehas nopermanentargument. - Without
send,replyrequiresdraft: true, so the reply can only be saved as a draft. tools/callchecks the scope again and validates arguments against the full schema before running. A refused call on a write tool is written to the audit log asmcp.<tool name>.
Successful calls return the result as JSON text and as structuredContent.
Tools
| Tool | Scope | Annotations | What it does |
|---|---|---|---|
mailboxes | any token | read-only | Visible mailboxes, protocols and identities |
inbox | read | read-only | Unified feed across mailboxes; checkpoint and since_cursor for new mail |
list_folders | read | read-only | Folders and their special use |
list_messages | read | read-only | One folder, newest first, raw IMAP search |
search | read | read-only | Structured search with the same filters as the HTTP API |
get_message | read | read-only | Bodies, attachments, authentication results and signals; reader, safe_html, headers |
get_raw_message | read | read-only | Raw source as text, truncated at limits.mcp_inline_max_bytes |
get_attachment | read | read-only | One attachment as base64; too_large above the inline limit |
get_thread | read | read-only | The conversation, oldest first |
list_threads | read | read-only | Recent conversations (default 20) |
identities | read | read-only | Sender identities and *@domain patterns |
list_drafts | read | read-only | Messages in Drafts |
mailbox_stats | read | read-only | Per-folder counts and quota |
create_draft | draft | none | Save a draft |
update_draft | draft | destructive | Replace a draft; only the passed fields change |
delete_draft | draft | destructive | Delete a draft |
delete_message | organize, plus delete for permanent | destructive | Move to Trash, or erase from Trash |
mark_seen | organize | idempotent | Set or clear \Seen |
set_labels | organize | idempotent | Add or remove labels |
move_messages | organize | idempotent | Move to an existing folder |
create_folder | organize | idempotent | Create a folder |
reply | send, or draft with draft: true | destructive, open-world | Reply with server-derived recipients and threading |
send | send | destructive, open-world | Send a new message |
send_draft | send | destructive, open-world | Send a draft |
audit_log | admin | read-only | Recent audited actions (limit up to 500, default 100) |
health_check | admin | read-only, open-world | Connect to every visible IMAP and SMTP server |
webhook_deliveries | admin | read-only | Delivery records for one mailbox |
replay_webhook | admin | open-world | Queue a recorded delivery again |
“read-only” tools are also marked idempotent. The arguments match the HTTP API’s query parameters and bodies, with these differences:
delete_messageandmark_seentakeuidoruids;set_labelsandmove_messagestakeuids. Auidslist holds 1 to 500 entries.get_raw_messagereturnsuid,size,truncatedandraw.get_attachmentreturns the attachment fields plusdata_base64; download larger files over HTTP.limitarguments are capped at 500.
The tool descriptions tell the model that message content is untrusted and that send and send_draft deliver immediately, and steer it toward drafts when it is unsure.
Errors
A failed call is a tool result with isError: true and the same shape as an HTTP error body:
{
"error": {
"code": "permission_denied",
"message": "…",
"details": {
"required_scope": "send"
}
}
}
Codes are the HTTP API’s (permission_denied, not_found, conflict, too_large, validation_error, invalid_identity, rate_limited, imap_error, smtp_error), plus internal_error for anything unexpected. An unknown tool name returns not_found.