multi-mail-mcp
Provides tools for interacting with Gmail accounts under per-account access policies, supporting capabilities such as reading mail, creating drafts, organizing, sending, and deleting when allowed, with recipient allowlists, confirmation requirements, minimum OAuth scopes, and local audit logging.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@multi-mail-mcpsummarize unread emails across all my accounts"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
multi-mail-mcp
One local MCP server in front of any number of Gmail and Exchange Online / Outlook.com accounts, with access control per email account: read-only for some mailboxes, drafts-only or scoped sending for others, all behind one server.
Any MCP client, over stdio. The server runs on your machine; only it talks to Google and Microsoft.
Every account has its own policy: capabilities (
read,draft,organize,send,delete), a recipient allowlist, a recipient limit, and whether each send must be confirmed by you.Each account's OAuth token is requested with the minimum scopes its policy needs, so the provider itself refuses what the policy forbids wherever scope granularity allows. The server enforces the rest before any provider call.
Email content is treated as untrusted input, and every tool call is written to a local audit log.
The full design is in SPEC.md.
Why this exists
This started as research: docs/ surveys the multi-account email MCP servers that existed in September 2026. Its headline finding was that none enforced per-account permissions inside one instance. Several handled many accounts and several had good permission controls, but in every one the permission profile was a property of the process while the account was a property of the call:
Account granularity | Permission granularity | |
| per tool call ( | per process ( |
| per connection (OAuth 2.1) | per process ( |
Agent Email | per process | per process ( |
The second finding shaped the design more: the tool list is not the security boundary, the OAuth scope is. Hiding tools shapes what the model attempts; only the token decides what the provider allows. So this server treats the account as the unit of policy and derives each account's token scopes from its policy.
Path | Holds |
Every server and product evaluated, with evidence and a verdict | |
How access control works today: the three layers, and which one is real | |
Getting to one entry point: gateways vs. client-native rules | |
What was missing, and what a new server would have to do about it |
Related MCP server: gmail-mcp
Status
v1 is built to SPEC.md and passes its unit, contract and end-to-end suites against fake providers. It is in use with real Gmail and Outlook.com accounts, but the live test suite has not been run yet (it checks the provider behaviour the fakes can only assume), so treat 0.1 as pre-release.
Survey existing multi-account email MCP servers
Establish how permission control is done today
Establish the options for a single entry point
Decide to build: SPEC.md (TypeScript, Node.js 22+, stdio, test-first)
Config, per-account policy engine, scope derivation, audit log
OAuth login (PKCE and loopback), token storage (keychain or file), token manager
Gmail and Microsoft Graph adapters, tested against scope-enforcing fakes
Tool layer and MCP server with call-time enforcement and elicitation-based confirmation
CLI (
serve,login,logout,status,permissions,validate,init) and composition rootControl panel (
multi-mail-mcp ui): account status, sign-in and revoke, per-account permission panel, editing accounts with review (comment-preserving writes), OAuth app setup with keychain-stored secrets, client registration snippets, activity log, settingsEnd-to-end suite: the real server and CLI as child processes against the fakes
Adversarial security review; confirmed findings fixed with regression tests (draft recipient smuggling via encoded display names, Spam/Junk as a delete path, routing-style local parts,
send_draftedit races, request timeouts, IPv6 loopback callback squatting)Live suite run against real test mailboxes (written, opt-in, not yet run)
Published package (
npm install -g multi-mail-mcp)
Install
Requires Node.js 22 or later.
npm install -g multi-mail-mcpFrom source:
git clone https://github.com/jcpinto54/multi-mail-mcp.git
cd multi-mail-mcp
npm install
npm run build # compiles to dist/; the command is dist/cli/index.js
npm link # optional: puts `multi-mail-mcp` on your PATHnpm run verify runs the typecheck, the test suite and the build.
1. Register your OAuth apps (once)
You use your own OAuth clients: one Google Cloud Desktop app client and one
Microsoft Entra public client app registration. The exact steps are in
SPEC.md §6.1, and
multi-mail-mcp init writes a config template that walks through them in its
comments. Two things matter most:
Google: set the consent screen to In production (it can stay unverified). In Testing, Google expires refresh tokens after 7 days.
Microsoft: redirect URI
http://localhostunder Mobile and desktop applications, Allow public client flows on, no client secret.
2. Configure
multi-mail-mcp init # writes ~/.config/multi-mail-mcp/config.yaml
multi-mail-mcp validate # prints every error, if anyThe config lives at --config <path>, else $MULTI_MAIL_MCP_CONFIG, else
~/.config/multi-mail-mcp/config.yaml. Changes take effect when the server
restarts. Unknown keys are errors, so a typo cannot silently weaken a policy.
version: 1
providers:
google:
clientId: 1234-abc.apps.googleusercontent.com
clientSecret: env:MULTI_MAIL_MCP_GOOGLE_CLIENT_SECRET # Desktop-app client
microsoft:
clientId: 00000000-0000-0000-0000-000000000000 # public client, no secret
accounts:
- id: personal
provider: google
address: me@gmail.com # verified at login
capabilities: [read] # read-only: the token cannot write or send
- id: work
provider: microsoft
address: me@company.com
microsoft: { tenant: organizations }
capabilities: [read, draft, organize, send]
send:
allowedRecipients: ["*@company.com", "partner@example.org"]
requireConfirmation: true # ask you before every send (MCP elicitation)
maxRecipients: 10
tokenStore: { kind: keychain } # keychain (default on macOS) | file
audit: { path: ~/.local/state/multi-mail-mcp/audit.log }
security: { overPrivilegedTokens: refuse }Instead of env:NAME, a secret can live in the OS keychain:
clientSecret: keychain:multi-mail-mcp-secrets/google-client-secret. The
control panel's OAuth apps page stores it there for you, and then no MCP client
needs to pass the secret to the server.
Every rule is in SPEC.md §4. providers.<p>.endpoints
also exists, but only for the test suite: it points the server at fake
providers, and the server warns on stderr whenever it is set.
3. Sign accounts in and check them
The easiest way is the control panel:
multi-mail-mcp ui # opens a local page; --no-browser prints its addressIt lists every account with its sign-in state, signs accounts in (or revokes and
signs in again), and shows for each one what the AI may do and whether Google or
Microsoft enforces that too, or only the server. You can add, edit and remove
accounts there: every change is validated like validate, and before saving you
see the exact lines that change and what the change does to the account's
sign-in. Your comments and formatting in the file are kept. It also sets up the
OAuth apps (storing the Google client secret in the keychain), shows the exact
command to register the server in Claude Code and other clients, and lists
recent tool calls from the audit log. It runs on 127.0.0.1 only, needs
the key in the link it prints, and stops when you close the page. Your MCP client
never starts it, so the AI cannot change its own permissions. Details:
SPEC.md §6.7.
The same from the command line:
multi-mail-mcp login personal # opens the browser; --no-browser prints the URL
multi-mail-mcp login work
multi-mail-mcp status # auth state, granted vs required scopes, expiry
multi-mail-mcp permissions # what each account may do, and who enforces it
multi-mail-mcp logout work --revokeloginverifies you signed in as the configured address (signing into the wrong account in the browser is the easiest mistake to make) and that the granted scopes cover the policy, before anything is stored.statusreports each account asok,missing,reauth_required,under_privileged(the policy was widened: log in again) orover_privileged(the token grants more than the policy: it is refused until you runlogout <id> --revokeandlogin <id>). It never prints tokens;--jsonis machine-readable.permissions [id] [--json]is offline: per account, the capabilities, the scopes login requests, the tools it can call, and the enforcement matrix below.
4. Register the server with an MCP client
The control panel's Connect a client page prints the exact command for this install, with absolute paths (GUI clients often don't share your shell's PATH).
Claude Code:
claude mcp add --scope user multi-mail -- multi-mail-mcp serve--scope user makes it available in every project. Add -e MULTI_MAIL_MCP_GOOGLE_CLIENT_SECRET=... if your config reads the secret from the
environment (not needed with a keychain: secret), and --config /abs/path/config.yaml after serve to use a config outside the default
location.
Other MCP clients (Claude Desktop, Cursor, and anything else that launches stdio servers) take the same command in their JSON config:
{
"mcpServers": {
"multi-mail": {
"command": "/abs/path/to/node",
"args": ["/abs/path/to/multi-mail-mcp/dist/cli/index.js", "serve"],
"env": { "MULTI_MAIL_MCP_GOOGLE_CLIENT_SECRET": "..." }
}
}
}Send confirmation uses MCP elicitation. With a client that does not support
it, sends from an account with requireConfirmation: true are denied
(CONFIRMATION_UNAVAILABLE), never silently allowed.
What each account can do, and who stops it
Each tool is advertised only if some account may call it, and its account
parameter is an enum of exactly those accounts; every call is re-checked when it
arrives regardless. The OAuth scope is the first boundary, the server the second:
Gmail
Capabilities | Scope requested | The provider refuses | Only the server refuses |
read |
| send, modify, delete | — |
draft |
| reading non-draft mail, modify | send (compose can send) |
send |
| read, modify | recipients outside the allowlist; unconfirmed sends |
organize / delete |
| permanent delete | send, drafts, Trash unless |
Microsoft Graph
Capabilities | Scope requested | The provider refuses | Only the server refuses |
read |
| send, modify, delete | — |
draft / organize / delete |
| send | editing non-drafts, permanent delete, Trash unless |
send |
| read, modify | recipients outside the allowlist; unconfirmed sends |
The headline guarantee: an account with only read holds a token the provider
will not let write or send, on both providers. Unions of capabilities get the
minimal union of scopes; multi-mail-mcp permissions prints the exact matrix
for each of your accounts. Details, including send policy, confirmation and
reply semantics: SPEC.md §5 and
§7.4.
How it is verified
Layer | What | Where |
Unit | Every config rule, scope derivation and coverage, recipient matching (hostile inputs included), capability checks, reply recipients, untrusted-content wrapping, audit redaction, PKCE and state, token lifecycle |
|
Provider contract | The Gmail and Graph adapters against in-process fakes of the Gmail API, Microsoft Graph and both OAuth servers, which enforce scopes per endpoint (403 on insufficient scope) and record every request, including 401/403/404/429/5xx paths |
|
End to end | The real |
|
Live | Against real, already signed-in test mailboxes: identity, read tools, a read-only token refused by the real send endpoint, Graph reply recipient fidelity |
|
npm test # unit, contract and e2e; no build needed
MULTI_MAIL_MCP_LIVE_CONFIG=/path/to/live.yaml npm run test:live # live; skipped without the variableAn honest caveat: the fakes are built from the providers' published API
documentation, so they are only as faithful as that documentation. Everything
the e2e suite proves about Google and Microsoft behaviour (which scope a given
endpoint demands, what Graph's /reply does with explicit recipients) is proven
against the fakes' reading of the docs. The live suite is what confirms the real
behaviour, and it has not been run yet. The environment variables it needs are
documented at the top of test/live/live.test.ts.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
Read, send, file and search email in any Gmail, Microsoft 365 or IMAP mailbox, plus its calendar.
Fully-managed email as MCP tools - register domains, real mailboxes, send and receive mail.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables Gmail management via MCP, including email search, retrieval, labeling, sending, and forwarding through IMAP and SMTP.1511BSD 3-Clause
- AlicenseNot gradedqualityCmaintenanceEnables searching, reading, sending, drafting, and organizing emails across multiple Google accounts via MCP.1MIT
- AlicenseNot gradedqualityBmaintenanceEnables personal Outlook/Hotmail/Live account email management through Microsoft Graph, letting users list, search, read, and delete emails (with confirmation) via MCP.ISC
- AlicenseNot gradedqualityBmaintenanceEnables remote email management for Gmail, Outlook, iCloud, Yahoo, and custom IMAP/SMTP accounts via MCP, supporting account setup, search, read, draft, and send operations.25 npmMIT