Proton Bridge MCP
Give any AI assistant read, organise and carefully-gated send access to your Proton Mail, without handing your mail to anyone.
Proton is end-to-end encrypted, which is the whole point of it, and it's also why there's no API to plug an assistant into. Your mail is only readable on your own machine. Proton Mail Bridge is the piece that decrypts locally and speaks ordinary IMAP and SMTP to 127.0.0.1, so with Bridge in place this server never has to send your mail anywhere at all.
Unofficial, and not affiliated with or endorsed by Proton AG.
What it can do
Search and read mail, pull attachments out and read them (including the text of PDF invoices), tag and file messages one at a time or in batches, reply in thread, and send or forward behind a confirmation step.
Anything that sends takes draft=true instead, which puts it in your Drafts for you to look at. That path needs no confirmation, because nothing goes anywhere.
Files can be attached to anything you send, though only from directories you've said are allowed. Reading any file on the machine and posting it out is how data walks off a computer, so the default is the attachments folder and widening it is your decision, not something an instruction in an email can talk it into.
| Tool | What it does |
|---|---|
list_folders | Every folder and label, read live each time |
folder_status | Counts, plus the UIDVALIDITY every uid in that folder depends on |
poll_folder | What has arrived since you last looked |
ack_folder | Confirms a batch was handled |
search_mail | Search by text, sender, subject, date range, unread, starred; optionally report each message's other labels and folders |
search_all_mail | The same search across every folder and label, duplicates collapsed |
get_headers | Headers with SPF, DKIM and DMARC verdicts, and Proton metadata |
read_message | Full headers and body |
list_attachments | Real documents, kept apart from inline images and PGP keys |
read_attachment | Pulls the text out, PDFs included |
view_attachment | Hands back an image attachment so it can actually be looked at |
save_attachment | Writes a file out, deleted again after 15 minutes unless you say otherwise |
purge_attachments | Deletes those files now |
find_thread | The whole conversation, and which messages carry documents |
bulk_mark | Read, unread, star or unstar many messages in one pass |
bulk_apply_label | One label onto many messages |
bulk_remove_label | Takes one label off many messages, gated |
bulk_move | File or Trash many at once, gated |
reply | Replies with correct threading, gated |
reply_all | Same, with your own addresses stripped from Cc, gated |
create_draft | Writes into Drafts, never sends |
update_draft | Replaces a draft, keeping its threading |
delete_draft | Moves a draft to Trash, gated |
send_draft | Sends a saved draft, gated |
unsubscribe | Reports how to unsubscribe, and can send the email form |
mark | Read, unread, star, unstar |
apply_label | Tags a message, leaves it where it is |
remove_label | Takes a label off, leaves the message where it is, gated |
move_to_folder | Files it somewhere else |
create_folder_or_label | New folder or label, gated |
delete_label | Deletes a label, messages keep their place and lose the tag, gated |
bulk_delete_labels | The same for several labels at once, gated |
send | Gated, and can carry attachments |
forward | Gated |
Three things it can't do, and won't pretend otherwise. Folders can be created but not deleted, because a folder is where a message actually lives and deleting one would have to decide what happens to the mail inside it, so that stays a job for the Proton app. Bridge has no access to Proton's server-side filters or auto-forwarding rules, so those stay a manual job in the Proton web app. And nothing here hard-deletes, the furthest it goes is Trash.
Watching for new mail
poll_folder hands back whatever has turned up since you last looked, which is what turns this from something that reads your mail when asked into something that can react to mail arriving.
The first poll on a folder returns nothing on purpose. It notes where the folder currently ends, so switching it on doesn't dump years of backlog into a conversation. It reads nothing as read either.
If you're doing something with each message that you'd rather not do twice, poll with advance=false. You get the batch and a checkpoint, the cursor stays where it was, and polling again hands you the same batch until you confirm with ack_folder. Crash halfway and you pick up where you left off instead of losing the lot. Confirming twice is harmless.
The cursor records the UIDVALIDITY next to the message number, so a folder that resyncs underneath you is spotted rather than acted on. When that happens it re-anchors to the current end and says so, because the alternative is replaying whatever those old numbers now point at.
Getting at attachments
Three ways in, for three different situations. read_attachment pulls the text out and is what you want almost always, invoices included, and nothing touches the disk. save_attachment writes the file out for anything that isn't text, and works if whatever you're using can read files off disk. view_attachment hands an image straight back so it can be looked at, which is the only route to a photo or a scan when the client can't reach the filesystem.
Images only for that last one, on purpose. Encoding a file to send it inline makes it a third bigger and drops it into the conversation as characters, and for a spreadsheet or a Word document that's a lot of context spent on something nothing can read. Images are different because they arrive as an image rather than as text, so they cost about what a picture costs and can actually be seen.
Searching everywhere
A message in Proton lives in one folder but also turns up under every label you've put on it, and again in All Mail. Sweep the lot naively and you get the same mail three times. search_all_mail keys on Message-ID instead, so you get one entry per message with the other places it appears listed underneath, and it scans All Mail last as a safety net rather than treating it as a source. Every hit carries the UIDVALIDITY of the folder it was found in, because those differ per folder and a uid without one isn't safe to act on.
UIDs go stale
IMAP identifies a message by a number that's only meaningful until the folder resyncs. When that happens the number quietly starts pointing at something else, which is how the wrong message gets filed or trashed. Every folder reports a UIDVALIDITY alongside its uids, and if you hand one back with a uid that no longer matches, the tool refuses and asks you to search again rather than acting on the wrong mail.
Conversations aren't messages
Worth knowing before you trust an answer about attachments. Proton's app groups mail into conversations and shows a paperclip if anything in the thread has one. IMAP hands over individual messages with no grouping at all. A reply sitting in your inbox can be completely empty while the original, filed somewhere else, is carrying the PDFs. That's why find_thread exists, and why "no attachments" from a single message is an answer worth checking.
Other mail providers
Bridge is what this was built for, and it's the case with no alternative, since Proton has no API to point anything else at. The rest of it is ordinary IMAP and SMTP though, so it works against a normal mailbox too, which is useful if your business mail comes from a smaller host rather than Google or Microsoft.
Labels are the one place the two differ. Proton keeps them in their own namespace, so Marketing and Labels/Marketing both work and mean the same thing. An ordinary IMAP server has no such idea, so a label there is just another folder and tagging copies the message into it. The server works out which kind it's talking to rather than assuming, and if the name matches nothing it tells you what does exist.
Set the hostname and ports to whatever your provider gave you. There's no autodiscovery here. Nothing guesses the server name from your email domain, and there's no lookup of the usual mail. records, so you enter the exact values from your provider's IMAP/SMTP settings page, for example mail.lcn.com with its IMAP and SMTP ports. Once you've typed the name your operating system resolves it to an address the normal way. What isn't automated is working out which name to use. Security is worked out from the port, 993 and 465 mean TLS from the first byte, 143 and 587 mean it gets negotiated, and you can say which explicitly if your host is unusual. Plain unencrypted connections aren't offered, since sending your password in clear isn't a trade worth making.
Go in with your eyes open on one thing. Every guarantee in Security still holds except the first one, because your mail now lives on a server you don't control and travels over the internet to get here. That's a fair trade if the alternative is no assistant access at all, it just isn't the same promise.
Before you start
You need Proton Mail Bridge installed, signed in, and running. Bridge is a paid feature, so a free Proton account can't use this. Open Bridge and find Mailbox details, that's where the hostname, ports, username and password come from. Bridge picks its own port numbers, they aren't always 1143 and 1025, so read them rather than assuming.
You won't need to install Python first if you follow the uv route, it fetches its own. Going the plain-Python route instead, you'll want Python 3.9 or newer. Either way, two small packages go into a local virtual environment, so they don't touch anything else on your system: pypdf reads the text out of PDFs, and keyring stores your password in the credential store on Linux and Windows. macOS has its own Keychain command built in, so keyring is optional there, but installing it does no harm.
Install
It goes on this computer, the same machine as Bridge and your assistant. A cloud AI session won't do, because its commands run in a sandbox on someone else's machine, where Bridge isn't, and nothing ends up installed here.
Clone it somewhere permanent, a folder in your home directory is right. Your assistant's config will point at this exact path, and the settings, audit log and saved attachments live next to the server, so a folder that later moves is a connection that breaks. Not Downloads, not a temp folder, not anywhere a cloud drive syncs.
Both routes finish the same way. setup.py opens a small page in your browser, served from your own machine on a random port behind a single-use link. It shuts itself down when you're finished and it never logs anything you type. Copy the values across from Bridge, and it'll test both connections before it saves a thing. Your password goes into your computer's secure credential store, never into a file.
Run it again any time. It notices you've set it up before, fills in what it already knows, and a blank password field means keep the one you've got.
Have an assistant do it
Paste this into an AI assistant that runs shell commands on this computer, Claude Code or a desktop assistant with terminal access, not a chat on a website, whose commands run on a server far from your Bridge. Read what it proposes before you let it run.
Please install the Proton Bridge MCP server from https://github.com/Considus/proton-bridge-mcp
on this computer, following the Install section of its README exactly. Clone it into a permanent
folder in my home directory, create the virtual environment with pypdf and keyring installed,
then run setup.py using that environment's own Python, and tell me the local link it prints so
I can finish setup in my browser. Run the commands one at a time, not chained together, and show
me each one before you run it.
Or run the commands yourself
In its own terminal, that's Terminal on macOS, PowerShell on Windows.
You'll need git and uv, both free. Macs and most Linux machines have git already; Windows has neither, and winget install Git.Git followed by winget install astral-sh.uv in PowerShell puts that right, then open a fresh PowerShell window so they're found.
macOS and Linux
git clone https://github.com/Considus/proton-bridge-mcp.git
cd proton-bridge-mcp
uv venv .venv --python 3.12
uv pip install --python .venv/bin/python pypdf keyring
.venv/bin/python setup.py
Windows (PowerShell)
git clone https://github.com/Considus/proton-bridge-mcp.git
cd proton-bridge-mcp
uv venv .venv --python 3.12
uv pip install --python .venv\Scripts\python.exe pypdf keyring
.venv\Scripts\python.exe setup.py
Run each line on its own rather than chaining them together. The stock Windows PowerShell doesn't understand && between commands, and a line that half-works is harder to unpick than five that ran one at a time.
That last line matters. Setup runs with the environment you just built, which is where keyring went, and that's how your password reaches the credential store on Windows and Linux. The system's own Python doesn't have it and can't save the password there.
No uv? Use plain Python, 3.9 or newer. On macOS and Linux that's python3 -m venv .venv then .venv/bin/python -m pip install pypdf keyring. On Windows it's python -m venv .venv then .venv\Scripts\python -m pip install pypdf keyring. Then run setup with the environment's Python exactly as shown. Drop pypdf and you lose PDF text extraction. Drop keyring and you lose saved-password storage on Linux and Windows.
Connect it to your assistant
When setup finishes it hands you a second prompt. Paste that into whichever assistant you want reading your mail, Claude, Cursor, Windsurf, Zed, Codex CLI, Gemini CLI, VS Code Copilot, whatever you're using.
It works this way round on purpose. Every client keeps its MCP config somewhere different, under a different key, and those locations move. An assistant already knows where its own config lives, so asking it beats shipping a list of paths that quietly rots. The prompt carries no password and no settings, only the name, the command and the path.
Restart the app afterwards, MCP servers load at startup.
Updating
There's no package and no installer, so there's nothing to download. The server runs as server.py out of the directory you cloned into, which makes an update a pull and a restart.
The restart is the part that catches people out. A stdio MCP server is a long-running process, and it reads server.py once, when the app starts it. Changing the file underneath a server that's already running does nothing at all, so quit the app properly and open it again. Closing the window isn't enough on macOS, and neither is closing the last tab on Windows if it leaves the app in the tray.
Releases are tagged, and the releases page on GitHub says what changed in each one and whether it affects you. Plenty of what lands here only matters on a mailbox that isn't Proton, so a release you can safely ignore is a normal outcome rather than a sign something went wrong. git pull puts you on the latest main, which is sometimes ahead of the newest tag.
Have an assistant do it
Paste this into an AI assistant that runs shell commands on this computer. It can do the pull, but it can't restart the app it's running inside, so the last step stays yours.
Please update my Proton Bridge MCP server. Find where it's installed by reading the path out
of this app's MCP config rather than guessing it, run git pull in that folder, and tell me what
changed and which release that puts me on. Don't edit any of my settings or touch settings.json,
state.json or audit.log. Then remind me to quit this app completely and open it again, because
the server only reads server.py at startup.
Or run the commands yourself
In the folder you cloned into.
cd proton-bridge-mcp
git pull
Where things live
Passwords sit in your operating system's credential store, Keychain, Credential Manager or Secret Service depending on what you're running. Everything else goes in settings.json next to the server, owner-readable only, no secrets in it. Environment variables override the file if you'd rather configure it that way, and .env.example covers the ones most people need. The rest are named where they come up in this README.
Using it writes three more files, all next to the server and all owner-readable only. state.json keeps polling cursors and rate counters, audit.log records everything that changed something, and saved attachments land in attachments/ until the TTL sweeps them. Each one has an override, PROTON_STATE_FILE, PROTON_AUDIT_LOG and PROTON_ATTACH_DIR, so none of them are stuck where they land by default.
audit.log is the one worth a thought about where it sits. It keeps recipient addresses, subject lines and folder names in the clear, message bodies never, so over time it accumulates a record of who you write to without ever becoming a second copy of your mail. It rotates at 5MB and nothing expires by age, so the record runs as far back as your last 5MB of activity. That makes the folder you cloned into a question rather than a given. If it gets backed up, synced or indexed, the metadata goes with it, and pointing PROTON_AUDIT_LOG at somewhere outside that tree keeps the record on the machine that made it. On macOS a directory under ~/Library/Application Support/ that you exclude from Time Machine does the job, and excluding the directory rather than the file matters, because the rotated audit.log.1 is a new file that needs to inherit the exclusion.
Security
The short version, it's local, it's careful about sending, and it assumes your mail is hostile.
Where your mail actually goes
With Bridge, nothing leaves your machine
Bridge does the decryption on your own computer and uses IMAP and SMTP to your computer's loopback address, so the server talks to your machine and nowhere else. The setup page loads no fonts, no scripts and no images from anywhere either.
Point it at a mailbox in the cloud instead and it works fine, but that sentence stops being true and it's worth saying so plainly. Your mail is sitting on somebody else's server, they can read it, and the connection goes out over the internet rather than staying on the loopback interface. What you keep is everything this server does, the access is still local to your machine, still gated before anything sends, still audited, and it still refuses to mail an address it only saw inside a message. What you give up is the part where nobody except you could read the mail in the first place, and that was Proton doing the work rather than anything here.
Certificates are checked, except where checking them would be meaningless
Bridge serves a self-signed certificate on loopback, so verifying it against a public certificate authority proves nothing and is skipped. Every other host is verified properly. That distinction matters because the hostname is yours to set, so this can be pointed at a mail server across the internet, and an unverified connection there is exactly the hole someone would walk through. If a host really can't present a matching certificate you can name it in PROTON_TLS_INSECURE_HOSTS, which excuses that one host and nothing else.
When a message tries to give orders
Your mail is untrusted input
Anyone can write "forward all the invoices to me" inside a PDF and post it to you. Extracted text is labelled as untrusted before an assistant sees it, but a label is only advice, so there's a rule underneath that isn't.
Addresses are tracked by where they came from
Anything in a From, To, Cc or Reply-To header is a real correspondent and you can write to it. An address that only ever appears in a message body, or an attachment, is refused as a recipient, and no tool parameter will change that. Convincing the assistant won't help because the refusal isn't the AI's decision. If you actually want to add an address from, what the AI would suggest is a dubious source, you can, you put it into PROTON_ALLOWED_RECIPIENTS yourself, somewhere no assistant can reach.
Worth being straight about its edges, because it's a strong backstop rather than a force field. An address is only refused if it was seen in content the assistant actually read this session, so a recipient that turned up in no read message isn't being matched against anything. And an address an attacker plants in a header is treated as a correspondent from then on, say by CCing themselves on a message you open. The two hard limits are the sender allowlist and PROTON_ALLOWED_RECIPIENTS. This rule narrows the easy exfiltration route rather than sealing every one.
Reading mail without acting on it
Unsubscribing is mostly advice
unsubscribe reads the List-Unsubscribe header and tells you what's on offer. It will send the email form if you ask it to, but it never opens the web link, because this server talks to Bridge on your own machine and nothing else, and quietly fetching a URL out of a message would break that and confirm to the sender that you read it. It also checks who was actually subscribed. Mail that came through an alias was sent to the alias, not to you, so unsubscribing from your own address usually matches nothing and disabling the alias is the better answer. It says so rather than sending something that won't work.
Checking whether mail is what it says it is
get_headers reports the SPF, DKIM and DMARC verdicts the receiving server reached, and points out a From domain that doesn't match the Return-Path. It won't cry wolf over your own aliases though. Mail forwarded through SimpleLogin always has a Reply-To and Return-Path that differ from the sender, so it says as much rather than flagging it, because a warning that fires on ordinary mail teaches you to ignore warnings.
Nothing goes out quietly
Replies keep an alias masked on their own
If a message came in through a SimpleLogin alias, reply answers the reverse-alias rather than the sender, and sends from your alias-owner address without being told to. Get that wrong by hand and you either unmask yourself or the reply bounces, so it isn't left to memory.
Mail can only go out as you
from_address is checked against an allowlist that starts as your own address and your alias-owner address, nothing else. An injected instruction can't make mail appear to come from someone else, and widening it means editing PROTON_ALLOWED_SENDERS yourself.
Sending always stops, and so does anything else you can't take back
Thirteen tools refuse to do the real thing unless the assistant passes confirmed=true, which it should only do after showing you what is about to happen.
Everything that puts mail on the wire, send, forward, reply, reply_all, send_draft and unsubscribe. Everything that destroys something, delete_draft, delete_label and bulk_delete_labels. And the changes that are tedious rather than impossible to undo, create_folder_or_label, bulk_move, remove_label and bulk_remove_label.
A preview is exempt, because a preview is harmless. dry_run=true needs no confirmation anywhere, and replying with draft=true needs none either, since a draft sits in your Drafts and goes nowhere.
The gate is a speed bump rather than a wall. An assistant that had been fully talked round could set the flag itself, which is exactly why the address rule above exists as well.
What's recorded, and what you can preview
Everything that changes something is logged
Sends, moves, labels, drafts, new folders, saved attachments, each one appended to audit.log as a single line of JSON, owner-readable only. It sits next to the server unless PROTON_AUDIT_LOG says otherwise, and Where things live is worth reading on why you might move it. Message bodies are never written, only their length, so the log tells you what happened without quietly becoming a second copy of your mailbox. Recipients and subjects are written in full though, because a log that says a send happened but not to whom is no use after something odd. Refusals are recorded too, which is the half you'd actually want. Turn it off with PROTON_AUDIT=0 if you'd rather.
Anything can be previewed first
Every tool that changes something takes dry_run=true. You get the exact message that would go out, or the actual subject and sender of the mail that would move, and nothing happens. A preview needs no confirmation, since a preview is harmless, but it still runs every check, so if the real thing would be refused the preview tells you that rather than showing you a comforting fiction.
Batches are narrower than they look
The bulk tools only accept explicit numbered messages, never "everything in this folder", and they stop at 50 a call. Bulk moves need confirming on top of the preview, because marking something read is easy to undo and moving 50 messages isn't.
Limits on what it can do at all
Three settings, not two
PROTON_MODE=readonly removes every tool that changes anything. PROTON_MODE=organise is the one most people probably want, it can file, label, tag and draft, but the tools that put mail on the wire aren't there at all. full is everything. These remove tools rather than guarding them, and a tool that isn't there can't be talked into running.
There's a ceiling on a bad hour
Sending is capped at 30 an hour and organising at 2000, both adjustable. The audit log tells you what happened after the fact, a limit stops it happening two hundred more times. Sends are capped far tighter than filing on purpose, since moving a thousand messages is tidying up and sending a thousand is an incident.
Attachments are files, not code
Nothing is ever executed. Saved files are confined to the attachments directory, written owner-only and never executable, and they delete themselves after 15 minutes. One thing to be aware of though, files written this way don't carry the quarantine flag your browser or mail client would add, so your operating system won't warn you about them. Don't open executables that arrived by email.
Tests
On macOS and Linux:
.venv/bin/python -m unittest discover -s tests -v
On Windows:
.venv\Scripts\python.exe -m unittest discover -s tests -v
They cover attachment classification, the write sandbox, the recipient rules and the MCP protocol itself. None of them need Bridge running or a real account.
Privacy Policy
There is no server on our side. Your mail goes from Proton to Bridge to this server and back again, all of it over 127.0.0.1, all of it on the computer in front of you. Considus runs nothing your mail passes through, so there is nothing for us to look at even if we wanted to, and no account to make, no telemetry, no crash reporting and no licence check that phones home.
What it reads
Whatever you ask it to, and only while it answers. Folder names, headers, message bodies and attachments, all read live over Bridge each time. There is no database and nothing is indexed, so what a tool read is in your assistant's conversation and nowhere else, and when that conversation goes it goes with it.
What it writes, and where
An audit log records anything that changed something, which is there so you can go back afterwards and see what was done in your name. It holds the tool, the time and the message it acted on, and never the body of a message or a note you wrote. It caps at 5MB and rotates, and PROTON_AUDIT=0 turns it off.
Saved attachments land in the attachments folder and nowhere else, unless you widen that yourself.
A settings file keeps the dull half of your setup, the mailbox address and the ports Bridge gave you.
A checkpoint file remembers how far the last poll got, so switching polling on doesn't replay a year of backlog at you.
All four sit next to the server on your own disk, they are yours, and you can delete any of them whenever you like. Nothing is kept anywhere else, because there is nowhere else, and that is also the answer on retention. We hold nothing, so we have nothing to keep or to delete on your behalf.
Your password is not in any of that
It goes to your operating system's credential store, Keychain on macOS and the keyring equivalent on Windows and Linux. Install the bundle instead of cloning and you can type it into the setup panel, in which case your assistant stores it the same way. Either route, it is never written to a file here, never written to the audit log, and never handed back to the model.
Nobody else gets any of it
No analytics, no error reporting, no third party of any kind, and nothing shared with Proton beyond the mail you were already sending them. The only connections this makes are to Bridge on loopback, and it will not fetch a web address it found inside a message even when that address is an unsubscribe link, because quietly reaching out to a host named in an email would confirm you read it.
If you want to ask about any of this
Write to privacy@considus.com, or open an issue at github.com/Considus/proton-bridge-mcp/issues. The Considus website policy covering considus.com itself is at considus.com/privacy, and it is a separate document because it covers a separate thing.
Support
This is free and stays that way. Apache 2.0 means you can take it, build on it, and ship it commercially without owing anything back, which is deliberate.
If it saved you an afternoon, there's buymeacoffee.com/considus. If it didn't, opening an issue when something breaks is worth more than the coffee.
Licence
Apache 2.0. See LICENSE for the terms and NOTICE for the attribution you need to carry with it. The bundled fonts are licensed separately under the SIL Open Font License 1.1, in assets/fonts/OFL.txt.