Skip to main content
Glama

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

ms-365-mcp-server

per tool call (account param)

per process (--read-only, --preset, --allowed-scopes)

google_workspace_mcp

per connection (OAuth 2.1)

per process (--read-only, --tool-tier, --permissions)

Agent Email

per process

per process (EMAIL_AGENT_MCP_SCOPE_PROFILE)

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

docs/landscape.md

Every server and product evaluated, with evidence and a verdict

docs/permissions.md

How access control works today: the three layers, and which one is real

docs/aggregation.md

Getting to one entry point: gateways vs. client-native rules

docs/the-gap.md

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 root

  • Control 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, settings

  • End-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_draft edit 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-mcp

From 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 PATH

npm 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://localhost under 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 any

The 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 address

It 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 --revoke
  • login verifies 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.

  • status reports each account as ok, missing, reauth_required, under_privileged (the policy was widened: log in again) or over_privileged (the token grants more than the policy: it is refused until you run logout <id> --revoke and login <id>). It never prints tokens; --json is 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

gmail.readonly

send, modify, delete

—

draft

gmail.compose

reading non-draft mail, modify

send (compose can send)

send

gmail.send

read, modify

recipients outside the allowlist; unconfirmed sends

organize / delete

gmail.modify

permanent delete

send, drafts, Trash unless delete

Microsoft Graph

Capabilities

Scope requested

The provider refuses

Only the server refuses

read

Mail.Read

send, modify, delete

—

draft / organize / delete

Mail.ReadWrite

send

editing non-drafts, permanent delete, Trash unless delete

send

Mail.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

src/**/*.test.ts

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

test/fakes/, src/providers/**

End to end

The real multi-mail-mcp serve and login as child processes, driven by the MCP SDK's stdio client, against the fakes: tool enums per policy; read-only accounts denied with zero provider requests, audited, and their stored tokens refused by the fake API's send endpoint; recipient allowlist incl. reply-all and send_draft re-reading recipients; confirmation accepted, declined and unavailable; several accounts per provider concurrently and account: "all"; over-privileged tokens refused until repaired; login with wrong identity or wrong state rejected; Graph draft tools refusing non-drafts; stdout carrying only JSON-RPC

test/e2e/

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

test/live/ (opt-in)

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 variable

An 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

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables Gmail management via MCP, including email search, retrieval, labeling, sending, and forwarding through IMAP and SMTP.
    15
    11
    BSD 3-Clause
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables searching, reading, sending, drafting, and organizing emails across multiple Google accounts via MCP.
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables personal Outlook/Hotmail/Live account email management through Microsoft Graph, letting users list, search, read, and delete emails (with confirmation) via MCP.
    ISC
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables 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 npm
    MIT