Skip to main content
Glama
proton-bridge-mcp

proton-bridge-mcp

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.

Docs · Install · Security · FAQ

proton-bridge-mcp MCP server

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. Name one and you get its counts, plus the UIDVALIDITY every uid in it depends on

poll_folder

What has arrived since you last looked, and confirms a batch was handled

search_mail

Search by text, sender, subject, date range, unread and starred, in one folder or across every folder and label with duplicates collapsed. With nothing to search for, it lists the newest. Can also report each message's other labels and folders

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, and hands an image back 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

reply

Replies with correct threading, to everyone if you ask, 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 or unstar, one message or many in one pass

apply_label

Tags one message or many, leaves them where they are

remove_label

Takes a label off one message or many, leaves them where they are, gated

move_to_folder

Files a message somewhere else, or many at once, gated for many

create_folder_or_label

New folder or label, gated

rename_folder_or_label

Renames one, gated, and leaves Inbox, Sent, Trash and the rest of the built-in folders alone

delete_label

Deletes one label or several, messages keep their place and lose the tag, gated

send

Gated, and can carry attachments

forward

Gated

Three things it can't do, and won't pretend otherwise. Folders can be created and renamed 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, by polling once more with the checkpoint as commit. 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 situations, two tools. 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. And read_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_mail across every folder matches on Message-ID, arrival time and size 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.

Related MCP server: ProtonMail MCP Server

Things to actually ask it

Four that exercise different parts of it, and none of them need you to know a tool name.

"Find the invoices from my accountant this quarter and total them up." Searches, then opens the attachments and reads the text out of the PDFs. This is the one that justifies bundling a PDF library rather than telling you an invoice exists and leaving you to open it.

"What's arrived since I last checked, and what actually needs me?" Uses the checkpointed batch, so if it falls over halfway you get the same batch again rather than losing it. Poll with advance=false and nothing moves until you say so.

"File everything from Companies House into Admin, but show me the list first." A search, then a bulk move. The preview runs every check the real thing would, and the move needs confirming on top of it, because moving 50 messages somewhere you didn't intend is an afternoon.

"Draft a reply to Sam saying I'll confirm Monday. Don't send it." Goes to your Drafts and stops. That path needs no confirmation at all, because nothing has gone anywhere.

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.

When something goes wrong

The connection is refused. Bridge isn't running, or it isn't on the ports this is looking at. Bridge picks its own port numbers per install and they aren't always 1143 and 1025, so open it, read the real ones out of Mailbox details, and set PROTON_IMAP_PORT and PROTON_SMTP_PORT to match.

The login is rejected. This is the common one by a distance. Bridge has a password of its own, shown in Bridge under Mailbox details, and it isn't your Proton account password. The account password is refused every time, and what you get back looks like a broken install rather than the wrong credential.

It says setup is required. Run python3 setup.py and let it walk you through. Doing it by hand instead, set PROTON_USER to the address Bridge shows you, then supply the Bridge password either through the PROTON_BRIDGE_PASSWORD environment variable or through your computer's credential store.

A uid that worked ten minutes ago is refused. The folder resynced underneath you and those numbers now point at different mail, so the refusal is the tool doing its job rather than failing at it, described in UIDs go stale. Search again and act on the uid the search hands back, not the one you were holding.

An attachment you can see in Proton isn't there. Proton's app groups mail into conversations, IMAP hands over individual messages, and the reply sitting in your inbox can be completely empty while the original, filed somewhere else, carries the PDFs. find_thread is the way round it, and Conversations aren't messages is why.

It stopped and asked you before sending. Working as designed, not a fault. Nothing leaves the machine without you saying so, which is Nothing goes out quietly.

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. Every setting has the full list.

Using it writes three more files, all next to the server and all owner-readable only. state.json keeps polling cursors, rate counters, the addresses refused as recipients and a list of the attachments it saved, with an empty state.json.lock beside it, 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, nothing expires by age, and rotating never drops a record less than a week old, so the record runs as far back as your last 5MB of activity or the last week, whichever is further. 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.

