protonmail-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- protonmail_mcp-0.1.0/.github/workflows/ci.yml +26 -0
- protonmail_mcp-0.1.0/.github/workflows/publish.yml +20 -0
- protonmail_mcp-0.1.0/.gitignore +24 -0
- protonmail_mcp-0.1.0/LICENSE +21 -0
- protonmail_mcp-0.1.0/PKG-INFO +195 -0
- protonmail_mcp-0.1.0/README.md +171 -0
- protonmail_mcp-0.1.0/env.example +10 -0
- protonmail_mcp-0.1.0/pyproject.toml +56 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/__init__.py +3 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/__main__.py +54 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/bridge.py +226 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/config.py +77 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/models.py +38 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/parsing.py +250 -0
- protonmail_mcp-0.1.0/src/protonmail_mcp/server.py +125 -0
- protonmail_mcp-0.1.0/tests/test_bridge.py +181 -0
- protonmail_mcp-0.1.0/tests/test_config.py +55 -0
- protonmail_mcp-0.1.0/tests/test_parsing.py +149 -0
- protonmail_mcp-0.1.0/tests/test_server.py +107 -0
- protonmail_mcp-0.1.0/uv.lock +719 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.13", "3.14"]
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7.0.1
|
|
20
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
- run: uv sync --locked
|
|
24
|
+
- run: uv run ruff check .
|
|
25
|
+
- run: uv run pytest
|
|
26
|
+
- run: uv build
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
pypi:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
environment: pypi
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7.0.1
|
|
18
|
+
- uses: astral-sh/setup-uv@v10.2.0
|
|
19
|
+
- run: uv build
|
|
20
|
+
- uses: pypa/gh-action-pypi-publish@v1.14.2
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
wheels/
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
venv/
|
|
13
|
+
|
|
14
|
+
# Tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
|
|
21
|
+
# Secrets and local config
|
|
22
|
+
.env
|
|
23
|
+
.env.local
|
|
24
|
+
*.pem
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Manoah BERNIER
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: protonmail-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for Proton Mail via Proton Bridge (IMAP/SMTP)
|
|
5
|
+
Project-URL: Homepage, https://github.com/mhbxyz/protonmail-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/mhbxyz/protonmail-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/mhbxyz/protonmail-mcp/issues
|
|
8
|
+
Author-email: Manoah BERNIER <manoah.bernier@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: email,imap,llm,mcp,proton-bridge,protonmail
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Communications :: Email :: Post-Office :: IMAP
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: imapclient>=3
|
|
22
|
+
Requires-Dist: mcp>=2.3
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# protonmail-mcp
|
|
26
|
+
|
|
27
|
+
[](https://github.com/mhbxyz/protonmail-mcp/actions/workflows/ci.yml)
|
|
28
|
+
|
|
29
|
+
A lightweight [MCP](https://modelcontextprotocol.io) server that gives AI agents
|
|
30
|
+
read access to a Proton Mail mailbox through a local
|
|
31
|
+
[Proton Bridge](https://proton.me/mail/bridge) instance.
|
|
32
|
+
|
|
33
|
+
> **Unofficial.** This project is not affiliated with, endorsed by, or supported by
|
|
34
|
+
> Proton AG. "Proton Mail" and "Proton Bridge" are trademarks of Proton AG.
|
|
35
|
+
|
|
36
|
+
Proton does not provide a public API for reading your mailbox. Bridge is the supported
|
|
37
|
+
way in: it runs locally and exposes your account over IMAP and SMTP on `127.0.0.1`.
|
|
38
|
+
This server wraps that local IMAP endpoint in a small, auditable set of MCP tools.
|
|
39
|
+
|
|
40
|
+
## Scope
|
|
41
|
+
|
|
42
|
+
The current release is **read-only**: it can list folders, list messages, search, and
|
|
43
|
+
read a message. Mailboxes are opened with IMAP `SELECT ... READONLY`, so nothing is
|
|
44
|
+
ever modified — not even the `\Seen` flag. Write tools are on the roadmap, behind
|
|
45
|
+
mandatory confirmation (see [Security](#security)).
|
|
46
|
+
|
|
47
|
+
## Tools
|
|
48
|
+
|
|
49
|
+
| Tool | Description |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `list_folders` | List every folder and label, with IMAP flags and whether it is selectable |
|
|
52
|
+
| `list_emails` | Most recent messages in a folder, newest first: `limit`, `unread_only`, `since_days`, `sender`, `subject` |
|
|
53
|
+
| `search_emails` | Full-text search across headers and body in a folder |
|
|
54
|
+
| `read_email` | Read one message by `Message-ID`: decoded text body, attachments, flags, truncation via `max_chars` |
|
|
55
|
+
|
|
56
|
+
Results are structured (Pydantic models). Every message carries its `Message-ID`; use
|
|
57
|
+
that for follow-up reads — IMAP UIDs are not stable across Bridge resynchronisations.
|
|
58
|
+
|
|
59
|
+
## Requirements
|
|
60
|
+
|
|
61
|
+
- A paid Proton Mail plan (required by Bridge)
|
|
62
|
+
- Proton Bridge installed, running, and signed in
|
|
63
|
+
- Your Bridge credentials: Proton address + the mailbox password shown in the Bridge UI
|
|
64
|
+
- Python 3.13+ (only if you do not use `uv`)
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Run without installing (recommended)
|
|
70
|
+
uvx protonmail-mcp
|
|
71
|
+
|
|
72
|
+
# Or install it
|
|
73
|
+
pipx install protonmail-mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configure
|
|
77
|
+
|
|
78
|
+
| Variable | Default | Purpose |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `PROTONMAIL_BRIDGE_USERNAME` | — | Your Proton address (required) |
|
|
81
|
+
| `PROTONMAIL_BRIDGE_PASSWORD` | — | Bridge mailbox password (required) |
|
|
82
|
+
| `PROTONMAIL_BRIDGE_HOST` | `127.0.0.1` | Bridge host |
|
|
83
|
+
| `PROTONMAIL_BRIDGE_IMAP_PORT` | `1143` | Bridge IMAP port |
|
|
84
|
+
| `PROTONMAIL_BRIDGE_IMAP_SECURITY` | `starttls` | `starttls` (Bridge 3.x on 1143) or `ssl` (direct TLS) |
|
|
85
|
+
| `PROTONMAIL_BRIDGE_TIMEOUT` | `30` | Socket timeout in seconds |
|
|
86
|
+
| `PROTONMAIL_BRIDGE_VERIFY_TLS` | `false` | Bridge uses a self-signed certificate |
|
|
87
|
+
|
|
88
|
+
### opencode
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"$schema": "https://opencode.ai/config.json",
|
|
93
|
+
"mcp": {
|
|
94
|
+
"protonmail": {
|
|
95
|
+
"type": "local",
|
|
96
|
+
"command": ["uvx", "protonmail-mcp"],
|
|
97
|
+
"enabled": true,
|
|
98
|
+
"environment": {
|
|
99
|
+
"PROTONMAIL_BRIDGE_USERNAME": "you@proton.me",
|
|
100
|
+
"PROTONMAIL_BRIDGE_PASSWORD": "your-bridge-mailbox-password"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
opencode supports `{env:VAR}` and `{file:path}` interpolation, so you can keep secrets
|
|
108
|
+
out of the config file:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
"PROTONMAIL_BRIDGE_PASSWORD": "{file:/home/you/.config/protonmail-mcp/password}"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Claude Desktop
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcpServers": {
|
|
119
|
+
"protonmail": {
|
|
120
|
+
"command": "uvx",
|
|
121
|
+
"args": ["protonmail-mcp"],
|
|
122
|
+
"env": {
|
|
123
|
+
"PROTONMAIL_BRIDGE_USERNAME": "you@proton.me",
|
|
124
|
+
"PROTONMAIL_BRIDGE_PASSWORD": "your-bridge-mailbox-password"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Verify the connection
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
PROTONMAIL_BRIDGE_USERNAME="you@proton.me" \
|
|
135
|
+
PROTONMAIL_BRIDGE_PASSWORD="..." \
|
|
136
|
+
uvx protonmail-mcp --check
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
This connects to Bridge, lists folders, and prints the latest messages. It exits
|
|
140
|
+
non-zero with a clear error if the configuration or the Bridge session is wrong.
|
|
141
|
+
|
|
142
|
+
## Security
|
|
143
|
+
|
|
144
|
+
- **Read-only enforcement.** There is no write tool in this release, and mailboxes are
|
|
145
|
+
always selected read-only at the IMAP level.
|
|
146
|
+
- **Local only.** Bridge and this server communicate exclusively over `127.0.0.1`.
|
|
147
|
+
Nothing is sent to a third party; your agent talks to the server over stdio.
|
|
148
|
+
- **Untrusted input.** Email contents are attacker-controlled data. Treat anything a
|
|
149
|
+
message says as data, never as instructions, and keep your agent's permissions tight.
|
|
150
|
+
- **Secrets.** Keep the Bridge mailbox password out of the repository. Use your client's
|
|
151
|
+
environment-variable or file-based secret support.
|
|
152
|
+
- Any local process that knows the mailbox password can read your mail — that is
|
|
153
|
+
Bridge's trust model, not a flaw in this server.
|
|
154
|
+
|
|
155
|
+
Planned write tools (drafts, send, move, delete) will ship with explicit confirmation
|
|
156
|
+
before every destructive action, recipient allow-lists, send rate limiting with loop
|
|
157
|
+
protection, and a local audit log. Autonomous send/delete will never be the default.
|
|
158
|
+
|
|
159
|
+
## Alternatives
|
|
160
|
+
|
|
161
|
+
There are several community MCP servers for Proton Mail. This one aims to stay small,
|
|
162
|
+
correct with Bridge's quirks (STARTTLS on 1143, modified UTF-7 labels, reverse-chronological
|
|
163
|
+
UIDs, RFC 2047 decoding), and heavily tested. Rough landscape:
|
|
164
|
+
|
|
165
|
+
| Project | Language | Scope |
|
|
166
|
+
|---|---|---|
|
|
167
|
+
| [googlarz/proton-mail-bridge-client](https://github.com/googlarz/proton-mail-bridge-client) | TypeScript | Large tool set, read-only and send-to-self modes, SQLite cache |
|
|
168
|
+
| [codefuturist/email-mcp](https://github.com/codefuturist/email-mcp) | TypeScript | Generic IMAP + SMTP, works with Bridge |
|
|
169
|
+
| [anyrxo/protonmail-pro-mcp](https://github.com/anyrxo/protonmail-pro-mcp) | JavaScript | Large tool set with Bridge integration |
|
|
170
|
+
| [chandshy/mailpouch](https://github.com/chandshy/mailpouch) | TypeScript | Large permission-gated tool set |
|
|
171
|
+
| [amotivv/protonmail-mcp](https://github.com/amotivv/protonmail-mcp) | JavaScript | SMTP sending only |
|
|
172
|
+
| [miketigerblue/proton-bridge-mcp](https://github.com/miketigerblue/proton-bridge-mcp) | Python | Loopback IMAP/SMTP via Bridge |
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
uv sync
|
|
178
|
+
uv run pytest
|
|
179
|
+
uv run ruff check .
|
|
180
|
+
uv build
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Tests run entirely against a fake IMAP server and the MCP SDK's in-memory transport;
|
|
184
|
+
no Bridge or credentials are needed.
|
|
185
|
+
|
|
186
|
+
## Releasing
|
|
187
|
+
|
|
188
|
+
Publishing is automated with GitHub Actions and PyPI Trusted Publishing. Create a
|
|
189
|
+
GitHub release tagged `vX.Y.Z`; the `publish` workflow builds the sdist/wheel and
|
|
190
|
+
uploads them to PyPI using the `pypi` environment (configure the trusted publisher on
|
|
191
|
+
PyPI for owner `mhbxyz`, repository `protonmail-mcp`, workflow `publish.yml`).
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# protonmail-mcp
|
|
2
|
+
|
|
3
|
+
[](https://github.com/mhbxyz/protonmail-mcp/actions/workflows/ci.yml)
|
|
4
|
+
|
|
5
|
+
A lightweight [MCP](https://modelcontextprotocol.io) server that gives AI agents
|
|
6
|
+
read access to a Proton Mail mailbox through a local
|
|
7
|
+
[Proton Bridge](https://proton.me/mail/bridge) instance.
|
|
8
|
+
|
|
9
|
+
> **Unofficial.** This project is not affiliated with, endorsed by, or supported by
|
|
10
|
+
> Proton AG. "Proton Mail" and "Proton Bridge" are trademarks of Proton AG.
|
|
11
|
+
|
|
12
|
+
Proton does not provide a public API for reading your mailbox. Bridge is the supported
|
|
13
|
+
way in: it runs locally and exposes your account over IMAP and SMTP on `127.0.0.1`.
|
|
14
|
+
This server wraps that local IMAP endpoint in a small, auditable set of MCP tools.
|
|
15
|
+
|
|
16
|
+
## Scope
|
|
17
|
+
|
|
18
|
+
The current release is **read-only**: it can list folders, list messages, search, and
|
|
19
|
+
read a message. Mailboxes are opened with IMAP `SELECT ... READONLY`, so nothing is
|
|
20
|
+
ever modified — not even the `\Seen` flag. Write tools are on the roadmap, behind
|
|
21
|
+
mandatory confirmation (see [Security](#security)).
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | Description |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `list_folders` | List every folder and label, with IMAP flags and whether it is selectable |
|
|
28
|
+
| `list_emails` | Most recent messages in a folder, newest first: `limit`, `unread_only`, `since_days`, `sender`, `subject` |
|
|
29
|
+
| `search_emails` | Full-text search across headers and body in a folder |
|
|
30
|
+
| `read_email` | Read one message by `Message-ID`: decoded text body, attachments, flags, truncation via `max_chars` |
|
|
31
|
+
|
|
32
|
+
Results are structured (Pydantic models). Every message carries its `Message-ID`; use
|
|
33
|
+
that for follow-up reads — IMAP UIDs are not stable across Bridge resynchronisations.
|
|
34
|
+
|
|
35
|
+
## Requirements
|
|
36
|
+
|
|
37
|
+
- A paid Proton Mail plan (required by Bridge)
|
|
38
|
+
- Proton Bridge installed, running, and signed in
|
|
39
|
+
- Your Bridge credentials: Proton address + the mailbox password shown in the Bridge UI
|
|
40
|
+
- Python 3.13+ (only if you do not use `uv`)
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Run without installing (recommended)
|
|
46
|
+
uvx protonmail-mcp
|
|
47
|
+
|
|
48
|
+
# Or install it
|
|
49
|
+
pipx install protonmail-mcp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Configure
|
|
53
|
+
|
|
54
|
+
| Variable | Default | Purpose |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `PROTONMAIL_BRIDGE_USERNAME` | — | Your Proton address (required) |
|
|
57
|
+
| `PROTONMAIL_BRIDGE_PASSWORD` | — | Bridge mailbox password (required) |
|
|
58
|
+
| `PROTONMAIL_BRIDGE_HOST` | `127.0.0.1` | Bridge host |
|
|
59
|
+
| `PROTONMAIL_BRIDGE_IMAP_PORT` | `1143` | Bridge IMAP port |
|
|
60
|
+
| `PROTONMAIL_BRIDGE_IMAP_SECURITY` | `starttls` | `starttls` (Bridge 3.x on 1143) or `ssl` (direct TLS) |
|
|
61
|
+
| `PROTONMAIL_BRIDGE_TIMEOUT` | `30` | Socket timeout in seconds |
|
|
62
|
+
| `PROTONMAIL_BRIDGE_VERIFY_TLS` | `false` | Bridge uses a self-signed certificate |
|
|
63
|
+
|
|
64
|
+
### opencode
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"$schema": "https://opencode.ai/config.json",
|
|
69
|
+
"mcp": {
|
|
70
|
+
"protonmail": {
|
|
71
|
+
"type": "local",
|
|
72
|
+
"command": ["uvx", "protonmail-mcp"],
|
|
73
|
+
"enabled": true,
|
|
74
|
+
"environment": {
|
|
75
|
+
"PROTONMAIL_BRIDGE_USERNAME": "you@proton.me",
|
|
76
|
+
"PROTONMAIL_BRIDGE_PASSWORD": "your-bridge-mailbox-password"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
opencode supports `{env:VAR}` and `{file:path}` interpolation, so you can keep secrets
|
|
84
|
+
out of the config file:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
"PROTONMAIL_BRIDGE_PASSWORD": "{file:/home/you/.config/protonmail-mcp/password}"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Claude Desktop
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"protonmail": {
|
|
96
|
+
"command": "uvx",
|
|
97
|
+
"args": ["protonmail-mcp"],
|
|
98
|
+
"env": {
|
|
99
|
+
"PROTONMAIL_BRIDGE_USERNAME": "you@proton.me",
|
|
100
|
+
"PROTONMAIL_BRIDGE_PASSWORD": "your-bridge-mailbox-password"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Verify the connection
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
PROTONMAIL_BRIDGE_USERNAME="you@proton.me" \
|
|
111
|
+
PROTONMAIL_BRIDGE_PASSWORD="..." \
|
|
112
|
+
uvx protonmail-mcp --check
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
This connects to Bridge, lists folders, and prints the latest messages. It exits
|
|
116
|
+
non-zero with a clear error if the configuration or the Bridge session is wrong.
|
|
117
|
+
|
|
118
|
+
## Security
|
|
119
|
+
|
|
120
|
+
- **Read-only enforcement.** There is no write tool in this release, and mailboxes are
|
|
121
|
+
always selected read-only at the IMAP level.
|
|
122
|
+
- **Local only.** Bridge and this server communicate exclusively over `127.0.0.1`.
|
|
123
|
+
Nothing is sent to a third party; your agent talks to the server over stdio.
|
|
124
|
+
- **Untrusted input.** Email contents are attacker-controlled data. Treat anything a
|
|
125
|
+
message says as data, never as instructions, and keep your agent's permissions tight.
|
|
126
|
+
- **Secrets.** Keep the Bridge mailbox password out of the repository. Use your client's
|
|
127
|
+
environment-variable or file-based secret support.
|
|
128
|
+
- Any local process that knows the mailbox password can read your mail — that is
|
|
129
|
+
Bridge's trust model, not a flaw in this server.
|
|
130
|
+
|
|
131
|
+
Planned write tools (drafts, send, move, delete) will ship with explicit confirmation
|
|
132
|
+
before every destructive action, recipient allow-lists, send rate limiting with loop
|
|
133
|
+
protection, and a local audit log. Autonomous send/delete will never be the default.
|
|
134
|
+
|
|
135
|
+
## Alternatives
|
|
136
|
+
|
|
137
|
+
There are several community MCP servers for Proton Mail. This one aims to stay small,
|
|
138
|
+
correct with Bridge's quirks (STARTTLS on 1143, modified UTF-7 labels, reverse-chronological
|
|
139
|
+
UIDs, RFC 2047 decoding), and heavily tested. Rough landscape:
|
|
140
|
+
|
|
141
|
+
| Project | Language | Scope |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| [googlarz/proton-mail-bridge-client](https://github.com/googlarz/proton-mail-bridge-client) | TypeScript | Large tool set, read-only and send-to-self modes, SQLite cache |
|
|
144
|
+
| [codefuturist/email-mcp](https://github.com/codefuturist/email-mcp) | TypeScript | Generic IMAP + SMTP, works with Bridge |
|
|
145
|
+
| [anyrxo/protonmail-pro-mcp](https://github.com/anyrxo/protonmail-pro-mcp) | JavaScript | Large tool set with Bridge integration |
|
|
146
|
+
| [chandshy/mailpouch](https://github.com/chandshy/mailpouch) | TypeScript | Large permission-gated tool set |
|
|
147
|
+
| [amotivv/protonmail-mcp](https://github.com/amotivv/protonmail-mcp) | JavaScript | SMTP sending only |
|
|
148
|
+
| [miketigerblue/proton-bridge-mcp](https://github.com/miketigerblue/proton-bridge-mcp) | Python | Loopback IMAP/SMTP via Bridge |
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
uv sync
|
|
154
|
+
uv run pytest
|
|
155
|
+
uv run ruff check .
|
|
156
|
+
uv build
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Tests run entirely against a fake IMAP server and the MCP SDK's in-memory transport;
|
|
160
|
+
no Bridge or credentials are needed.
|
|
161
|
+
|
|
162
|
+
## Releasing
|
|
163
|
+
|
|
164
|
+
Publishing is automated with GitHub Actions and PyPI Trusted Publishing. Create a
|
|
165
|
+
GitHub release tagged `vX.Y.Z`; the `publish` workflow builds the sdist/wheel and
|
|
166
|
+
uploads them to PyPI using the `pypi` environment (configure the trusted publisher on
|
|
167
|
+
PyPI for owner `mhbxyz`, repository `protonmail-mcp`, workflow `publish.yml`).
|
|
168
|
+
|
|
169
|
+
## License
|
|
170
|
+
|
|
171
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copy to .env for local use; never commit it.
|
|
2
|
+
PROTONMAIL_BRIDGE_USERNAME=you@proton.me
|
|
3
|
+
PROTONMAIL_BRIDGE_PASSWORD=the-password-shown-in-proton-bridge
|
|
4
|
+
|
|
5
|
+
# Optional
|
|
6
|
+
# PROTONMAIL_BRIDGE_HOST=127.0.0.1
|
|
7
|
+
# PROTONMAIL_BRIDGE_IMAP_PORT=1143
|
|
8
|
+
# PROTONMAIL_BRIDGE_IMAP_SECURITY=starttls
|
|
9
|
+
# PROTONMAIL_BRIDGE_TIMEOUT=30
|
|
10
|
+
# PROTONMAIL_BRIDGE_VERIFY_TLS=false
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "protonmail-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for Proton Mail via Proton Bridge (IMAP/SMTP)"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Manoah BERNIER", email = "manoah.bernier@gmail.com" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["mcp", "protonmail", "imap", "email", "proton-bridge", "llm"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
"Topic :: Communications :: Email :: Post-Office :: IMAP",
|
|
20
|
+
"Typing :: Typed",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"imapclient>=3",
|
|
24
|
+
"mcp>=2.3",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
protonmail-mcp = "protonmail_mcp.__main__:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/mhbxyz/protonmail-mcp"
|
|
32
|
+
Repository = "https://github.com/mhbxyz/protonmail-mcp"
|
|
33
|
+
Issues = "https://github.com/mhbxyz/protonmail-mcp/issues"
|
|
34
|
+
|
|
35
|
+
[dependency-groups]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.4",
|
|
38
|
+
"ruff>=0.12",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[build-system]
|
|
42
|
+
requires = ["hatchling"]
|
|
43
|
+
build-backend = "hatchling.build"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/protonmail_mcp"]
|
|
47
|
+
|
|
48
|
+
[tool.ruff]
|
|
49
|
+
line-length = 100
|
|
50
|
+
target-version = "py313"
|
|
51
|
+
lint.select = ["E", "F", "I", "UP", "B"]
|
|
52
|
+
lint.ignore = ["E501"]
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
addopts = "-q"
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from .bridge import MailboxError
|
|
8
|
+
from .config import ConfigError
|
|
9
|
+
from .server import get_client, server
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(argv: Sequence[str] | None = None) -> None:
|
|
13
|
+
parser = argparse.ArgumentParser(
|
|
14
|
+
prog="protonmail-mcp",
|
|
15
|
+
description="Read-only MCP server for Proton Mail via Proton Bridge (stdio).",
|
|
16
|
+
)
|
|
17
|
+
parser.add_argument(
|
|
18
|
+
"--check",
|
|
19
|
+
action="store_true",
|
|
20
|
+
help="Test the Proton Bridge connection, print a short diagnostic, and exit.",
|
|
21
|
+
)
|
|
22
|
+
args = parser.parse_args(argv)
|
|
23
|
+
if args.check:
|
|
24
|
+
raise SystemExit(run_check())
|
|
25
|
+
server.run(transport="stdio")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def run_check() -> int:
|
|
29
|
+
client = None
|
|
30
|
+
try:
|
|
31
|
+
client = get_client()
|
|
32
|
+
folders = client.list_folders()
|
|
33
|
+
recent = client.list_emails(limit=3)
|
|
34
|
+
except (ConfigError, MailboxError) as exc:
|
|
35
|
+
print(f"ERROR: {exc}", file=sys.stderr)
|
|
36
|
+
return 1
|
|
37
|
+
finally:
|
|
38
|
+
if client is not None:
|
|
39
|
+
client.close()
|
|
40
|
+
|
|
41
|
+
print(f"Bridge connection OK: {client.config.username} -> {client.config.endpoint}")
|
|
42
|
+
print(f"Folders ({len(folders)}):")
|
|
43
|
+
for folder in folders:
|
|
44
|
+
marker = "" if folder.selectable else " [not selectable]"
|
|
45
|
+
print(f" - {folder.name}{marker}")
|
|
46
|
+
print(f"Latest INBOX messages ({len(recent)}):")
|
|
47
|
+
for email in recent:
|
|
48
|
+
state = "unread" if email.unread else "read"
|
|
49
|
+
print(f" - [{state}] {email.received or email.date} | {email.sender} | {email.subject}")
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
main()
|