proton-mcp
This local MCP server connects AI assistants to Proton Mail via Bridge, providing tools to read, search, organize, draft, and send emails securely.
Read & Search: List folders/labels, list messages, search by criteria (text, subject, sender, recipient, date, read/starred state, size), read messages as plain text (HTML converted), and fetch text attachments (binary refused).
Organize: Apply/remove labels, move messages between folders, mark read/unread or starred, and trash messages (no permanent deletion).
Draft & Send: Create, update, reply to, forward, and list drafts. Send new messages, replies, forwards, or drafts, with mandatory user confirmation.
Configuration & Security: Open a local web UI for sign-in, connection status, and credential management. Runs entirely locally with certificate pinning. Supports read-only mode. No access to calendar, contacts, Proton-specific features, or binary attachments.
Provides tools for interacting with Proton Mail, including reading emails, listing folders, searching messages, and retrieving attachments through the local Proton Mail Bridge.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@proton-mcpsearch for unread emails from last week"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
proton-mcp
An MCP server for Proton Mail. It talks to a locally running Proton Mail Bridge and makes your mailbox available to AI assistants such as Claude.
Status: under construction. Reading works and can be used, and so does signing in through the browser. Labels, moving between folders, read state, trash, drafts and sending all work. Every send asks you first, and that question is enforced by the server rather than requested in a prompt. See Status for the details.
Why everything runs locally
Proton Mail Bridge only listens on 127.0.0.1. It cannot be reached from outside, and that is by design: your messages are decrypted on your own machine.
This server therefore runs on your machine as well, started by the AI client as a child process. Your mail leaves your machine only as far as you show content to the assistant yourself. There is no remote access and no service in between.
Related MCP server: protonmail-mcp-server
Status
What works
Tool | What it does |
| Lists folders and labels, grouped by kind |
| Headers of a mailbox, newest first, with paging |
| Full text, subject, sender, recipient, date range, read state, star, size |
| One message as readable text, HTML converted, budgeted |
| One attachment by index, textual types only |
| Applies an existing label. The message stays in its folder |
| Removes a label. Folder, other labels and the message itself are untouched |
| Moves messages into a folder. Labels survive the move |
| Marks read or unread and sets or clears the star |
| Moves messages to the trash, which is what deletion means here |
| Writes a draft. Nothing is sent |
| Replaces a draft, keeping its id |
| Prepares a reply as a draft, with the original quoted |
| Prepares a forward as a draft, attachments and all |
| The drafts, newest first |
| Composes and sends. Asks you first, always |
| Replies and sends, keeping the conversation intact |
| Forwards and sends, attachments and all |
| Sends a draft that is already written |
Every tool that composes a message takes either text or html, never both: Proton drops the plain text half of a message that carries markup, so the half you confirmed would be the half that never arrived.
The server also tells an assistant how it works, so the rules are not something a model has to infer from failures. A short text travels with every connection naming the four things that go wrong quietly, and two longer guides can be read on demand: proton-mcp://guide/writing for composing messages and proton-mcp://guide/bridge for the measured behaviour of the Bridge itself.
Underneath: a held IMAP connection that recovers from a Bridge restart, stable identifiers based on the Message-ID, HTML to text conversion, filtering of the public key Proton attaches to every sent message, and a character budget so a single message cannot exhaust a context window.
There is also a local web interface, on 127.0.0.1 only, running for as long as the server does. It is divided into sections, reached from a rail on the left:
Section | What is there |
Overview | Connection, address, where the password is kept, read-only mode, both Bridge ports, uptime and counters. State only: every control lives in the section it acts on |
Folders and labels | On its own page, only when asked. They belong to the whole account, so a folder named after a bank gives that away on its own |
Bridge | The ports, the pinned certificate, and a button that tries the connection. Ports are stored in |
Credentials | Where the Bridge password is kept, and signing out, which clears it from every store at once |
Activity | What is running and for how long, plus the last 50 finished calls. Tool names and timings only, never arguments |
Before signing in there are two more: the sign-in form, where the address and Bridge password go into the browser rather than into a client configuration or a conversation, with four places to keep the password and the cost of each stated; and the unlock page, which after a restart asks for the master password alone, since the Bridge password is already on disk.
None of it uses JavaScript. The Content-Security-Policy forbids it, so navigation is links and anything interactive is a form control. It follows the system light or dark setting, which needs no script either.
When a tool is called before anyone has signed in, the server does not ask for the password in the conversation. It returns the address of that page, and on clients that support URL elicitation it asks the client to open it directly.
What does not exist yet
This table says what the server cannot do today. The issue behind each entry says what is planned and how far it has got.
Missing | Tracked in |
Attachments composed from files on this machine | |
Choosing per tool what an assistant may do | |
Choosing how much markup a message may carry | |
A fixed extra recipient the assistant cannot remove |
Binary attachments are missing from that table on purpose. They are not an unfinished feature waiting for its turn: get_attachment refuses anything that is not text and says why. Handing one over would mean either base64 in the context window, which is unusable, or writing decrypted content to your disk, which this server does not do. Changing that needs a decision first, not an implementation, so there is nothing to track yet.
What will never exist
These limits come from the Bridge itself and cannot be worked around:
No calendar, no contacts. Neither flows through the Bridge.
No Proton-specific features. No message expiration, no password-protected messages to outside recipients.
No access to filters, forwarding rules or account settings. This is also a deliberate security boundary, see below.
No server-side threading or sorting. The Bridge supports neither
THREADnorSORT, so ordering happens in this server, which costs one date read per message before a page can be cut out.No permanent deletion. Deleted will mean moved to trash, on purpose.
Requirements
A paid Proton plan. The Bridge is not included in the free tier.
Proton Mail Bridge, installed, running and unlocked. The official application expects a desktop. On a machine without one, proton-mail-bridge-docker runs it in a container instead. That is a way to get a bridge, not a way to put one somewhere else: it decrypts your mail wherever it runs, so a bridge on another machine means your mail is decrypted on that machine. The section above still applies, and the container's own readme says the same.
Node.js 24 or newer.
The Bridge password, which is not your Proton account password. The Bridge generates one per account. You find it in the Bridge application under the account, or in a terminal via
protonmail-bridge --cliand theninfo.
Installing
Hand it to your assistant
If you already have an AI assistant with a shell, the shortest route is to let it do the work:
Fetch https://raw.githubusercontent.com/RndmJoker/proton-mcp/main/prompt.md and follow itIt checks the prerequisites, registers the server with your client and tells you what to do next. It never asks for your Bridge password and cannot: that goes into the local web interface, in your browser, and nowhere else.
From npm
npx @rndmjoker/proton-mcpThat is also the command to register with your client, so nothing has to be installed permanently. The published package carries provenance, so npm can show which commit and which workflow built it.
To register it with Claude Code:
claude mcp add proton-mcp -- npx -y @rndmjoker/proton-mcpFor Claude Desktop, Cursor and anything else that reads a JSON file:
{
"mcpServers": {
"proton-mcp": {
"command": "npx",
"args": ["-y", "@rndmjoker/proton-mcp"]
}
}
}Nothing else belongs in that entry. Credentials are not passed here, see Signing in.
From this repository
To run the current development state, or to change something. A setup script does the work on either platform.
Linux and macOS
git clone https://github.com/RndmJoker/proton-mcp.git
cd proton-mcp
./scripts/setup.shWindows
git clone https://github.com/RndmJoker/proton-mcp.git
cd proton-mcp
.\scripts\setup.ps1The script checks that Node.js is new enough, installs the dependencies, builds the server, runs the tests, and creates the credentials file with placeholders if it does not exist yet. It prints the exact line to register the server with your client. It never touches an existing credentials file.
Updating
Same script, one flag:
./scripts/setup.sh --update # Linux and macOS.\scripts\setup.ps1 -Update # WindowsThat pulls the latest commits, then installs, builds and tests again. If you have uncommitted changes in the working copy, the script stops instead of discarding them.
Signing in
Nothing needs to be entered before the first start. Register the server with your client, then ask the assistant anything about your mail. It will answer with a 127.0.0.1 address, or open it for you if your client supports that. Sign in there, in your browser, and repeat the request.
The page asks for two things: your Proton address and the Bridge password, which is the one the Bridge generates and not your Proton account password.
Then it asks where the password should be kept. All four options are supported and none is a fallback; what differs is the drawback you accept:
Option | On disk | You type something | Suits |
System keyring | Protected by the operating system | Rarely | A desktop machine you use yourself. The ordinary case, and the default when it works |
Encrypted file | Encrypted with your master password | Every server start | Servers, containers, headless machines. The only option that behaves the same everywhere |
This session only | Nothing | Every server start | A shared machine, or a one-off look |
Plain file | Unencrypted, readable by your account | Never | Automation, where nobody can type a password |
The interface probes the keyring at runtime rather than guessing from the operating system, because the same Linux with and without a graphical session is a different machine as far as a keyring is concerned. An option that cannot work is shown greyed out with the actual reason.
If you pick the encrypted file, every later start shows an unlock page asking for that master password alone. Forget it and nothing anywhere can recover the file; the page offers to throw it away so you can sign in afresh.
Without a browser
Environment variables still work and take precedence over anything stored:
BRIDGE_USER=your.address@example.com
BRIDGE_PASS=the-password-the-bridge-generatedPut them in ~/.config/proton-mcp/env (on Windows %USERPROFILE%\.config\proton-mcp\env), which the setup script creates with placeholders and which the server reads itself. Do not put them in your AI client's configuration, where they would sit in plain text and be read on every start. The file is created readable only by you, and the server warns on startup if that stops being true.
All variables are documented in .env.example.
Registering it with a client
Claude Code:
claude mcp add proton -- node /absolute/path/to/proton-mcp/dist/server.jsAny client that reads a JSON configuration (Claude Desktop, Cursor and others):
{
"mcpServers": {
"proton": {
"command": "node",
"args": ["/absolute/path/to/proton-mcp/dist/server.js"]
}
}
}Use an absolute path; ~ is not expanded by every client. The setup script prints the correct line for your machine.
Checking that it works
Without any client, by speaking the protocol directly:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"check","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_folders","arguments":{"kind":"system"}}}' \
| node dist/server.jsYou should see your system mailboxes. If instead you get a message saying the Bridge is probably not running, start and unlock the Bridge and try again. The server explains what to do in its error messages rather than printing error codes.
Security
The content of someone else's email is text written by strangers, and it is handed to a language model that can call tools. A crafted message may try to instruct the assistant. This server assumes that will happen:
Message content is marked as untrusted in every answer, enclosed in explicit markers and separated from anything the server itself says.
Nothing is filtered out of the content and no attempt is made to detect attacks. Such filters pretend to offer safety they cannot deliver.
Sending will always require your confirmation, enforced in the server rather than in a system prompt. There will be no switch to turn it off. Until sending exists at all, there is nothing to confirm.
There will be no permanent deletion, and no tools for filters, forwarding, account or key settings. A hijacked session must not be able to leave behind lasting access.
HTML is never passed through raw and external content is never fetched. Tracking pixels stay ineffective.
No message ever touches your disk. No cache, no index, no copies. Anything this server wrote down would be your mail in plain text, outside the encryption you pay Proton for.
Writing to your mailbox
Every tool that changes something is bounded on purpose:
PROTON_MCP_READ_ONLY=truerefuses every write, and the interface reports the mode it is in. Reading keeps working.Nothing is ever deleted for good.
trash_messagesmoves messages to the trash, where Proton keeps them and wheremove_messagescan take them back out. There is no tool that empties the trash.Removing a label is an expunge inside the label's own mailbox. The same command aimed at a folder would delete mail, so the code refuses any path outside
Labels/, checked again in the line immediately before the expunge. A label name containing a path separator is rejected rather than repaired.Labels are never created, only applied. Create them in Proton itself.
A label is not a destination for
move_messages. Moving a message into a label was measured to apply the label and leave the message where it is, so the tool refuses it instead of reporting a move that did not happen.A batch touches at most 50 messages, and every one of them is accounted for in the answer, including the ones that failed and why.
A draft is the safe default. The assistant prepares, you decide.
Messages can be plain text or formatted. A formatted one may use headings, paragraphs, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything outside that set is refused with a reason rather than quietly removed, because a message that was silently altered is no longer the message anyone agreed to.
Formatting changes what a confirmation has to show, and that is the interesting part. In plain text a link's visible text and its target are the same string, so showing you the body shows you everything. In markup they are two strings, which is the shape of every phishing mail ever written. So the confirmation for a formatted message adds two blocks that are never shortened:
Every address in the message, links and images alike, each with the text it is shown as.
Text a recipient can read that the body preview does not show. Measured: an image's alt text never appears in the converted body, and mail clients block remote images by default, so the alt text is frequently what the recipient actually reads. That was a way past the confirmation, and it is closed.
The set of permitted markup leaves out style, script, noscript, textarea and head by not listing them. Those are the elements whose content the preview and a mail client disagree about, and a list of what is allowed refuses them without anyone having had to think of them first. Attachments from files on this machine are deliberately not supported: a path named by a model, read by the server and carried out by the next confirmation is not a feature, it is a way out for anything on your disk. Forwarding loses nothing all the same, because the original message travels along whole.
One thing worth knowing about drafts: Proton rewrites the thread headers of anything it stores. A reply is built with In-Reply-To and References, and what comes back has neither, only Proton's own internal thread id. Threading a stored draft is therefore Proton's business rather than this server's.
Writes need a moment to settle. A move takes roughly fifteen seconds to reconcile with Proton, and until then the Bridge lists the message in both places, so the tools say so rather than reading back an intermediate state and calling it the result. Flags are the exception: those hold immediately.
Sending, and the question you cannot switch off
Mail is text written by strangers, handed to a model that can call tools. A message can politely ask to be forwarded somewhere, and other projects answer that with a line in a system prompt. A system prompt is not a security boundary. This one lives in the server:
Nothing is sent without a person saying yes. The first call never sends. It returns a question through your client showing the final recipients separated into To, Cc and Bcc, the subject, and the first lines. Only the second call, carrying your answer, sends.
There is no setting that turns the question off. Not an environment variable, not an argument, not a mode.
A client that cannot ask cannot send. If your client does not support form elicitation, sending is refused and you are pointed at drafts instead. That is deliberately different from the sign-in prompt, which falls back to text: the worst outcome there is an inconvenience, and here it is a message that cannot be recalled. What counts as "can ask" is the capability your client declares, read from the request when the protocol revision carries it there and from the handshake otherwise.
Your yes covers one specific message. A fingerprint of the sender, every recipient, the subject and the body is sealed with an HMAC whose key exists only in the running process, and it travels with the question. On the way back the fingerprint is recomputed from what is being asked for now. If a single address was added in between, nothing is sent. A confirmation is also bound to the tool that asked, so one cannot be reused for another.
Blind copies stay blind. They travel in the envelope and never appear in the headers the recipients see. Verified against a real delivery.
The Bridge password is protected on the way out too. SMTP is verified against the same pinned certificate as IMAP, and STARTTLS is required rather than merely attempted.
Replying and forwarding exist as sending tools of their own, not just as drafts. The reason is measured: Proton rewrites the thread headers of anything it stores, so a reply written to a draft and sent later loses the chain, while a reply handed straight to SMTP keeps its In-Reply-To and stays in the conversation.
After a send, nothing checks its own work. The Bridge took about six and a half seconds to accept a message and the message appeared over IMAP about three seconds after that, so an immediate look would describe a state that is not the outcome.
What lands on your disk
Only these, all under ~/.config/proton-mcp/ (on Windows %USERPROFILE%\.config\proton-mcp\) and all written readable by your account alone:
File | When | What is in it |
| On the first connection | The Bridge's certificate and its fingerprint |
| When you change the ports in the interface | Two port numbers |
| Only if you chose the encrypted file | Your Bridge password, encrypted with your master password |
| Only if you put it there yourself | Your Bridge password in plain text |
Choosing the system keyring puts the password where the operating system keeps your other passwords, and nothing of it in a file. Choosing "this session only" writes nothing at all.
No mail, no subjects, no sender addresses, no folder names, no search terms. The activity list in the interface records tool names and timings, in memory only, and is gone when the server stops.
The web interface
It binds to 127.0.0.1 and there is no option to change that. It is not remote access and cannot be made into it. Four defences, each against a specific attack:
A token in the URL, generated per process and never written down. Any other program on your machine can reach
127.0.0.1; without the token it can do nothing here.A Host header check. A page on the internet can point its own domain at
127.0.0.1and have your browser send requests there, which the browser then treats as same-origin with that domain. Only loopback names are accepted. This is DNS rebinding, and it is the attack local servers most often forget.An Origin check and a per-action CSRF token on everything that changes something, so a token for one action cannot be replayed against another.
A strict Content-Security-Policy. Nothing may be loaded from anywhere and nothing can reach the network. The interface also serves no files from disk at all, which removes path traversal as a category rather than guarding against it.
It shows no message content, by design. It is for configuration and state, not a mail client.
What you actually expose
In Proton, folders and labels belong to the account, not to an individual address. If you connect the server using a secondary address, the complete folder structure of your account is still visible, even though the messages inside belong to the other address and stay hidden.
The names alone say something. A folder Banking/Revolut reveals your bank, Tax your dealings with the tax office, a company name your employer. Separating both content and structure requires a separate Proton account, not just a second address.
The Bridge certificate
The Bridge generates its own certificate and keeps it inside its encrypted vault rather than on disk, so it cannot be trusted in advance. Switching verification off would mean anything that manages to listen on the port gets your Bridge password.
Instead the certificate is pinned on first use. The first connection records its fingerprint in ~/.config/proton-mcp/known-certificate.json and reports it on stderr. The web interface shows it too, along with the date it was recorded. Every connection after that is verified against that one certificate, which is stricter than the public trust store: a certificate signed by a recognised authority would not do.
If the certificate ever changes, the server refuses to connect and says so:
The certificate of the Bridge at 127.0.0.1:1143 has changed. Refusing to connect.
expected: 93:87:F5:3D:...
received: 11:22:33:44:...Reinstalled the Bridge or moved machines? Then delete the file and the next connection records the new certificate. Changed nothing? Then do not delete it, because something else may be listening on that port.
The one moment this cannot protect you is the very first connection, which is what trust on first use means. If that matters to you, compare the fingerprint the server prints against the certificate the Bridge shows.
Development
Area | Choice |
Language | TypeScript, Node.js 24 or newer |
MCP |
|
IMAP |
|
MIME |
|
SMTP |
|
Schemas |
|
Tests | Vitest, |
Every dependency is MIT or MIT-0, except deepmerge-ts which is BSD-3-Clause. All permissive.
npm run build # compile into dist/
npm test # run the tests once
npm run test:watch # run the tests in watch mode
npm run test:coverage # with coverage report
npm run typecheck # type check only, no outputThe tests run without a Bridge, so they work in CI. Real credentials and real mail content have no place in test files.
Layout
src/
├── bridge/ connection handling, certificate pinning, error translation
├── credentials/ the four places a password can be kept, and the runtime probe
├── mail/ listing, reading, searching, identifiers, attachments
├── mime/ parsing, HTML to text, budgeting
├── tools/ the MCP tools and their output formatting
├── web/ the local interface and its security layer
├── activity.ts what is running and what ran, in memory only
├── session.ts whether we are signed in, and where from
├── settings.ts the Bridge ports, when changed in the interface
└── server.ts entry point and transportThe web interface has one property worth knowing before touching it: Referrer-Policy must not be no-referrer. Under that policy browsers send Origin: null, which the CSRF defence rightly refuses, and the interface then refuses its own forms. The tests set the Origin header themselves and cannot catch it. There is a comment in src/web/security.ts and a test asserting the cause.
Checks before a pull request
The CI in .github/workflows/ci.yml runs the tests, the type check, the build, an audit of the dependencies and a scan for accidentally committed secrets. All of it runs locally too:
npm test
npx tsc --noEmit
npm run build
npm audit --omit=devMeasured behaviour of the Bridge
Some of what this server does looks odd until you know what the Bridge actually does, as opposed to what a standard IMAP server would do. The comments in src/bridge/, src/mail/ and src/mime/ name the measurement behind each decision. The three that surprise people most:
FETCH 1:*on an empty mailbox is answered withBAD no such messageinstead of an empty result, so a message count has to be checked first.UIDs are per mailbox and change when a message is moved. The Message-ID is the only stable handle.
A median message costs roughly 16000 tokens in its raw form and a fraction of that as text, which is why nothing raw is ever passed through.
Worth reading before changing anything in those directories.
License
MIT
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceAn MCP server that enables AI models to read, search, and send emails via IMAP and SMTP protocols. It supports various providers like Gmail and Outlook, allowing for tasks such as retrieving unread messages, searching by sender, and managing mailbox folders.Last updated
- AlicenseAqualityDmaintenanceA read-only MCP server that connects to Proton Mail via Proton Bridge, enabling AI assistants to search, list, and read emails securely without leaving your machine.Last updated4121MIT
- AlicenseAqualityBmaintenanceEnables AI agents to send, read, search, and organize emails via ProtonMail using Proton Bridge. Supports MCP-compatible clients like Claude and Cursor.Last updated17273MIT
- AlicenseAqualityCmaintenanceMCP server for reading, searching, and sending ProtonMail emails via the ProtonMail Bridge.Last updated162GPL 3.0
Related MCP Connectors
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
Hosted email MCP for AI agents with inboxes, send/receive, memory, recovery, and credits.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/RndmJoker/proton-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server