Every setting

All of these work as environment variables, and most can sit in settings.json too, with the environment winning where both are set. The few marked "env only" are read from the environment and nowhere else, so put those in your MCP client's env block.

Connecting to Bridge

Setting

Default

What it does

PROTON_USER

none, it's required

The Bridge username, the address Bridge shows you

PROTON_BRIDGE_PASSWORD

none

The Bridge password, if you'd rather not use the credential store

PROTON_KEYCHAIN_SVC

proton-bridge-imap

The name the password is saved under in the credential store

PROTON_IMAP_HOST

127.0.0.1

Where to read mail from

PROTON_IMAP_PORT

1143

Bridge can pick a different one, so check Mailbox details

PROTON_IMAP_SECURITY

auto

auto, ssl or starttls

PROTON_IMAP_TIMEOUT_S

60

Seconds one IMAP read or write can wait before it gives up

PROTON_SMTP_HOST

127.0.0.1

Where to send mail through

PROTON_SMTP_PORT

1025

Bridge can pick a different one here too

PROTON_SMTP_SECURITY

auto

auto, ssl or starttls

PROTON_SMTP_USER

the same as PROTON_USER

Only needed if sending ever uses a different login

PROTON_SMTP_PASSWORD

none

A separate sending password. Without one, the reading password is used when the user is the same

PROTON_SMTP_KEYCHAIN_SVC

proton-bridge-smtp

The name a separate sending password is saved under

PROTON_TLS_INSECURE_HOSTS

none

Comma-separated remote hosts excused from certificate checks

PROTON_AUTHSERV_ID

mail.protonmail.ch

The server whose SPF, DKIM and DMARC verdicts get_headers trusts, comma-separated if there's more than one

What it's allowed to do

Setting

Default

What it does

PROTON_MODE

full

full, organise or readonly

PROTON_READONLY

off

1, true, yes or on forces readonly, whatever PROTON_MODE says

PROTON_ALIAS_FROM

none

The address replies go out from when you answer through a SimpleLogin reverse-alias

PROTON_ALLOWED_SENDERS

your own addresses

Extra From addresses, comma-separated

PROTON_ALLOWED_RECIPIENTS

none

Addresses allowed as recipients even though they only turned up inside a message, comma-separated. Env only

PROTON_ATTACH_SOURCE_DIRS

the attachments folder

Folders a file can be attached from, comma-separated

Limits

Setting

Default

What it does

PROTON_RATE_SEND_PER_HOUR

30

Sends in any hour

PROTON_RATE_WRITE_PER_HOUR

2000

Every other change in any hour

PROTON_MAX_BULK

50

Messages one bulk call can act on

PROTON_MAX_FETCH_MB

2

The largest image read_attachment shows inline

PROTON_MAX_ATTACH_MB

25

The largest other attachment read_attachment or save_attachment will take. Env only

PROTON_MAX_PDF_PAGES

200

PDF pages read as text. Env only

PROTON_PDF_TIMEOUT_S

10

Seconds reading one PDF can take. Env only

PROTON_MAX_OUTGOING_MB

20

Total size of the files attached to one message you send

PROTON_MAX_OUTGOING_FILES

10

Files attached to one message you send

PROTON_ATTACH_TTL_SECONDS

900

How long a saved attachment stays before it's swept, unless it was saved with persist. Env only

Files

Setting

Default

What it does

PROTON_SETTINGS_FILE

settings.json next to the server

Where the settings file is. Env only, as it can't very well be in itself

PROTON_STATE_FILE

state.json next to the server

Polling cursors, rate counters and refused addresses

PROTON_ATTACH_DIR

attachments/ next to the server

Where saved attachments go

PROTON_STOP_FILE

STOP next to the server

While anything is there, nothing is sent

PROTON_AUDIT_LOG

audit.log next to the server

The audit log

PROTON_AUDIT

1

0 turns the audit log off

PROTON_AUDIT_MAX_MB

5

