Universal Mail MCP
# Universal Mail MCP
A local stdio MCP server for several email accounts, including accounts from different providers. Each mailbox action requires an `account_id`. Credentials stay in the operating system's credential store.
Version 0.1 implements Gmail OAuth, IMAP/SMTP, and Proton Mail Bridge adapters. Automated tests cover protocol fixtures and a real MCP stdio handshake. Live provider accounts have not yet been tested. Treat this release as an early version and start with read-only access.
## Providers
| Provider | Connection | Required setup |
|---|---|---|
| Gmail / Google Workspace | Gmail API, OAuth | Gmail API enabled; Google Desktop OAuth client; browser sign-in for each account |
| Yandex | IMAP/SMTP over TLS | IMAP access enabled; app password |
| Mail.ru | IMAP/SMTP over TLS | External-application password |
| Proton Mail | Local Proton Mail Bridge | Paid plan including Bridge; Bridge credentials; exported Bridge TLS certificate |
| Other IMAP providers | Custom TLS endpoints | IMAP/SMTP hosts, ports and credentials |
Several accounts from the same provider can coexist. Accounts are added locally; the public repository contains no working account configuration or credentials.
## Windows setup
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if needed, then open a new PowerShell window:
```powershell
winget install --id astral-sh.uv --exact
git clone https://github.com/moz9/universal-mail-mcp.git
cd universal-mail-mcp
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\setup.ps1 -RegisterCodex
```
The script installs the locked environment. `-RegisterCodex` adds a new connection named `universal_mail` using the Codex CLI. It refuses to replace an existing connection. Omit that flag to install without changing Codex settings. `-CheckOnly` prints the plan and changes nothing.
Add an account:
```powershell
$mail = '.\.venv\Scripts\universal-mail-mcp.exe'
& $mail add
& $mail list
```
The wizard asks for provider, email and a unique account ID. Sending and read-status changes default to OFF. For IMAP accounts, store the app or Bridge password through hidden terminal input:
```powershell
& $mail secret --account yandex_work
& $mail doctor --account yandex_work
```
Do not paste passwords into chat or pass them as command-line arguments. Restart Codex after registration. `list_accounts` should show your configured IDs; test reading before enabling sending.
### Gmail OAuth
Create a [Desktop OAuth client](https://developers.google.com/workspace/gmail/api/quickstart/python) in your Google Cloud project, enable Gmail API and configure the consent screen. If the app is in testing mode, add each intended account as a test user. Download its client JSON privately.
```powershell
& $mail auth-gmail --account google_personal --client-secret 'C:\private\client_secret.json'
& $mail doctor --account google_personal
```
The browser must authorize the email saved for that account ID. A different account fails verification and its token is not saved. Use a separate sign-in on another PC; do not copy user tokens. Token refresh uses only the selected account's stored credentials.
To enable sending or read-status changes later:
```powershell
& $mail permissions --account google_personal --send on
& $mail auth-gmail --account google_personal --client-secret 'C:\private\client_secret.json'
```
Repeat OAuth after changing Gmail permissions so the granted scopes match. Read-only uses `gmail.readonly`; sending adds `gmail.send`; read-status changes use `gmail.modify`. Google consent, testing limits and verification requirements depend on your Cloud project.
### Proton Bridge
Install the [official Bridge](https://proton.me/mail/bridge) on the same computer and sign in there. Use the username, password and ports displayed by Bridge, not your Proton login password. In the account wizard, choose `proton` and provide the exported Bridge certificate as `ca_file`.
Only `127.0.0.1`, `localhost` and `::1` are accepted. Certificate verification remains required. The trusted Bridge certificate replaces hostname validation for this loopback-only connection; remote IMAP/SMTP uses normal certificate and hostname checks.
Bridge decrypts mail locally. Text returned to Codex enters the model's context; this is not an entirely local AI processing system.
## Linux and macOS
```sh
uv sync --frozen --no-dev --python 3.13
.venv/bin/universal-mail-mcp add
codex mcp add universal_mail -- "$PWD/.venv/bin/python" -m universal_mail.cli serve
```
The OS credential backend must be Windows Credential Manager, macOS Keychain or Linux Secret Service. Plaintext, null and third-party fallback keyrings are rejected. Headless Linux needs an available Secret Service session. Linux/macOS setup has not yet been checked with live credentials.
## Tools
| Tool | Effect |
|---|---|
| `list_accounts` | Lists account IDs and permissions; no credential access |
| `check_account` | Checks connectivity; Gmail verifies authenticated email |
| `list_folders` | Lists IMAP folders or Gmail labels |
| `search_messages` | Structured filters, bounded results and pagination |
| `read_message` | MIME text and attachment metadata; does not mark read |
| `prepare_send` | Local preview, locked sender, optional threaded reply; sends nothing |
| `send_prepared` | Sends that preview once after user authorization |
| `mark_read` | Explicit read/unread change with verification |
Every tool except `list_accounts` requires an explicit account ID. Search filters are sender, subject, text, after, before and unread. Dates use `YYYY-MM-DD`: UTC boundaries for Gmail, server calendar days for IMAP. IMAP Unicode search requires server UTF-8 support; unsupported searches fail without a lossy fallback.
Gmail `mailbox` is a label ID (`INBOX` by default; empty searches all mail). IMAP uses a folder name. Message IDs returned by this MCP are opaque references tied to account configuration and exact mailbox. Reuse the original account/mailbox when reading, replying or changing read status. IMAP checks UIDVALIDITY. These references prevent accidental context mixing; they are not an authorization boundary against a caller who deliberately constructs one.
To reply, pass the parent reference as `reply_to_message_id` to `prepare_send` and use the same subject, optionally prefixed by `Re: `. The server derives RFC reply headers and Gmail thread ID from that account's parent. Recipients remain explicit; it does not automatically reply-all or follow Reply-To.
The preview token expires after ten minutes. A token does not prove human authorization: the assistant must show the preview and obtain permission to send. Identical recipient/subject/body/reply sends are blocked for 24 hours across processes. Failure or uncertain delivery consumes the token and blocks automatic retries. Inspect Sent before preparing another send. SMTP acceptance and Gmail message ID are not proof of recipient delivery.
## Configuration and privacy
The default config is `%LOCALAPPDATA%\UniversalMailMCP\accounts.json` on Windows, or `~/.config/UniversalMailMCP/accounts.json` elsewhere. Use `--config PATH` before the subcommand for a separate profile. The wizard creates the file; [the example](examples/accounts.example.json) shows its non-secret structure.
Secrets are namespaced by the absolute config path and account ID. Moving the config requires entering credentials again. The send journal stores only request hashes, timestamps and attempt state; bodies and recipients are not persisted there. Pending previews stay in process memory and disappear on restart. Search/read results are not cached to disk.
Account settings reload before each MCP call. Changing any setting invalidates existing message references and pending send previews. No default-account fallback, aliases, forwarding rules, mailbox filters, deletion or movement tools are implemented in this version.
Do not give mail content authority to invoke tools. Headers, bodies, HTML source and attachment names are untrusted data. HTML is never rendered or executed. Bodies are limited to 100000 characters and messages to 4 MB; `truncated` reports text truncation. Attachment downloads/uploads, remote drafts and separate IMAP/SMTP login credentials are not supported in v0.1.
## Development
```sh
uv sync --frozen
uv run pytest
uv run ruff check .
uv run python -m build
uv run pip-audit --progress-spinner off
```
Tests use fake mail transports and synthetic messages. The stdio integration test starts the actual MCP process with an empty temporary account profile. No test sends real mail. [A GitHub Actions example](examples/github-actions-tests.yml) covers Windows and Linux; it is not enabled because the publishing credential lacks the `workflow` scope.
Connection configuration follows [Codex MCP documentation](https://learn.chatgpt.com/docs/extend/mcp?surface=cli). Provider setup references: [Yandex](https://www.yandex.ru/support/yandex-360/customers/mail/ru/mail-clients/others), [Mail.ru](https://help.mail.ru/mail/login/mailer/), [Proton Bridge](https://proton.me/mail/bridge).
## Быстрый старт
В PowerShell клонируйте репозиторий и запустите `scripts/setup.ps1 -RegisterCodex`. Затем выполните `.venv/Scripts/universal-mail-mcp.exe add`. Для каждого ящика задайте отдельный ID и пройдите свою авторизацию. Пароли вводятся командой `secret`, Gmail подключается через `auth-gmail`. После перезапуска Codex сначала проверьте чтение. Текущие почтовые подключения и автоматизации этот пакет не заменяет.
TDQS
Scored across 8 tools
Each tool has a clearly distinct purpose: account/folder discovery (list_accounts, check_account, list_folders), message access (search_messages, read_message), state change (mark_read), and a two-step send flow (prepare_send, send_prepared). The potentially confusable pair mark_read vs read_message is explicitly differentiated ('without setting Seen' vs 'change read/unread state').
All eight tools follow a consistent snake_case verb_noun convention (mark_read, list_accounts, check_account, list_folders, search_messages, read_message, prepare_send, send_prepared). No mixed styles or vague standalone verbs.
Eight tools is well-scoped for a mail server covering discovery, read, search, flag, and send. Each tool earns its place with no redundant or filler operations.
Core read and send workflows are covered end-to-end, including a safe preview/send split. However, lifecycle gaps remain: no move/delete/archive or flag/star operations, and attachment download is explicitly unsupported in v0.1.