A Model Context Protocol (MCP) server for Proton Mail, letting Claude Code (or any MCP client) manage addresses, custom domains, mail settings, and encryption keys.
Stage: S2 — read and write tools, writes env-gated default-off, CI green, lockfile committed. Full Proton-parity surface (the climb to S3) is tracked in the feature-parity roadmap.
v2. 32 tools total: 16 reads (always registered) + 13 writes (PROTONMAIL_MCP_ENABLE_WRITES=1) + 3 dangerous writes (PROTONMAIL_MCP_ENABLE_WRITES=1 + PROTONMAIL_MCP_ENABLE_DANGEROUS=1).
| Capability | Available | Notes |
|---|---|---|
| Addresses (list, get, set status, delete) | yes | via go-proton-api; proton_delete_address is dangerous-gated |
| Create address (alias on custom domain) | yes | via internal/protonraw |
| Update address display name + signature | yes | global account fields; upstream has no per-address setter |
| Custom domains (list, get, add, verify, remove) | yes | via internal/protonraw; proton_remove_custom_domain is dangerous-gated |
| Mail settings (get, update display name + signature) | yes | |
| Account settings (get, update telemetry + crash reports) | yes | locale update is not exposed by upstream |
| Encryption keys (list with fingerprint + armored public key) | yes | via gopenpgp/v2 |
| Encryption key generation / set primary | deferred to v1.5 | requires keyring unlock + signed KeyList |
| Mail search + header inspection | yes | metadata + raw headers; proton_get_message with include_body=true decrypts the plaintext body when the keyring unlocks |
| Mail draft create/update | yes | proton_create_draft, proton_update_draft |
| Mail label / mark / permanent-delete | yes | proton_label_messages, proton_mark_messages, proton_delete_messages (dangerous-gated) |
| Mail send | v2.x | requires SRP-signed send path |
| Calendar (list calendars, list events with recurrence expansion, get event) | yes | read-only; upstream exposes no event-write methods |
| Drive | v3 |
Requires Go 1.26+ (the toolchain is auto-bumped by go-proton-api master).
The Go module path is github.com/millsymills-com/protonmail-mcp. Build from a clone:
git clone https://github.com/millsymills-com/protonmail-mcp.git
cd protonmail-mcp
go build -o ./protonmail-mcp ./cmd/protonmail-mcp
Or go install github.com/millsymills-com/protonmail-mcp/cmd/protonmail-mcp@latest. Note that go install @latest ignores the replace directive in go.mod (the resty fork), so the clone + go build path above is the supported install; @latest may fail to build until a clean tag exists.
go.mod already pins go-proton-api to a master HEAD pseudo-version and adds a replace directive routing github.com/go-resty/resty/v2 to ProtonMail's fork. Both are required. Do not remove them.
./protonmail-mcp login
Prompts for your Proton email, password, and (if 2FA is enabled) an otpauth:// URI or a one-shot 6-digit code. Pasting the URI lets the server refresh sessions silently; pasting a code requires re-login on token expiry.
Credentials are stored in the OS keychain (macOS Keychain / Linux Secret Service) under service protonmail-mcp (default) or in $PROTONMAIL_MCP_STATE_DIR when PROTONMAIL_MCP_CREDENTIAL_BACKEND=file.
./protonmail-mcp status
Confirms the session and prints email + storage. Run ./protonmail-mcp logout to clear stored credentials.
Configure your MCP host (Claude Code, etc.) to launch:
./protonmail-mcp
over stdio. By default the server registers read-only tools. To expose mutating tools (create address, add domain, create draft, label messages, update settings):
PROTONMAIL_MCP_ENABLE_WRITES=1 ./protonmail-mcp
Irreversible operations (permanent message deletion, address deletion, custom-domain removal) additionally require PROTONMAIL_MCP_ENABLE_DANGEROUS=1 — see the tool reference.
| Variable | Values | Default | Scope |
|---|---|---|---|
PROTONMAIL_MCP_TRANSPORT |
stdio | sse |
stdio |
always |
PROTONMAIL_MCP_HOST |
listen address | 127.0.0.1 |
sse only |
PROTONMAIL_MCP_PORT |
listen port | required when sse | sse only |
PROTONMAIL_MCP_SSE_TOKEN |
bearer token clients must send (≥16 chars) | required when sse | sse only |
PROTONMAIL_MCP_CREDENTIAL_BACKEND |
keychain | file |
keychain |
always |
PROTONMAIL_MCP_STATE_DIR |
credentials dir | $STATE_DIRECTORY → $XDG_STATE_HOME/protonmail-mcp → ~/.local/state/protonmail-mcp |
file only |
PROTONMAIL_MCP_ENABLE_WRITES |
1/true/yes registers mutating tools |
unset (reads only) | always |
PROTONMAIL_MCP_ENABLE_DANGEROUS |
1/true/yes additionally registers irreversible tools (proton_delete_messages, proton_delete_address, proton_remove_custom_domain); requires PROTONMAIL_MCP_ENABLE_WRITES too |
unset | always |
PROTONMAIL_MCP_LOG_LEVEL |
debug for verbose JSON logs to stderr |
info |
always |
PROTONMAIL_MCP_API_URL |
override Proton API base URL (used in tests) | https://mail.proton.me/api |
always |
For headless Linux deployments (systemd + SSE + file backend), see docs/headless-deployment.md.
Each tool advertises its full input schema and field-by-field description over MCP (tools/list); the handlers live in internal/tools/. For a generated tool-by-tool input/output field matrix (modes, env gates, required fields), see docs/tool-schema-matrix.md — kept in sync by TestSchemaMatrixNoDrift. Key behaviors worth knowing:
proton_update_addressupdates the global account display name / signature (upstream'sSetDisplayName/SetSignatureare not per-address). Theidparameter is accepted for forward compatibility but ignored. The tool description spells this out.proton_update_core_settingstoggles telemetry and crash reports -SetUserSettingsLocaleis not exposed by upstreamgo-proton-apimaster, so locale update is intentionally absent.proton_list_address_keysusesgopenpgp/v2to extract the fingerprint + armored public key from each stored key. Private key material never leaves the process.- DNS records for custom domains are returned as structured JSON; orchestrate with your DNS provider's MCP (e.g. Gandi MCP) to publish them.
proton_delete_messages(dangerous tier) is a permanent expunge and is irreversible. To move to Trash recoverably, useproton_label_messageswithlabel_id "3"instead.proton_delete_addressandproton_remove_custom_domain(dangerous tier) are irreversible: deleting an address is permanent, and removing a custom domain orphans all aliases on it. To stop using an address recoverably, disable it withproton_set_address_statusinstead.
The full tool list with modes and env gates is in docs/tool-schema-matrix.md.
- Credentials and refresh tokens stored in the OS keychain (macOS Keychain / Linux Secret Service, default) or a 0600 state file (
filebackend for headless Linux deployments). - Logs redact any field name containing
password,passphrase,token,secret,totp,key. - Writes opt-in via env flag - Claude Code's per-tool permission UI provides defense-in-depth.
- Defaults to stdio - no network listener. An optional SSE transport (
PROTONMAIL_MCP_TRANSPORT=sse) binds an HTTP listener onPROTONMAIL_MCP_HOST/PROTONMAIL_MCP_PORT(127.0.0.1by default). The endpoint requires a bearer token (PROTONMAIL_MCP_SSE_TOKEN, ≥16 chars); clients sendAuthorization: Bearer <token>. Authentication is enforced regardless of bind address, so even a loopback listener is not reachable by other local users without the token. The SDK's DNS-rebinding (Host-header) protection only applies on a loopback bind - a non-loopback bind logs a warning and relies on the bearer token alone. The connection is plain HTTP - terminate TLS at a reverse proxy if you bind a non-loopback address. - Sends
x-pm-appversion: macos-bridge@3.24.1because Proton's API rejects unknown product names with code 2064. We impersonate proton-bridge (live-tested 2026-04-26 againstmail.proton.me) - if Proton tightens the minimum (codes 5002/5003), bump the version ininternal/session/appversion.goto whatever proton-bridge has tagged latest.
go vet ./...
go test ./... -race # unit + harness tests (uses go-proton-api dev server in-process)
go test -tags=integration ./... -race # extra integration suite for read tools
Manual pre-release checks: docs/testing-checklist.md.
MIT.