The size the audit log rotates at

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

The address 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, a subject line, the name shown beside an address, or an attachment or its filename, 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, so a recipient that turned up in no read message isn't being matched against anything. The refused addresses are saved in state.json, so a restart doesn't clear them. If the messages read ever hold more than 20,000 of them, every recipient outside the headers is refused until you delete state.json. The same goes if state.json ever gets damaged and can't be read, because it can no longer tell what it's meant to be refusing. 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 your own mail server reached, and points out a From domain that doesn't match the Return-Path. Only your server's verdicts count, mind, because anyone sending you mail can write a header claiming they passed. Proton stamps its own as mail.protonmail.ch, so that's the default, and if your mail lives somewhere else, PROTON_AUTHSERV_ID names its server instead. 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. It won't vouch for the mail on that basis either, since the header saying it came through SimpleLogin is one any sender can write too.

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. It does check first, though, because any sender can write the header that says a message came through SimpleLogin, so it only switches when your own mail server's stamp shows it really did. 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

Eleven 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, send_draft and unsubscribe. Everything that destroys something, delete_draft and delete_label. And the changes that are tedious rather than impossible to undo, create_folder_or_label, rename_folder_or_label, remove_label, and move_to_folder when it's given a batch.

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

A batch is only ever explicit numbered messages, never "everything in this folder", and it stops at 50 a call. Moving a batch needs 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 recipients 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.

Stopping a run that's already going

Put a file called STOP in the server's folder and every send is refused from the next one on, previews included. Nothing else changes, it can still read, file and draft, and there's no restart, so stopping one run doesn't mean pulling the whole server. Delete the file and sending comes back. It can be empty, and the ending doesn't matter, so the STOP.rtf TextEdit quietly makes works too. PROTON_STOP_FILE moves it somewhere easier to reach if you'd rather.

None of this server's tools can make or remove that file. An assistant that also has a shell on your computer could, though, so it's the quickest brake rather than the last one.

There's also a quicker way to stop just the session you're in. Type /mcp__proton-mail__stop in Claude Code and that session can't send again, previews included, until it ends. Like the file, it only stops sending, so reading, filing and drafting carry on. It's a command you run rather than a tool the assistant sees, so the assistant can't run it, and nothing it does through this server can undo it. To send again, start a new session.

It's per session because most apps start a separate copy of the server for each session, and the stop lives in that copy. An app that runs one copy for all its chats would stop all of them, which is, at least, the safe way round.

Support is patchy. I've tested it in Claude Code. VS Code, Gemini CLI, Zed, opencode and Cursor say they support MCP prompts, which is what this is, but I haven't tried them. Codex doesn't support prompts yet, and Claude Desktop reportedly won't attach them from a local server like this one. So the STOP file stays the brake that works everywhere, including a run you're not sat in front of.

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 rotates at 5MB, always keeping at least the last week, 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.

Something broken or behaving oddly, open an issue. Anything exploitable goes through GitHub's private reporting or to security@considus.com instead, as described in SECURITY.md, not a public issue. For anything that doesn't fit either, including press and licensing, it's support@considus.com, and the rest of the ways to reach us are at considus.com/support.

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.

Available Tools

25 tools
apply_labelApply a labelA
Idempotent

Tag one message (uid) or many (uids) with an existing label. Messages stay where they are.

Inputs: label (required), uid, uids, folder, +2 more.

create_draftCreate a draftA

Write a draft into the Proton Drafts folder. Never sends. For a reply, pass in_reply_to (the original Message-ID).

Inputs: to (required), body (required), subject (required), cc, +4 more.

create_folder_or_labelCreate a folder or labelA
Idempotent

Create a new label or folder. GATED: it changes your folder and label structure, so confirm the exact name and type with the user, then call with confirmed=true.

Inputs: kind (required), name (required), confirmed (required), dry_run.

delete_draftDelete a draftA
DestructiveIdempotent

Move a draft to Trash. GATED. Nothing here deletes permanently.

