Skip to main content
Glama
agimenezdev

imap-mcp

by agimenezdev
README.md
# imap-mcp

[![CI](https://github.com/agimenez-dev/imap-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/agimenez-dev/imap-mcp/actions/workflows/ci.yml)
[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

A live **IMAP + SMTP** [MCP](https://modelcontextprotocol.io) server. It lets an
LLM read, search, send and manage email across multiple accounts over a real
mail connection — with sending that is **safe by default**.

```
You:  "Reply to the vendor confirming we approved the quote, attach the PO."
LLM:  → mail_send(..., confirm=false)        # returns a preview, sends nothing
You:  "Looks good, send it."
LLM:  → mail_send(..., confirm=true)          # now it actually goes out
```

## Why another email MCP

Email is the one integration where a careless agent does real damage — a wrong
auto-send can't be unsent. This server is built around that:

- **Confirm before send.** `mail_send` returns a preview and sends nothing unless
  you pass `confirm=true`. No message ever leaves on the first call.
- **Read-only accounts.** Mark an account `readonly` and every mutating tool
  (send, flag, move, delete) is refused for it — useful for mailboxes you only
  want the model to *read*.
- **No secrets on disk.** The config file contains no passwords. Each account
  says *where* its password comes from — an environment variable or a command
  (your password manager) — and it's read on demand, held only in memory.
- **Multi-account.** Personal, work and shared mailboxes side by side, each with
  its own host and credentials.

## Tools

| Tool | What it does |
| --- | --- |
| `mail_list(account, folder, limit, unseen_only)` | Recent messages (headers only) |
| `mail_search(account, query, folder, limit)` | Search From / Subject / body |
| `mail_read(account, uid, folder)` | Full message: body + attachment names |
| `mail_download_attachment(account, uid, folder, filename, dest_dir)` | Save attachments to disk |
| `mail_send(account, to, subject, body, cc, bcc, attachments, confirm)` | **Send — `confirm=true` required** |
| `mail_flag(account, uid, action, folder)` | Mark seen / unseen / flagged / deleted |
| `mail_move(account, uid, dest_folder, folder)` | Move between folders |
| `mail_folders(account)` | List IMAP folders |
| `mail_accounts()` | List configured accounts and credential status |

## Install

Requires Python 3.11+.

```bash
git clone https://github.com/agimenez-dev/imap-mcp
cd imap-mcp
uv sync          # or: pip install -e .
```

## Configure

Create `~/.config/imap-mcp/accounts.toml` (or set `IMAP_MCP_CONFIG` to any path).
Start from [`accounts.example.toml`](accounts.example.toml):

```toml
[[account]]
address = "you@example.com"
host = "imap.example.com"     # used for IMAP (993) and SMTP (465)
password_env = "MY_MAIL_PASSWORD"     # read the password from this env var

[[account]]
address = "shared@example.com"
host = "imap.example.com"
password_command = "rbw get 'Shared Mailbox'"   # ...or from a command's stdout
readonly = true                                  # read-only: no send/flag/move
```

Each account resolves its password from **either** `password_env` (an environment
variable) **or** `password_command` (a shell command whose first line of stdout is
the password). The file itself never contains a secret.

## Use with Claude Desktop / Claude Code

```json
{
  "mcpServers": {
    "imap": {
      "command": "uv",
      "args": ["--directory", "/path/to/imap-mcp", "run", "imap-mcp"]
    }
  }
}
```

## Security notes

- Passwords are never written to disk by this server. They live in memory only
  for the duration of a connection (cached per process to avoid re-prompting a
  password manager on every call).
- Reads use `BODY.PEEK[]`, so reading a message does **not** mark it as seen.
- `readonly` accounts reject send/flag/move/delete at the application layer,
  before any connection is opened.
- IMAP and SMTP use implicit TLS (ports 993 / 465) with certificate verification.

## Development

```bash
uv sync --extra dev
uv run pytest        # config + MIME layers are covered without a mail server
uv run ruff check .
```

The IMAP/SMTP transport is a thin wrapper over the standard library; the tested
logic is the parts that are easy to get wrong — config/credential resolution and
MIME decoding.

## License

MIT — see [LICENSE](LICENSE).