Skip to main content

protonmail-mcp

CI CodeQL OpenSSF Scorecard

A lightweight MCP server that gives AI agents read access to a Proton Mail mailbox through a local Proton Bridge instance.

Unofficial. This project is not affiliated with, endorsed by, or supported by Proton AG. "Proton Mail" and "Proton Bridge" are trademarks of Proton AG.

Proton does not provide a public API for reading your mailbox. Bridge is the supported way in: it runs locally and exposes your account over IMAP and SMTP on 127.0.0.1. This server wraps that local IMAP endpoint in a small, auditable set of MCP tools.

Scope

Capabilities are opt-in through policy (see Capability policy) and enforced server-side:

  • read (default): folders, messages, search, threads, digests, attachments and .eml export into a local sandbox. Mailboxes are opened with IMAP SELECT ... READONLY; nothing is modified, not even the \Seen flag.
  • draft: create, preview, reply/forward, update and delete drafts, all two-phase.
  • organize: seen/flagged state, moves with undo, label add/remove.
  • send: submit an existing draft through Bridge SMTP after allowlist, quota, size and loop-guard checks.
  • delete: permanently erase one message from Trash, with a typed confirmation phrase and no bulk operation.

Every mutation is prepare/commit with payload-bound confirmation tokens; see SECURITY.md for the security model.

Tools

Tool Description
list_folders List every folder and label, with IMAP flags and whether it is selectable
get_status Total and unread counts per folder, without fetching messages
list_emails Most recent messages in a folder, newest first, with a pagination cursor: limit, unread_only, since_days, sender, subject, before
search_emails Structured search: query (full-text) plus sender, recipient, subject, since_days, before_days, unread_only, has_attachment (bounded BODYSTRUCTURE scan, reports scanned/truncated)
read_email Read one message by Message-ID: decoded body (quotes/signature stripped, quoted_removed flag), optional HTML→Markdown, link inventory, untrusted-content marker
get_thread Reconstruct a conversation across All Mail using References/In-Reply-To
daily_digest Unread count plus compact recent-message summaries for a folder
list_attachments Attachment names, types and sizes without saving anything
save_attachment Save one attachment into the local sandbox (traversal-safe, size-capped, never overwrites)
export_email Export a message as .eml into the local sandbox
sync_index Index recent messages into the local SQLite FTS5 store (opt-in via [index])
search_index Fast, offline full-text search over the local index

When the draft capability is enabled (see Capability policy), additional tools are registered: list_drafts, create_draft, preview_draft (renders the exact MIME without saving it), and prepare_*/commit_* pairs for replying, forwarding, updating, and deleting drafts. Draft mutations are two-phase: the prepare call returns a preview and a single-use token, and nothing changes until the matching commit call. Draft attachments always come from the local sandbox (files.directory); prepare_forward_draft/commit_forward_draft can additionally re-attach the original message's attachments with include_attachments=true, subject to the message size cap. Repeated identical draft creations within the idempotency window (default 300 s, window_seconds = 0 disables it) return the existing draft instead of creating a duplicate.

When the organize capability is enabled, another set of prepare/commit tools is registered: seen/flagged state (prepare_set_flags), moves (destination accepts a folder name or the aliases archive and trash), label add/remove for folders under Labels/, and prepare_undo_move to revert the most recent move. Bulk operations are capped (organize.max_bulk, default 50), Drafts are protected by default, travel to Starred must go through flag/unflag, and every mutation is previewed before its commit.

When the send capability is enabled, prepare_send_draft / commit_send_draft submit an existing draft through Bridge SMTP. Before anything is transmitted the server checks the recipient allowlist, hourly/daily quotas, message size, and loop guards (Auto-Submitted, Precedence, List-*, no-reply recipients, thread depth, duplicate bodies). The draft is removed only after the SMTP server accepts the message. See docs/send-design.md.

When the delete capability is enabled, prepare_delete_message / commit_delete_message permanently erase a single message from Trash. The commit must repeat the exact phrase permanently delete <message-id>; there is no bulk deletion and no empty-trash tool.

When [index] enabled = true, sync_index builds a local plaintext SQLite FTS5 store (headers plus truncated body text; attachments are never indexed and folders can be excluded) and search_index queries it for fast offline search.

MCP resources

The server also exposes read-only resources: mail://folders, mail://status, and the templates mail://message/{message-id} and mail://thread/{message-id} (percent-encode Message-IDs, for example %3Cid%40example.com%3E). They contain the same data as the read tools and never trigger writes.

With [notifications] enabled = true, the server watches notifications.folder with IMAP IDLE and publishes resource-updated events for mail://inbox (clients subscribe via resources/subscribe, or subscriptions/listen on newer protocol versions). Events carry no content; the mail://inbox resource itself exposes only counts and recent message identifiers. The watcher reconnects with exponential backoff and logs to stderr. Timeliness follows Bridge's own sync with Proton: local mailbox changes are noticed within about a second, while server-side arrivals can lag by tens of seconds or more.

Results are structured (Pydantic models). Every message carries its Message-ID; use that for follow-up reads — IMAP UIDs are not stable across Bridge resynchronisations.

