This is an MCP (Model Context Protocol) server that provides Claude with tools to interact with Fastmail via the JMAP API. The primary use case is interactive email cleanup - finding and managing newsletters, receipts, marketing emails, and other accumulated mail.
- Single-file server:
server.pycontains all code - Protocol: JMAP (JSON Meta Application Protocol) over HTTPS
- Framework: FastMCP from the official MCP Python SDK
- Auth: Bearer token via Fastmail API token
- Caching: Session and mailbox data are cached in memory to reduce API calls
server.py- The MCP server implementation.env- ContainsFASTMAIL_API_TOKEN(not in git).gitignore- Ignores.envREADME.md- User-facing setup and usage docs
- Session:
https://api.fastmail.com/jmap/session - API:
https://api.fastmail.com/jmap/api/
- All requests use
methodCallsarray with[methodName, args, callId]format - Result references allow chaining:
"#ids": {"resultOf": "a", "name": "Email/query", "path": "/ids"} - Capabilities must be declared in
usingarray
Mailbox/querydoes NOT support filtering byrole- this was a bug we fixed- To find mailbox by role (inbox, trash, drafts), use
Mailbox/getto fetch all, then filter in code - Use helper functions
get_mailbox_by_role()andget_mailbox_by_name() - Caching: The
get_mailboxes()function caches mailbox data for the session lifetime to avoid repeated API calls
- JMAP returns
None(not empty dict/list) for empty result fields - Never use
"key" in response- this is True even when value isNone - Always use
response.get("key")which is falsy when value isNone - Example:
if response.get("updated"):notif "updated" in response:
| Tool | Purpose | Destructive |
|---|---|---|
fastmail_get_session_info |
Verify connection, show account info | No |
fastmail_list_mailboxes |
List all folders with counts | No |
fastmail_search_emails |
Search by sender, recipient, age, etc. | No |
fastmail_analyze_senders |
Top senders by volume | No |
fastmail_analyze_aliases |
Which aliases receive most mail | No |
fastmail_get_cleanup_candidates |
Find newsletters, shipping, marketing | No |
fastmail_delete_emails |
Move to trash or permanently delete | Yes |
# With uv (recommended)
uv run --with mcp --with httpx --with python-dotenv --with pydantic python server.py
# Or install deps and run directly
pip install mcp httpx python-dotenv pydantic
python server.pyLocated at ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"fastmail": {
"command": "/opt/homebrew/bin/uv",
"args": [
"run",
"--with", "mcp",
"--with", "httpx",
"--with", "python-dotenv",
"--with", "pydantic",
"python",
"/Users/greg/repos/fastmail-mcp/server.py"
]
}
}
}# Verify syntax
python -m py_compile server.py
# Test imports
uv run --with mcp --with httpx --with python-dotenv --with pydantic python -c "import mcp; import httpx; import dotenv; print('OK')"- "FASTMAIL_API_TOKEN not set" - Create
.envfile with token - "Authentication failed" - Token expired or revoked, regenerate in Fastmail settings
- "Permission denied" - Token needs Email scope, not read-only
- Move to trash fails with
Mailbox/query-Mailbox/querydoesn't support role filter; useMailbox/get+ filtering (fixed inget_mailbox_by_role()) TypeError: object of type 'NoneType' has no len()- JMAP returnsNonefor empty fields; useresponse.get("key")not"key" in response
Potential improvements:
- Add
fastmail_empty_trashtool - Add filtering by date range (not just "older than X days")
- Support for labels/tags if Fastmail adds them
- Batch operations with progress reporting
- Add cache invalidation/refresh mechanism for mailboxes