MCP Servers

模型上下文协议服务器、框架、SDK 和模板的综合目录。

F
Fastmail Clean MCP

MCP server by MalcolmWardlaw

创建于 6/5/2026
更新于 about 3 hours ago
Repository documentation and setup instructions

fastmail-clean-mcp

A sanitizing Fastmail MCP proxy. It speaks JMAP directly to api.fastmail.com and returns email bodies already stripped of HTML, tracking URLs, and invisible preview padding — so the model never pays tokens for noise it can't use.

Unofficial and independent. Not affiliated with or endorsed by Fastmail Pty Ltd. "Fastmail" is a trademark of its owner, used here only to describe what the proxy talks to.

Why

Fastmail's official MCP server (https://api.fastmail.com/mcp) is excellent — OAuth, zero-maintenance, works on mobile — but it does no body sanitization. It hands back the sender's text/plain part verbatim, and that part is frequently garbage:

  • HTML-stuffed text/plain. Many ESP/transactional senders (PayPal and most billing mail) dump a full <table> + inline CSS into the plain-text part. Measured: one "your statement is available" email = ~14,000 tokens for ~40 tokens of meaning (≈378×).
  • Tracking URLs. The same ~900-char base64 click-tracker repeated a dozen times per message (euid, tuid, pid, configId, utm_*, %opentrack%, …).
  • Invisible padding. Runs of U+034F / U+00AD that marketers use to blank the inbox preview line; tiktoken expands a 59-char run into ~184 tokens.

A strip only saves tokens if it happens in the retrieval path, before the bytes enter the context window. The official server won't do it and exposes no knob, so this proxy intercepts and cleans there. The goal: stream full-ish bodies of an entire inbox sweep into context and categorize with real compute — not guess from previews, and not hard-pull raw bodies that blow up every time.

Architecture

The two servers sit side by side over the same JMAP account. Neither knows the other exists; mailbox and email IDs are identical across both.

                        ┌─ official MCP server (Fastmail-hosted, raw) ─┐
your Fastmail account ──┤                                              ├─→ Claude
   (JMAP API)           └─ server.py (this proxy, sanitizes) ──────────┘

Keep the official server connected for what this one deliberately omits (calendar, contacts, sending); route body reads here. See Tool routing.

Tools

| tool | returns | |------|---------| | list_folders | mailbox folders: id, name, role, totalEmails, unreadEmails | | search_email | email summaries (subject/from/receivedAt/preview) — no bodies | | read_email | one email with a fully sanitized body (head_chars caps length) | | triage_bulk | N emails with sanitized, head-truncated bodies in one batched JMAP call |

This proxy is read-only by design — no send/move/flag/delete.

Files

| file | role | |------|------| | fastmail_clean.py | pure-Python sanitizer. clean() is the whole point. Network-free; --selftest benchmarks it on bundled fixtures. | | server.py | FastMCP server: JMAP client + the four tools above. Imports clean() from fastmail_clean.py — keep them in the same dir. |

Both files carry PEP 723 inline metadata, so uv run resolves dependencies automatically. No venv to manage.

Prerequisites

  • Python ≥3.11
  • uv on PATH
  • A Fastmail account with a JMAP API token (created in the Run step below)
  • macOS only for the login-Keychain token path; the FASTMAIL_API_TOKEN env-var path is cross-platform

Run

# Debug the sanitizer with zero credentials (bundled fixtures):
uv run fastmail_clean.py --selftest

# Run the live server (stdio transport):
export FASTMAIL_API_TOKEN=fmu1-...   # Fastmail → Settings → Privacy & Security → API tokens
uv run server.py

Create the token as JMAP protocol, read-only, Email scope — that maps to the only JMAP capability the server requests (urn:ietf:params:jmap:mail). The MCP protocol option is Fastmail's own hosted server and won't work with this JMAP client.

Wire into Claude Desktop / Code

