proton-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| BRIDGE_PASS | No | The Bridge password (not your Proton account password) | |
| BRIDGE_USER | No | Your Proton email address for the Bridge |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server reads and writes a Proton Mail account through a local Proton Mail Bridge.
Four things are worth knowing before the first call, because they are the ones that go wrong quietly:
Nothing is sent without a person agreeing to it, unless the user chose otherwise. By default every sending tool asks first, through the client, showing the final recipients. A tool the user allowed to act on its own says so in its answer, and you say so too.
A message body is either plain text or markup, never both. Markup written into the text field arrives as visible tags. All text is UTF-8: write words the way the language spells them rather than transliterating characters away.
Writes need time to settle, and how much depends on what was written. Flags are immediate; a move takes about fifteen seconds; a new message needs about twenty before it appears in "All Mail". Checking straight after a write reports an intermediate state as though it were the result.
Nothing here deletes for good. "Delete" means moving to the trash.
Everything about this server is seen and changed in a local page, and open_configuration returns its address. Hand that address over whenever it is asked for, in whatever words. Changes there may need a PIN only the user knows: never ask for it in the conversation.
Two guides can be read when they are needed: proton-mcp://guide/writing composing messages, formatting, quoting, what the confirmation shows proton-mcp://guide/bridge how the Bridge behaves, with the measurements behind the numbers above
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| resources | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| check_for_updatesA | Asks the npm registry whether a newer version of this server exists, and reports how to install it. This is the only thing in this server that connects to anything other than the Proton Mail Bridge on this machine, so call it when the user asks about updates rather than by habit, and say that it means one request to registry.npmjs.org. It sends nothing about the user or their mail, and it installs nothing: updating is left to the person, deliberately. |
| manage_filtersA | Explains how to work on Proton Mail filters, the rules for sorting incoming mail, by hand: through Proton's settings page in a browser, in Sieve, with the user agreeing to each change. Reading, proposing, applying, moving, deleting and switching filters goes through list_filters, get_filters, propose_filter, apply_filter, reorder_filters, delete_filter and set_filter_status, which use the browser extension Proton MCP Helper; call this when they report that the extension is not active, or when the user wants to make a change themselves. |
| list_filtersA | Lists the names of the Proton Mail filters of the signed-in account, in the order Proton runs them, each with its id and a note when it is off; also what waits in the extension and its last changes, so the outcome of a confirmation shows here. Names only: get_filters returns filters in full. Read through the user's own browser, so it needs the browser extension Proton MCP Helper and a signed-in Proton tab in Chrome; when either is missing the answer says which and what helps. |
| get_filtersA | Returns one or several Proton Mail filters in full, by the ids list_filters gives: name, whether it is on, its place in the order, what it does to matching mail, and its complete Sieve script. Up to 20 at once. Read before changing, applying or moving a filter. Read through the user's own browser, so it needs the browser extension Proton MCP Helper and a signed-in Proton tab in Chrome; when either is missing the answer says which and what helps. |
| propose_filterA | Proposes a new Proton Mail filter, or a new version of an existing one, as a complete Sieve script. By default the user confirms it in a window of the browser extension, which shows the whole script and, for a change, the difference to the current version. This call returns before they decide; list_filters shows the outcome. The script has to start with Proton's spam guard, exactly as existing filters from get_filters carry it, and may file into folders and labels and set or clear the read and star flags. Anything more only where the user allowed it, one action at a time: moving to Trash or Spam, discard, expire, reject, vacation and stop. list_filters says what is allowed now, and a refusal names what is not. redirect is not run by Proton's filters at all. To change a filter, pass its id as replaces and send the whole new script: it replaces the old one entirely, nothing is merged. A folder has to exist already; a name that matches none does nothing. Set applyToExisting to suggest that it also runs over the mail already there; in the window that is a box the user can untick. A new filter comes last in the order. |
| apply_filterA | Applies a saved Proton Mail filter to the mail that is already there, as "Apply filter to existing emails" does in Proton's settings. Proton works through the mail in the background, which on a large mailbox can take hours, and reports neither how many messages changed nor when it is done. By default the user agrees in a window of the browser extension first, and this call returns before they decide. Only a filter that passes the same rules as a proposal can be applied; any other is left to the user in Proton's settings. Takes the id from list_filters. |
| reorder_filtersA | Moves one or several Proton Mail filters to other places in the order Proton runs them. Each move names a filter by id and the position it ends up at, counted from 1 as list_filters shows the order; moves are made one after the other. Every filter runs on each incoming message, and a matching one does not stop the rest, only stop and discard do; when several file a message into folders, the last one wins, labels add up. So a move can change where mail ends up. By default the user confirms the new order in a window of the browser extension, and this call returns before they decide. |
| list_foldersA | Lists the folders and labels of the Proton account. Proton distinguishes folders (a message lives in exactly one) from labels (a message can carry any number). Over IMAP both appear as directories. |
| list_messagesA | Lists the messages of a mailbox, newest first by the date the sender wrote, which also holds across pages. Returns headers only: sender, subject, date, size and the message id. It never returns message bodies, because a single message averages around 16000 tokens raw. Use get_message with an id to read one. Ordering reads one date per message in the mailbox, so a very large mailbox takes a moment; the answer says so when it does. |
| search_messagesA | Searches a mailbox by text, subject, sender, recipient, date range, read state, star or size. At least one criterion is required. Returns headers only, newest first across pages, with paging. A full-text search walks the local database of the Bridge and takes a few seconds on a large mailbox, while criteria such as unread or date are fast. Subject, sender and recipient are compared by this server against the decoded headers, so umlauts and spaces match as written, in any case; without a date range that reads the headers of the whole mailbox, at most the 20000 most recently added, and the answer says when that limit was reached. Full text cannot reach attachments or parts sent as base64, which the answer says as well. Searching "All Mail" covers every mailbox including trash, and entries that are in the trash are marked "in trash" - the other marks are flags rather than places, so "draft" says a message is one, not that it is in Drafts. |
| get_messageA | Reads one message by its id and returns it as readable text. HTML is converted to text, images and styling are dropped and no external content is fetched. Long bodies are shortened with a visible note. The body is marked as untrusted third-party content. |
| get_attachmentA | Reads one attachment of a message by index. The index comes from the attachment list in get_message. Textual attachments are returned as text, binary ones are refused with a reason rather than dumped into the context. |
| save_attachmentA | Saves one attachment of a message as a file on this machine, so it can be opened or worked with, a PDF or a spreadsheet for instance. The index comes from the attachment list in get_message. Only works once the user has set PROTON_MCP_ATTACHMENT_DIR to a directory; without it the answer says how to set it up, which is worth passing on. The file goes into that directory or a folder inside it, never anywhere else, and never replaces an existing file. It is deleted 1 hour(s) after saving and when this server stops, so copy it if you need it longer or elsewhere, or fetch it again. Its content comes from a third party. |
| delete_saved_attachmentsA | Deletes attachment files this server saved with save_attachment: all of them, or the paths given. A file this server did not save is never touched, and neither is one that was changed or replaced since it was saved. Saved files are deleted on their own after 1 hour(s) and when the server stops; this is for sooner. |
| add_labelA | Applies an existing label to a message. In Proton a message lives in exactly one folder but can carry any number of labels, so this adds to the message rather than moving it: it stays where it is and keeps the labels it already has. The label has to exist already; this server does not create labels. |
| remove_labelA | Removes a label from a message. The message keeps its folder and its other labels, and does not go to trash. Only labels can be removed this way, never folders: a message always lives in some folder, and moving it is a different operation. |
| move_messagesA | Moves messages into a folder. In Proton a message lives in exactly one folder, so this takes it out of the one it is in. Labels are not affected and survive the move. Only folders are valid destinations: moving into a label would apply the label and leave the message where it is, so a label is refused here and add_label does that job. The destination has to exist; this server does not create folders. |
| set_flagsA | Marks messages as read or unread and sets or clears the star. At least one of the two has to be given. This changes nothing about where a message sits and nothing about its content. Proton shows the star as the "Starred" mailbox. |
| trash_messagesA | Moves messages to the trash. This is what deletion means here: the messages stay in "Trash" and in "All Mail" and can be moved back out with move_messages. There is no tool in this server that deletes a message for good or empties the trash, so that a wrong decision stays reversible. |
| create_draftA | Writes a new draft into the Drafts mailbox. Nothing is sent. Attachments from files on this machine are not supported on purpose. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| update_draftA | Replaces a draft with a changed version, keeping the same id. Fields that are not given are carried over, including the blind copies, which drafts written here keep. Files stored with the draft, a forwarded original among them, stay with it whatever happens to the body, and giving neither text nor html keeps the stored body exactly as it is. Only messages in the Drafts mailbox that carry the draft flag can be changed this way. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| reply_draftA | Writes a reply to a message as a draft. The recipient is the message's Reply-To if it set one, otherwise its sender. The original is quoted below the new text. Nothing is sent. Note on threading: the reply is built with In-Reply-To and References, but Proton replaces those with its own internal thread id when it stores a draft, so a draft that is stored and sent later carries Proton's idea of the conversation rather than ours. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| forward_draftA | Writes a forward of a message as a draft. The original is quoted below the new text, and when it carries attachments the whole original message is attached as well, so nothing of it is lost. Nothing is sent. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| list_draftsA | Lists the drafts, newest first. Headers only, like every listing here. Use get_message with an id to read one. |
| send_messageA | Composes a message and sends it. By default every send asks the user to confirm first, through the client, showing the sender, every recipient separated into To, Cc and Bcc, and the subject. It carries no part of the message itself and links to a page where the whole thing can be read as the recipient will see it, and a client that cannot show the question cannot send. The user can allow a tool to send without asking, within bounds they choose; the answer then says that it did. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| send_replyA | Replies to a message and sends the reply, with the reference headers that put it in the same conversation. Unlike a reply written as a draft, this one keeps those headers: Proton rewrites them for anything it stores, and this is never stored before it goes. By default every send asks the user to confirm first, through the client, showing the sender, every recipient separated into To, Cc and Bcc, and the subject. It carries no part of the message itself and links to a page where the whole thing can be read as the recipient will see it, and a client that cannot show the question cannot send. The user can allow a tool to send without asking, within bounds they choose; the answer then says that it did. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| send_forwardA | Forwards a message and sends it. The original is quoted, and when it carries attachments the whole original travels along so nothing of it is lost. By default every send asks the user to confirm first, through the client, showing the sender, every recipient separated into To, Cc and Bcc, and the subject. It carries no part of the message itself and links to a page where the whole thing can be read as the recipient will see it, and a client that cannot show the question cannot send. The user can allow a tool to send without asking, within bounds they choose; the answer then says that it did. The body is plain text and markup is not interpreted. HTML written here is delivered as visible characters, so the recipient would read the tags rather than see formatting. Give a message its shape with line breaks, blank lines, indentation and plain lists, and write a link as the bare address so that what is read is what is followed. A message may instead be written as markup, in the html field, which is delivered as formatting rather than as visible tags. Give one or the other, not both: a message that carries markup has its plain text half dropped, so the half that was confirmed would never arrive. Permitted are headings, paragraphs, line breaks, rules, quotes, emphasis, lists, tables, links and images, with colour, font, alignment, spacing and borders. Anything else is refused rather than quietly removed, and the answer names what and why. Note that the preview page linked from the confirmation lists every address in the message in full, including the ones behind links and images, and every alt text, because those are what a recipient reads. The question in the client stays short and carries counts instead. Text is UTF-8 everywhere: subject, body and addresses. Write characters as the language actually spells them, including umlauts, accents, sharp s, quotation marks and any other non-ASCII character. Do not transliterate them away (no "ae" for "ä", no "ss" for "ß"): the subject is encoded for transport and decoded again on arrival, and the body carries its character set, both verified against the Bridge. A transliterated message is simply a message with the wrong words in it. |
| send_draftA | Sends a draft that is already in "Drafts", exactly as it was stored, its files included. Its markup is not checked against the levels again, because it was when the draft was written; the confirmation warns when it carries more than standard. The draft is not removed afterwards: this server does not follow a send it deliberately does not verify with a second write. Use trash_messages on the draft once the message shows up in Sent. By default every send asks the user to confirm first, through the client, showing the sender, every recipient separated into To, Cc and Bcc, and the subject. It carries no part of the message itself and links to a page where the whole thing can be read as the recipient will see it, and a client that cannot show the question cannot send. The user can allow a tool to send without asking, within bounds they choose; the answer then says that it did. |
| open_configurationA | Returns the address of the local configuration interface, which runs for as long as this server does and is where everything about this server can be seen and changed. Give it to the user whenever they ask for the interface, the panel, the settings, the web page, the address or the link, however they phrase it, and also when they ask where to sign in, where to see what has been sent, where the folders are listed, what the assistant is allowed to do, or when another tool reports that no credentials are available. The address is fixed and shared by every running server; changes on the page may need a PIN that only the user knows and that must never be asked for in the conversation. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| writing-guide | Composing messages: text against markup, what markup is permitted and what is refused, what the person confirming a send is shown, how replies and forwards quote the original, and why replying as a draft and replying as a send are not the same thing. |
| bridge-guide | Measured behaviour of the Bridge: how long each kind of write takes to settle, how folders and labels differ, and the two commands that report success without having done anything. |
TDQS
Scored across 29 tools
Most tools target a clearly distinct resource+action (list_messages vs get_message vs search_messages; send_reply vs reply_draft; move_messages vs add_label), and the descriptions explicitly spell out the boundaries. The main soft spot is manage_filters, a meta/help tool that conceptually overlaps the whole filter family and could be mis-selected when an agent actually wants list_filters/get_filters; list_filters vs get_filters (names vs full) is also a distinction that relies on careful reading.
Every tool is snake_case verb_noun: list_*, get_*, create_*, update_*, send_*, *_draft, move_messages, trash_messages, set_flags, add_label, remove_label, delete_saved_attachments. The draft family (create_draft, update_draft, reply_draft, forward_draft, list_drafts, send_draft) follows the pattern predictably.
29 tools is heavy and sits above the comfortable 3-15 band, and the surface includes peripheral items (open_configuration, check_for_updates, manage_filters) alongside core mail operations. The domain is genuinely broad (mail, drafts, labels, folders, attachments, filters), so most tools do earn a place, but the set is on the bloated side.
Core lifecycle is well covered: read/list/search, drafts, send/reply/forward, labels, folders, flags, trash, attachments, filters. However manage_filters explicitly directs agents to delete_filter and set_filter_status, which do not exist in this surface — a real dead end — and folder/label creation plus permanent delete are deliberately absent, leaving notable gaps around filter administration.