Inputs: uid (required), folder, dry_run, confirmed, +1 more.

delete_labelDelete a labelA
DestructiveIdempotent

Delete a label outright. Every message that carried it stays exactly where it is and simply loses the tag — no mail is deleted. Labels only; folders are where messages live and are not deletable here. Pass label for one or labels for several. GATED.

Inputs: label, labels, dry_run, confirmed.

find_threadFind a threadA
Read-only

Pull every message of a conversation from All Mail, showing which ones carry documents. ESSENTIAL: Proton's UI groups conversations but IMAP exposes single messages, so an inbox 'Re:' can look attachment-free while the thread's original holds the PDFs. Run this before concluding what a thread needs.

Inputs: uid, folder, subject.

forwardForwardA
Destructive

Forward an existing message to someone on demand. GATED like send: confirm recipient with the user, then call with confirmed=true.

Inputs: to (required), uid (required), confirmed (required), note, +4 more.

get_headersGet message headersA
Read-only

Headers plus SPF/DKIM/DMARC verdicts and Proton metadata, for judging whether a message is what it claims to be. Pass raw=true for the unparsed header block.

Inputs: uid (required), raw, folder, uidvalidity.

list_attachmentsList attachmentsA
Read-only

List a message's attachments, separating real DOCUMENTS from inline cid: images and PGP keys (hidden by default). Call before claiming a message has no attachment — read_message shows body text only and never reveals files.

Inputs: uid (required), folder, uidvalidity, include_inline.

list_foldersList folders and labelsA
Read-only

List all Proton folders and labels available over the Bridge. Call this first to learn exact folder/label names for filing and tagging. Pass folder to get that folder's message counts plus UIDNEXT and UIDVALIDITY instead: UIDs are only valid within one UIDVALIDITY generation, so check it before acting on uids noted earlier.

Inputs: folder.

markMark a messageA
Idempotent

Mark one message (uid) or many in one pass (uids) read/unread or star/unstar.

Inputs: action (required), uid, uids, folder, +2 more.

move_to_folderMove to folderA
Idempotent

File one message (uid) or many (uids): move it into another folder (e.g. 'Folders/', 'Archive', 'Trash'). Many at once is GATED: preview with dry_run=true, show the user, then call with confirmed=true.

Inputs: to_folder (required), uid, uids, folder, +3 more.

poll_folderPoll for new mailA

Messages that have arrived since the last poll. The very first poll emits nothing and just records where the mailbox ends, so turning this on does not replay the backlog. Marks nothing as read. Pass commit to commit a checkpoint an advance=false poll returned; repeating that is harmless.

Inputs: limit, commit, folder, advance.

purge_attachmentsDelete saved attachmentsA
DestructiveIdempotent

Immediately delete all ephemeral saved attachments. Call after reading a saved file so nothing lingers on disk.

Inputs: dry_run.

read_attachmentRead an attachmentA
Read-only

Read an attachment inline. Text comes back as text: PDFs via pypdf, plus text/csv/json/ics/eml, for invoices, decks, reports. An image comes back as a viewable image, the only way to see a photo or a scan when the client cannot read local files. Other binary types must use save_attachment instead.

Inputs: uid (required), folder, filename, uidvalidity, +1 more.

read_messageRead a messageA
Read-only

Read the full headers and body text of one message by uid within a folder.

Inputs: uid (required), folder, uidvalidity.

remove_labelRemove a labelA
Idempotent

Take a label off one message (uid) or many (uids). The messages stay where they are. GATED: removing a label ends in an expunge against the label mailbox, so confirm it or preview with dry_run=true first.

Inputs: label (required), uid, uids, folder, +3 more.

rename_folder_or_labelRename a folder or labelA

Rename a folder or label where it stands: a label stays a label and a folder keeps its parent. INBOX and the special mailboxes (Sent, Drafts, Trash, Spam, Archive, All Mail) are refused. GATED: confirm the old and new names with the user, then call with confirmed=true.

Inputs: name (required), new_name (required), dry_run, confirmed.