server.py resolves the token from FASTMAIL_API_TOKEN if set, otherwise from the macOS login keychain (service fastmail-clean-mcp). Pick one of:

macOS login keychain (recommended). Seed it once — readable without a prompt while you're signed in, so the config stays a plain uv run with no secret on disk and no auth in the launch path:

security add-generic-password -U -s fastmail-clean-mcp -a "$USER" -w fmu1-... -A
"fastmail-clean": {
  "command": "/opt/homebrew/bin/uv",
  "args": ["run", "/abs/path/to/server.py"]
}

(-A lets any app read it without a prompt; the token is read-only and revocable. Absolute uv path because the GUI app launches with a minimal PATH.)

1Password via op run (cross-platform secret manager; authorizes on each launch). --no-masking is required so op's secret-masking doesn't corrupt JSON-RPC over stdio:

"fastmail-clean": {
  "command": "/opt/homebrew/bin/op",
  "args": ["run", "--no-masking", "--",
           "/opt/homebrew/bin/uv", "run", "/abs/path/to/server.py"],
  "env": { "FASTMAIL_API_TOKEN": "op://Private/<item>/credential" }
}

Inline (simplest; token on disk in the config):

"fastmail-clean": {
  "command": "uv",
  "args": ["run", "/abs/path/to/server.py"],
  "env": { "FASTMAIL_API_TOKEN": "fmu1-..." }
}

Sanitizer design (clean())

Pipeline: detect-or-forced HTML → BeautifulSoup(lxml).get_text → strip invisibles (an explicit zero-width set plus Unicode categories Cf/Mn) → drop URLs that exceed max_url_len or carry tracker params (euid, tuid, pid, configId, utm_*, %opentrack%, …) while keeping short clean links → collapse whitespace → optional head_chars truncation.

Knobs: is_html (force HTML handling), head_chars (keep only the meaningful head), max_url_len (default 80).

Acceptance targets

From real inbox measurements (tiktoken cl100k_base):

  • PayPal-class HTML-stuffed email: ~14,000 → < 100 tokens
  • Airbnb-class padded marketing: ~1,000 → < 400
  • Clean personal prose: unchanged (no false stripping)
  • A 50-email triage_bulk (head_chars=600): < 20K tokens total (vs ~83K raw) — 500+ bodies fit a 200K window.

uv run fastmail_clean.py --selftest currently reports 37× on the PayPal fixture, 6.9× on padded marketing, and 1.0× on clean prose. If a change regresses these, check _TRACKER_PARAMS and _looks_like_html first.

Tool routing

With both servers connected, the body-bearing tools collide by name. The token savings only land if reads route here. A routing rule that works:

  • Reading a body / bulk triage (read_email, triage_bulk): always use this server. The official equivalents return raw HTML and blow up context.
  • Search / list folders: body-free on both, either is fine.
  • Calendar / contacts / send / move / flag: the official server only — this one is read-only and mail-only.
  • IDs are identical across both servers, so ids from triage_bulk can be passed straight into the official server's write tools with no re-fetch.

JMAP notes

  • Session bootstrap: GET https://api.fastmail.com/jmap/sessionapiUrl + primaryAccounts["urn:ietf:params:jmap:mail"].
  • Bodies: Email/get with fetchTextBodyValues + fetchHTMLBodyValues, then map textBody[].partId / htmlBody[].partId into bodyValues. Prefer text, fall back to HTML; clean() flattens HTML-stuffed text either way.
  • Calendar is unavailable via API token (JMAP Calendars is still an IETF draft) — this proxy is mail-only by design.

License

MIT. See LICENSE.

快速设置
此服务器的安装指南

安装包 (如果需要)

uvx fastmail-clean-mcp

Cursor 配置 (mcp.json)

{ "mcpServers": { "malcolmwardlaw-fastmail-clean-mcp": { "command": "uvx", "args": [ "fastmail-clean-mcp" ] } } }
作者服务器
其他服务器由 MalcolmWardlaw