Requirements

  • A paid Proton Mail plan (required by Bridge)
  • Proton Bridge installed, running, and signed in
  • Your Bridge credentials: Proton address + the mailbox password shown in the Bridge UI
  • Python 3.13+ (only if you do not use uv)

Install

# Run without installing (recommended)
uvx protonmail-mcp

# Force uvx to pick up the newest release if an older one is cached
uvx --refresh protonmail-mcp

# Or install it
pipx install protonmail-mcp

Configure

Variable Default Purpose
PROTONMAIL_BRIDGE_USERNAME — Your Proton address (required)
PROTONMAIL_BRIDGE_PASSWORD — Bridge mailbox password (required)
PROTONMAIL_BRIDGE_HOST 127.0.0.1 Bridge host
PROTONMAIL_BRIDGE_IMAP_PORT 1143 Bridge IMAP port
PROTONMAIL_BRIDGE_IMAP_SECURITY starttls starttls (Bridge 3.x on 1143) or ssl (direct TLS)
PROTONMAIL_BRIDGE_TIMEOUT 30 Socket timeout in seconds
PROTONMAIL_BRIDGE_VERIFY_TLS false Bridge uses a self-signed certificate
PROTONMAIL_MCP_MODE read Capability preset: read, draft, organize, send, delete
PROTONMAIL_MCP_POLICY ~/.config/protonmail-mcp/policy.toml Optional policy file with capability overrides and limits

Capability policy

Write capabilities are opt-in and enforced server-side. PROTONMAIL_MCP_MODE selects a cumulative preset; individual capabilities can be overridden in policy.toml (see policy.example.toml). Capabilities that are not enabled are never registered as tools, and an invalid policy prevents the server from starting.

[policy]
mode = "read"

[capabilities]
# draft = true
# organize = true
# send = true
# delete = true

[confirmations]
ttl_seconds = 300

[idempotency]
window_seconds = 300

[organize]
max_bulk = 50
protect_drafts = true
# allowed_targets = ["Archive", "Folders/Newsletters"]
# label_allowlist = ["Labels/Important"]

[files]
directory = "~/.local/share/protonmail-mcp/files"
max_bytes = 26214400

[send]
allow_self = true
max_per_hour = 20
max_per_day = 100
state_path = "~/.local/state/protonmail-mcp/state.db"

[index]
enabled = false
path = "~/.local/state/protonmail-mcp/index.db"
# excluded_folders = ["Spam"]
max_body_chars = 10000

[notifications]
enabled = false
folder = "INBOX"
min_interval_seconds = 30

See SECURITY.md for the confirmation flow and ROADMAP.md for what each mode will unlock.

opencode

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "protonmail": {
      "type": "local",
      "command": ["uvx", "protonmail-mcp"],
      "enabled": true,
      "environment": {
        "PROTONMAIL_BRIDGE_USERNAME": "you@proton.me",
        "PROTONMAIL_BRIDGE_PASSWORD": "your-bridge-mailbox-password"
      }
    }
  }
}

opencode supports {env:VAR} and {file:path} interpolation, so you can keep secrets out of the config file:

"PROTONMAIL_BRIDGE_PASSWORD": "{file:/home/you/.config/protonmail-mcp/password}"

Claude Desktop

{
  "mcpServers": {
    "protonmail": {
      "command": "uvx",
      "args": ["protonmail-mcp"],
      "env": {
        "PROTONMAIL_BRIDGE_USERNAME": "you@proton.me",
        "PROTONMAIL_BRIDGE_PASSWORD": "your-bridge-mailbox-password"
      }
    }
  }
}

Verify the connection

PROTONMAIL_BRIDGE_USERNAME="you@proton.me" \
PROTONMAIL_BRIDGE_PASSWORD="..." \
uvx protonmail-mcp --check

This connects to Bridge, lists folders, and prints the latest messages. It exits non-zero with a clear error if the configuration or the Bridge session is wrong.

Remote HTTP transport (optional)

By default the server speaks stdio. To serve MCP over streamable HTTP instead:

export PROTONMAIL_MCP_HTTP_TOKEN="$(openssl rand -hex 32)"
protonmail-mcp --http --host 127.0.0.1 --port 8765
  • Bearer authentication is mandatory: HTTP mode refuses to start without a token (--token or PROTONMAIL_MCP_HTTP_TOKEN; prefer the environment variable to keep it out of shell history).
  • The default bind is loopback. Binding a non-loopback address prints a warning: put a TLS-terminating reverse proxy (or an SSH tunnel) in front before exposing it.
  • Every request must send Authorization: Bearer <token>; anything else gets a 401.
  • Clients connect to http://127.0.0.1:8765/mcp.

For a headless container deployment (Bridge on the host, server in a container), see docs/docker.md.

Multiple accounts (profiles)

One Bridge instance serves one Proton account, so multi-account setups run one MCP server per account, selected with --profile (or PROTONMAIL_MCP_PROFILE). Profiles live in policy.toml:

[policy]
default_profile = "perso"