replyReplyA
Destructive

Reply to one message with correct threading. Replies to the Reply-To address when there is one, so alias mail stays masked. Pass all=true to reply to everyone, with your own addresses removed from Cc and duplicates dropped. Pass draft=true to save it for review, or confirmed=true to send.

Inputs: uid (required), body (required), all, draft, +8 more.

save_attachmentSave an attachmentA
Idempotent

Write attachment(s) to disk and return the path(s). Use for images, scanned PDFs, or anything not text-extractable. EPHEMERAL by default — files self-delete after the TTL.

Inputs: uid (required), folder, dry_run, persist, +4 more.

search_mailSearch a folderA
Read-only

Search a folder. Combine any of: text, from, subject, since (DD-Mon-YYYY), unread_only, flagged_only. Returns uids + envelopes, newest first. With no filters it lists the newest messages in the folder. Set include_locations to also report which labels and folders each message carries. Set all_folders to search every mailbox at once when you do not know where something is: duplicates collapse by Message-ID and each row says where the message lives.

Inputs: from, text, limit, since, +8 more.

sendSend mailA
Destructive

Send a new email. GATED: the agent must show the user the exact To/Subject/Body, get an explicit yes, then call with confirmed=true. Never call unattended.

Inputs: to (required), body (required), subject (required), confirmed (required), +6 more.

send_draftSend a draftA
Destructive

Send a saved draft as written, then move it to Trash. GATED. Sender and recipient checks run again at send time.

Inputs: uid (required), folder, dry_run, confirmed, +1 more.

unsubscribeUnsubscribeA
Destructive

Report how to unsubscribe from a message using its List-Unsubscribe header, and optionally send the email form. Reports only unless send=true. Web links are never fetched for you.

Inputs: uid (required), send, folder, dry_run, +2 more.

update_draftUpdate a draftA
DestructiveIdempotent

Replace a draft's contents. Threading headers are carried over, the new version is saved before the old one is binned. Omit a field to keep what the draft already had.

Inputs: uid (required), cc, to, body, +6 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 25 tools

    Disambiguation4/5

    Most tools target a distinct resource-action pair, and the descriptions make boundaries clear (e.g., apply_label vs. remove_label vs. delete_label; read_message vs. get_headers). The few potentially confusable areas are sending pathways (send, send_draft, reply, forward) and attachment handling (list_attachments, read_attachment, save_attachment), but each has a clear stated role.

    Naming Consistency4/5

    Names are consistently snake_case and mostly follow verb_noun form (list_folders, read_message, create_draft, move_to_folder). A handful use single verbs (send, forward, reply, mark, unsubscribe), which is readable and conventional but slightly less uniform than the rest.

    Tool Count3/5

    The 25-tool surface covers a broad email client domain, but it sits at the heavy end for an MCP server and includes several operations that could plausibly be consolidated (label/folder management, attachment lifecycle). Each tool has a reason to exist, yet the set feels large rather than tightly scoped.

    Completeness4/5

    Coverage is strong across reading, searching, threading, labels/folders, drafts, sending, forwarding, replying, attachments, marking, and unsubscribe flows. Minor gaps remain, notably no folder deletion and no permanent message deletion or empty-trash operation, only moves to Trash.

    Maintenance

    ActivityActive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables AI clients to interact with ProtonMail accounts through the Proton Bridge using SMTP and IMAP protocols. Provides email management capabilities via secure local bridge connections.
      8 npm
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI agents to send, read, search, and organize emails via ProtonMail using Proton Bridge. Supports MCP-compatible clients like Claude and Cursor.
      18
      20 npm
      4
      MIT
    • A
      license
      A
      quality
      C
      maintenance
      Unofficial MCP server for Proton Mail (not affiliated with Proton AG) — send, read, search & organize email over SMTP/IMAP
      31
      79 npm
      8
      MIT
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables AI assistants to send, read, search, and organize email from Proton Mail accounts via SMTP and IMAP.
      79 npm
      MIT