MCP server pro

The same product as the REST API, wearing the protocol an agent already speaks. Identical tokens, identical scopes, the same domain layer underneath.

Connecting

Streamable HTTP (preferred)

POST https://mail.example.com/api/mcp
Authorization: Bearer mimux_pat_...

The transport is stateless: no session table, no sticky routing, every POST self-contained. That is why it sits behind exactly the same auth middleware as the JSON API with nothing extra — the bearer token on each request is the whole session.

There is no standalone GET event stream; a client that opens one gets a 405. Configure your MCP client for streamable HTTP without the separate SSE channel.

The stdio bridge

For clients that only speak stdio, the pro binary bundles a bridge. It speaks MCP on stdin/stdout and forwards every call to a running mimux's /api/mcp. It never opens the database, so it is safe to run while the server is up — which is the point.

{
  "mcpServers": {
    "mimux": {
      "command": "mimux",
      "args": ["mcp"]
    }
  }
}

No env block is needed once you have run mimux mail login: the bridge reads the same stored credentials. Where nothing was stored — CI, containers — set MIMUX_URL (default http://localhost:8083) and MIMUX_TOKEN in the env map instead. The bridge mirrors whatever the remote session exposes — tools and resources, schemas passed through untouched — so it never drifts from the HTTP endpoint. See Command line.

Scopes are the tool list

Each request builds a server exposing only the tools the presented token's scopes allow. So tools/list is the permission model: a missing tool tells the model that this credential cannot do that, rather than letting it try and get an error back.

ScopeTools it adds
accounts:readlist_accounts
mail:readlist_folders, search_mail, read_message, and the mimux://inbox/summary resource
mail:modifymark_read, star_message, move_message
mail:senddraft_reply, send_draft

Nine tools in total. Mint a token with the narrowest set that does the job: an agent given mail:read mail:modify is not "asked" not to send — it is never shown a tool that can.

The tools

Reading

ToolWhat it does
list_accountsThe configured accounts with live sync state (ok/syncing/error), last sync time and message/unread counts. Use it to learn account names, and to check whether mail is syncing before trusting freshness.
list_foldersThe folder tree per account: id, name, special role (inbox/sent/archive/spam/trash), unread count. Folder ids are what move_message and search filters take. Omit account for every tree.
search_mailStructured search results: id, account, folder_id, from, subject, date, snippet, flags.
read_messageOne message by id: sender, recipients, subject, date, flags, labels, attachment list, and body. headers=raw|parsed|both adds its headers.
get_raw_messageThe exact RFC 822 source as base64, including its MIME attachments.
draft_forward_as_emlCreates a reviewable draft with the original attached. It never sends; use send_draft only after human approval.

Long bodies page. read_message returns a capped chunk; when truncated is true, call again with offset set to the returned next_offset. A truncated body is never silently cut — the flag is always there to be checked.

Deep search is a job. search_mail defaults to the local index, which is fast and usually enough. deep=true asks the IMAP servers directly; the tool waits about 15 seconds and then, if the fan-out has not finished, returns status: "running" with a job_id. Poll by calling the tool again with the same query — a finished job answers instantly from cache.

The query language is mimux's own: from:, to:, subject:, body:, is:unread, is:starred, has:attachment, before:/after: with YYYY-MM-DD, quoted phrases, and - to negate. Bare words match anywhere. Everything ANDs.

Triage

ToolWhat it does
mark_readMark a message read (default) or unread with read=false.
star_messageStar (default) or unstar with starred=false. Starring is the polite way to flag something for the human without moving it.
move_messageMove to a folder_id from list_folders, or to a special target: archive, spam or trash.

Trash needs confirmation. move_message with target=trash additionally requires confirm=true, and the tool description tells the model to ask the human first. Archive is the safe default for cleaning up an inbox — the worst realistic outcome of an over-eager agent is one drag-and-drop to undo.

Sending, in two steps

Outbound mail is deliberately split across two tools, and only the second one sends.

  1. draft_reply creates a normal mimux draft — a reply when in_reply_to is a message id (recipients, subject and threading are derived from the original; reply_all=true includes everyone), or a fresh mail when it is omitted (then to is required). Nothing is sent. It returns the draft id and a full preview of exactly what would go out, plus a note instructing the model to show that preview to the human and get approval.
  2. send_draft sends a previously created draft by id, exactly as previewed, and then deletes the draft. This is the only tool that sends mail.

The split means "review before send" is a property of the surface, not a prompt instruction that a model may or may not follow. If you never grant mail:send, neither tool exists. If you want an agent to propose mail without any chance of it going out, the pattern used by the bundled example is to strip send_draft from the tool list before the model ever sees it, and review the drafts in the mimux UI yourself.

The inbox summary resource

mimux://inbox/summary

JSON: per-account sync state and unread counts, the total unread across accounts, and the newest unread messages. A cheap first call for an agent that wants to know whether there is anything to do at all, without burning a search. Requires mail:read.

A worked example

The repository ships a single-file triage agent at examples/triage. It connects over the streamable-HTTP endpoint, reads unread mail, stars what needs a human, archives newsletters and notifications, and drafts — never sends — replies where one is obvious. Everything it does is visible live in the mimux UI while it runs.

export MIMUX_URL=http://localhost:8083
export MIMUX_TOKEN=mimux_pat_...
uv run triage.py

It needs a token with mail:read, mail:send and mail:modify, and an OpenAI-compatible model endpoint. Read the script — it is short, and the safety model is the interesting part.

Errors

The endpoint is behind the licence gate and the same per-token rate limit as the REST API: a paused licence answers 402, and an exhausted bucket answers 429 with Retry-After. The licence is checked before the token, so a 402 never spends a request from your budget. See The automation layer.