Agents over MCP
postbode mcp lets an agent host such as Claude Desktop or Claude Code read your mail, propose rules and, when you allow it, act on mail. It speaks MCP on stdin and stdout, and the host starts it. It can do nothing you did not grant: each capability is a scope, and the host’s config sets the scopes.
Setup
For Claude Desktop, register the server and restart the app:
postbode mcp install claude-desktop
For Claude Code:
postbode mcp install claude-code
The Claude Desktop install edits claude_desktop_config.json and keeps every other server in it. The file is rewritten pretty-printed with sorted keys, and the original is saved next to it as claude_desktop_config.json.bak. Re-running the install does not replace that backup unless you changed the file in between. The Claude Code install runs claude mcp remove and then claude mcp add --scope user, so a re-run replaces the old entry. With --dry-run, or when claude is not on your PATH, it prints those commands instead of running them.
For any other host, postbode mcp install json prints a snippet for its config:
{
"mcpServers": {
"postbode": {
"args": [
"mcp",
"--scopes",
"read,rules:propose"
],
"command": "/opt/homebrew/bin/postbode"
}
}
}
The command is the absolute path of the postbode you ran, because hosts started from the Dock do not see your shell’s PATH. The install command fills it in; the path above is an example.
Options for postbode mcp install:
--scopessets the scopes. Run the install again with other scopes to change them.--account NAMElimits the server to one account. Repeat it for more.--removetakes the entry out again.--dry-runshows only the postbode entry, or for Claude Code theclaudecommands, and writes nothing.
The install also prints a hint with the granted scopes and how to widen them. It goes to stderr, so the output of install json stays pure JSON.
Scopes
| Scope | What it grants | Tools |
|---|---|---|
read | Folders, message headers, the activity log, rules and trash listings, previews of rules that do not match on the body, and a sync. No body text. | folders, list, log, rules_check, rules_list, rules_schema, rules_test, search, sync, trash_list |
read:bodies | Message bodies and attachment names, search over stored bodies, and with read previews of rules that match on the body. | attachments, show |
rules:propose | Proposing a rule. It is stored disabled until you approve it. | rules_propose |
rules:write | Approving and rejecting rules, and turning them on or off. | rules_approve, rules_reject, rules_set_enabled |
mail:modify | Acting on mail and restoring from the trash. | archive, delete, mark, move, trash_restore |
The default is read,rules:propose. Three example grants:
- Read-only:
--scopes read.syncis in this scope and still runs your approved rules. - Rule author: the default. The agent proposes, you approve in the window or with
postbode rules approve. - Inbox assistant:
--scopes read,read:bodies,rules:propose,mail:modify.
rules:write lets the agent approve its own rules, and an approved rule can delete mail. Leave it off.
Tools
The names follow the CLI commands. A tool that lists things returns the CLI’s --json rows wrapped in {"rows": [...]}. Rows carry an account; in rules_list that is the rule’s own scope, and null means every account. Message rows carry no body text. show returns {"message": ..., "body": ..., "truncated": ...}.
rules.toml is shared by every account, so rules_list, rules_propose, rules_approve, rules_reject and rules_set_enabled take no account argument; a rule’s own account field scopes it. They follow --account as described under Safety.
read:
folderslists folders with total and unread counts.listlists the newest messages in a folder, or threads.limitdefaults to 50 and is at most 500. Withthreads: trueit counts threads, and each thread comes with all its messages.logshows the rule and action log.rules_checkvalidatesrules.toml.rules_listlists rules and their state.rules_schemareturns the JSON Schema for a rule.rules_testpreviews a rule, given as JSON, against the local store. It reports subjects, never bodies. A rule that matches on the body needsread:bodiestoo, since its matches would reveal what bodies contain.searchfinds messages by subject, from and to. It matches all the given words as plain words.syncasks the daemon to sync and run rules once per account and reports its result:new_messages,actionsanderrors, or anerrorwhen the daemon refuses the account (offline, for example). It is in thereadscope, but it writes the store and applies approved rules, so hosts see it as a changing tool.trash_listlists the.emlbackups.
read:bodies:
attachmentslists attachment names, types and sizes. It does not save them.showreturns the headers and the body. With this scope,searchalso covers stored bodies, in FTS5 syntax.searchanddry_runnever connect to the server.showandattachmentsfetch a message you have not stored yet, once.
rules:propose:
rules_proposestores a rule disabled, withproposed_byset tomcp:<client name>, ormcpwhen the host sends no name. A rule scoped to an account the server hides is refused. While--accounthides an account, a rule withoutaccountgets the only visible account, or is refused when several are visible.
rules:write:
rules_approveapproves a rule. It acts on mail that arrives afterwards.rules_rejectrejects a proposal.rules_set_enabledturns a rule on or off.
mail:modify:
archive,delete,markandmoveact on uids in a folder.logshows them under the rule namemcp:<client name>, ormcp.trash_restorerestores a backup, given as the bare file nametrash_listreturns.
With more than one visible account, every tool that acts on one message needs account.
Safety
- Mail is written by strangers.
showwraps the body in<untrusted_mail_content>, and tells the agent that text inside is data, never instructions. Any spelling of that tag name inside a body is rewritten tountrusted-mail-content, so a mail cannot close the wrapper early. A body is cut at 100 KB and marked"truncated": true. Every mail-derived string loses its control characters; a body keeps its newlines and tabs. The tag rewrite ignores letter case, but it does not catch look-alike letters. - A host that honours tool annotations asks you before tools that change things.
delete,rules_approveandrules_set_enabledare marked destructive, because they can delete mail. The other changing tools are marked as changing, but not destructive. - The
mail:modifytools takedry_run. It reports what would happen from the local store and does not ask the daemon. --accounthides other accounts from every tool. Rule writes refuse rules scoped to a hidden account, and so does proposing one. A rule withoutaccountapplies to every account, hidden ones too, so while an account is hiddenrules_approveandrules_set_enabledrefuse to turn such a rule on; turning it off and rejecting it still work.- Without
read:bodies, no tool returns or reveals body text:searchcovers subject and addresses only, as plain words, with no search operators, andrules_testrefuses a rule that matches on the body. - A call outside the granted scopes fails with “not allowed with these scopes”. Hosts only see the tools you granted, so they should not make such a call.
Alongside the window and the CLI
The MCP server reads the same local store as the mail window. Everything that needs the mail server goes through the Postbode daemon, which owns the connections: actions, trash_restore, show and attachments for a message not yet fetched, and sync. The first such call starts the daemon if none runs. The window picks up the changes as soon as the daemon reports them, so an archived message disappears from the list.
Troubleshooting
- Restart the host after installing. It reads its config at start.
- Logs go to stderr. Look in the host’s MCP log.
- “not allowed with these scopes” means the tool needs a wider scope. Run the install again with more scopes.
- “several accounts are visible; pass account” means the tool needs the
accountargument.