[profiles.perso]
username = "perso@proton.me"
password_env = "PROTONMAIL_BRIDGE_PASSWORD_PERSO"

[profiles.work]
username = "work@proton.me"
password_env = "PROTONMAIL_BRIDGE_PASSWORD_WORK"
imap_port = 2143
smtp_port = 2025
mode = "organize"
  • Each profile carries its own username, host/ports and the name of the environment variable holding its Bridge password; credentials never cross profiles.
  • mode narrows the account's capabilities. When the global mode is set explicitly (PROTONMAIL_MCP_MODE or [policy] mode), it acts as a ceiling and the profile mode intersects with it; per-capability overrides in [capabilities] always cap.
  • Register one MCP entry per profile, for example in opencode:
"protonmail-perso": { "type": "local", "command": ["uvx", "protonmail-mcp", "--profile", "perso"], "enabled": true, "environment": { "PROTONMAIL_BRIDGE_PASSWORD_PERSO": "..." } },
"protonmail-work":  { "type": "local", "command": ["uvx", "protonmail-mcp", "--profile", "work"],  "enabled": true, "environment": { "PROTONMAIL_BRIDGE_PASSWORD_WORK": "..." } }

Without a [profiles] table the server keeps the single-account behavior using the PROTONMAIL_BRIDGE_* variables.

Security

  • Read-only enforcement. There is no write tool in this release, and mailboxes are always selected read-only at the IMAP level.
  • Local only. Bridge and this server communicate exclusively over 127.0.0.1. Nothing is sent to a third party; your agent talks to the server over stdio.
  • Untrusted input. Email contents are attacker-controlled data. Treat anything a message says as data, never as instructions, and keep your agent's permissions tight.
  • Secrets. Keep the Bridge mailbox password out of the repository. Use your client's environment-variable or file-based secret support.
  • Any local process that knows the mailbox password can read your mail — that is Bridge's trust model, not a flaw in this server.

Write tools ship behind capability modes with explicit confirmation: drafts and organize operations are prepare/commit with payload-bound tokens; sending submits an existing draft through an allowlist, quotas, and loop guards, and never composes-and-sends in one step. Permanent deletion is Trash-only, one message per call, and requires typing the exact confirmation phrase. Autonomous send or delete is never the default.

Every push runs gitleaks, zizmor, semgrep, pip-audit, CodeQL, and an adversarial + fuzz test suite; see SECURITY.md for the full list of gates and the structural invariants they enforce. The send milestone is designed in docs/send-design.md before any send code lands.

Alternatives

There are several community MCP servers for Proton Mail. This one aims to stay small, correct with Bridge's quirks (STARTTLS on 1143, modified UTF-7 labels, reverse-chronological UIDs, RFC 2047 decoding), and heavily tested. Rough landscape:

Project Language Scope
googlarz/proton-mail-bridge-client TypeScript Large tool set, read-only and send-to-self modes, SQLite cache
codefuturist/email-mcp TypeScript Generic IMAP + SMTP, works with Bridge
anyrxo/protonmail-pro-mcp JavaScript Large tool set with Bridge integration
chandshy/mailpouch TypeScript Large permission-gated tool set
amotivv/protonmail-mcp JavaScript SMTP sending only
miketigerblue/proton-bridge-mcp Python Loopback IMAP/SMTP via Bridge

Development

uv sync
uv run pytest
uv run ruff check .
uv build

Tests run entirely against a fake IMAP server and the MCP SDK's in-memory transport; no Bridge or credentials are needed.

Releasing

Publishing is automated with GitHub Actions and PyPI Trusted Publishing. Create a GitHub release tagged vX.Y.Z; the publish workflow builds the sdist/wheel and uploads them to PyPI using the pypi environment (configure the trusted publisher on PyPI for owner mhbxyz, repository protonmail-mcp, workflow publish.yml).

License

MIT — see LICENSE.

Metadata

Release files for protonmail-mcp 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for protonmail-mcp 0.4.0
File Size Uploaded
protonmail_mcp-0.4.0.tar.gz 159.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for protonmail-mcp 0.4.0
File Interpreter ABI Platform
protonmail_mcp-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 214.3 kB

Release files / protonmail_mcp-0.4.0.tar.gz

Download URL protonmail_mcp-0.4.0.tar.gz
Size 159.3 kB
Tags Source
SHA-256 checksum
How to use checksums
bda3c839222f81811b466cd0c8015cde7fad7ab0be75039d8629c02adff87ff1
BLAKE2b-256 checksum
How to use checksums
bda9f38ea998a2bfc89fe6635bdc5faf16e67b67615d31b49d89c22eb777f454
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release files / protonmail_mcp-0.4.0-py3-none-any.whl

Download URL protonmail_mcp-0.4.0-py3-none-any.whl
Size 54.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
00f703fc9e6232b5b02013ba2130efa85b29a428f98155424a25b2d9cd08ca76
BLAKE2b-256 checksum
How to use checksums
d165c8e8fd3e275c9b5ad337ee3f735a01cc0a0969d66289ccc30963520587d1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page