mail-mcp
by JACKWULA123
README.md
# mail-mcp
A local [MCP](https://modelcontextprotocol.io) server that lets AI coding agents
(**Claude Code** and **OpenAI Codex CLI**) access your mailboxes: **search, read
messages/threads, read & download attachments, and create drafts** — across multiple
Gmail and personal Outlook accounts, routed by nickname.
**Current capability: read-only + drafts. It never sends, deletes, or relabels mail.**
Sending/organizing are declared in the provider contract but gated off; enabling them
later is a config change, not a rewrite (see *Extending*).
## How safety is enforced
- **Outlook (Graph):** we request `Mail.Read` + `Mail.ReadWrite` but **not** `Mail.Send`,
so sending is impossible at the token level.
- **Gmail:** there is *no* scope that grants drafts without also granting send
(`gmail.compose` covers both). So for Gmail, "no send" is enforced at the
**application layer** — no send tool is exposed and `drafts.send`/`messages.send`
are never called. Keep this in mind before granting the `send` capability.
## Install
Requires Python ≥ 3.11.
```bash
uv venv --python 3.12 .venv
uv pip install --python .venv/bin/python -e ".[dev]"
```
## Configure
```bash
.venv/bin/mail-mcp init # creates ~/.config/mail-mcp/{config.toml,secrets,tokens,downloads}
```
Edit `~/.config/mail-mcp/config.toml` (see `config.example.toml`). Each mailbox is one
`[accounts.<nickname>]` block. Everything lives under `$MAIL_MCP_HOME` (default
`~/.config/mail-mcp`); set `MAIL_MCP_HOME` to relocate it.
### Gmail OAuth (per Google account)
1. [Google Cloud Console](https://console.cloud.google.com/) → new project → **enable the Gmail API**.
2. **OAuth consent screen** → User type **External** → add each Gmail address as a
**Test user** (or publish to Production to avoid the 7-day refresh-token expiry in Testing mode).
3. **Credentials → Create OAuth client ID → Desktop app** → download the JSON →
save as `~/.config/mail-mcp/secrets/gmail_client_secret.json` (the same client can
serve multiple Gmail accounts).
4. `.venv/bin/mail-mcp auth gmail-personal`
> **University / Workspace accounts** (e.g. `@g.ecc.u-tokyo.ac.jp`): the admin may
> restrict third-party API access. If login fails with **`Error 400: admin_policy_enforced`**,
> your self-made client is blocked at the org level — Testing mode does **not** bypass
> this; only the Workspace admin can allowlist the client ID. Test a personal `@gmail.com`
> account first to confirm your setup, then try the university one.
### Outlook OAuth (personal account)
1. [Azure Portal](https://portal.azure.com/) → **Microsoft Entra ID → App registrations → New**.
Supported account types: **Personal Microsoft accounts only**.
2. **Authentication** → Add platform **Mobile and desktop applications** → redirect
`http://localhost` → mark as a public client.
3. **API permissions** → Microsoft Graph → **Delegated** → add `Mail.Read`,
`Mail.ReadWrite`, `offline_access`. **Do not add `Mail.Send`.**
4. Put the **Application (client) ID** into the `outlook` block's `client_id`.
5. `.venv/bin/mail-mcp auth outlook`
Check status any time: `.venv/bin/mail-mcp accounts`
## Register with your AI client
Both launch the server over stdio. Use an **absolute** interpreter path and set
`MAIL_MCP_HOME` explicitly so token discovery never depends on cwd or env forwarding.
**Claude Code** (user scope — available in every project):
```bash
claude mcp add --scope user mail \
--env MAIL_MCP_HOME=/Users/<you>/.config/mail-mcp \
-- /path/to/mail_mcp/.venv/bin/python -m mail_mcp
```
**Codex CLI** — add to `~/.codex/config.toml` (Codex does **not** forward parent env by
default, hence `env_vars`):
```toml
[mcp_servers.mail]
command = "/path/to/mail_mcp/.venv/bin/python"
args = ["-m", "mail_mcp"]
env_vars = ["HOME", "PATH"]
[mcp_servers.mail.env]
MAIL_MCP_HOME = "/Users/<you>/.config/mail-mcp"
```
Verify: `claude mcp list` (shows `mail ✓ Connected`) / Codex `/mcp`.
## Tools
| Tool | Purpose |
|------|---------|
| `list_accounts` | list mailboxes, provider, capabilities, auth status |
| `search_messages(account, query, limit=20)` | search (Gmail operators for Gmail; free-text `$search` for Outlook) |
| `read_message(account, message_id, format="text")` | headers + body (`text`/`html`/`full`) + attachment list |
| `read_thread(account, thread_id, format="text")` | all messages in a thread/conversation |
| `list_attachments(account, message_id)` | attachment id/name/mime/size |
| `download_attachment(account, message_id, attachment_id, dest_dir=None, include_base64=False)` | save to disk, return path |
| `create_draft(account, to, subject, body, cc, bcc, reply_to_message_id, body_is_html, attachments)` | create an unsent draft |
## Extending
- **A new mailbox of an existing type** → add an `[accounts.<nick>]` block + `mail-mcp auth <nick>`. No code.
- **A new mailbox *type*** (e.g. IMAP) → implement `MailProvider` (`src/mail_mcp/providers/base.py`)
and register it in `providers/registry.py`. The tool surface is unchanged.
- **Enable sending** → add `"send"` to an account's `capabilities`. The `send_message`
tool then registers automatically. For Graph, also add `Mail.Send` and re-run
`mail-mcp auth`; Gmail needs no scope change. (Re-read the Gmail safety note first.)
## Development
```bash
.venv/bin/pytest # unit tests (no network)
.venv/bin/ruff check .
.venv/bin/mypy src
```
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues