{"id":"13c5e6fb-1e77-4cea-a6b9-8af808fe5f7a","slug":"mu4e-mcp","kind":"mcp","name":"mu4e-mcp","summary":"Minimal, dependency-free MCP server exposing local mu (mu4e) email search to Claude. Pure Python stdlib.","owner":"nettnaut","repoUrl":"https://github.com/nettnaut/mu4e-mcp","license":"GPL-3.0","licenseTier":"strong-copyleft","trust":"community","stars":0,"match":null,"fewStars":true,"format":"mcp","createdAt":"2026-05-20T15:30:59.000Z","addedAt":"2026-10-21T06:06:58.680Z","logoUrl":"https://avatars.githubusercontent.com/u/1826798?v=4","linkOnly":false,"collection":null,"skillPath":null,"categories":["productivity","search","cloud-infra"],"tags":["claude","emacs","email","mu","mu4e"],"runtime":"binary","local":true,"cloudCapable":false,"cloudCapability":"unknown","cloudReadyOnPlatform":false,"needsApiKey":false,"reviewed":false,"syncedAt":"2026-10-23T00:32:03.983Z","sample":false,"url":"https://conduits.dev/tools/mu4e-mcp","language":"Python","transports":["stdio"],"install":{},"requirements":{"env":[],"upstreamMayCharge":false},"tools":null,"readme":"# mu4e-mcp\n\n[![CI](https://github.com/nettnaut/mu4e-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/nettnaut/mu4e-mcp/actions/workflows/ci.yml)\n\nA minimal MCP server that exposes local `mu` email search to Claude Code. Written in pure Python stdlib — no dependencies beyond `mu` itself.\n\n## What it does\n\nGives Claude two tools:\n\n| Tool | Description |\n|------|-------------|\n| `search_email` | Run any `mu find` query and get matching messages as JSON |\n| `get_email` | Fetch the full body and headers of a message by file path (must be inside the maildir) |\n\nExample queries you can ask Claude:\n- \"Search for unread emails in my work inbox\"\n- \"Find emails from alice@example.com in the last 7 days\"\n- \"Show me the body of that email\"\n\nFor a fuller, plain-language guide to *asking* Claude to search and read your\nmail (with example prompts), see **[USAGE.md](USAGE.md)**.\n\n## Requirements\n\n- Python 3.x (stdlib only)\n- [`mu`](https://www.djcbsoftware.nl/code/mu/) installed and indexed (`mu init && mu index`)\n- Claude Code\n\n## Installation\n\n### 1. Place the server script\n\n```bash\nmkdir -p ~/.local/mcp\ncp mu4e-mcp.py ~/.local/mcp/mu4e-mcp.py\nchmod +x ~/.local/mcp/mu4e-mcp.py\n```\n\n### 2. Register with Claude Code\n\nAdd to `~/.claude/settings.json` under `mcpServers`:\n\n```json\n{\n  \"mcpServers\": {\n    \"mu4e\": {\n      \"command\": \"python3\",\n      \"args\": [\"/home/<your-username>/.local/mcp/mu4e-mcp.py\"]\n    }\n  }\n}\n```\n\nReplace `<your-username>` with your actual username, or use an absolute path.\n\n### 3. Restart Claude Code\n\nThe MCP server is picked up on startup. After restarting, the `search_email` and `get_email` tools will be available.\n\n## Query syntax\n\n`search_email` accepts any `mu find` query string. Examples:\n\n| Query | Meaning |\n|-------|---------|\n| `flag:unread AND NOT flag:trashed` | All unread, non-trashed mail |\n| `from:alice subject:invoice` | From alice, subject contains \"invoice\" |\n| `maildir:/work/INBOX date:7d..now` | Work inbox, last 7 days |\n| `flag:unread maildir:/personal/INBOX` | Unread personal mail |\n\nSee the [mu-find man page](https://www.djcbsoftware.nl/code/mu/mu4e/Searching.html) for full query syntax.\n\n## Notes\n\n- Email bodies are capped at 8000 characters to avoid flooding context\n- The server returns up to 20 results by default (configurable per query)\n- Requires `mu` to already be indexed — run `mu index` if results are stale\n- `get_email` only reads files inside the maildir (`MU_MCP_MAILDIR`, default\n  `~/.maildir`) after resolving symlinks and `..`; anything else is refused\n- Query words are passed to `mu find` after `--`, so a query can never smuggle\n  in `mu` options (e.g. `--exec`)\n\n## Remote access (HTTP mode)\n\nTo reach your mail from a client that isn't on this machine — e.g. a claude.ai\ncustom connector — run the server in HTTP mode behind an authenticating proxy:\n\n```sh\npython3 mu4e-mcp.py --http 127.0.0.1:8766\n# MCP endpoint: http://127.0.0.1:8766/mcp\n```\n\n- **Transport:** stateless MCP *Streamable HTTP* — `POST /mcp` with a JSON-RPC\n  message (or batch), JSON response; notifications get `202`. No server-initiated\n  stream, so `GET` is `405`. Requests are handled one at a time.\n- **Bind to loopback** and put real authentication (e.g. an OAuth proxy) in\n  front. The server warns on a non-loopback bind.\n- **`MU_MCP_HTTP_TOKEN`** — optional: require `Authorization: Bearer <token>`\n  on every request, as a second lock behind the proxy.\n- **`MU_MCP_HTTP_TOKEN_HEADER`** — optional: carry the token in this header\n  instead (e.g. `X-Upstream-Key: <token>`, raw value, no `Bearer`). For proxies\n  that own `Authorization` themselves, such as Pomerium in MCP mode. A missing or\n  wrong value gets `403` (not `401`, which would make an OAuth proxy try\n  upstream OAuth).\n- **`MU_MCP_ALLOWED_ORIGINS`** — comma-separated; requests carrying any other\n  `Origin` header get `403` (DNS-rebinding guard). Requests without `Origin`\n  (server-to-server) are allowed.\n- In HTTP mode every `tools/call` is logged to stderr (tool, argument *names*,\n  ok, result size — never argument values or results).\n\n## Tests\n\n```sh\npython3 tests/test_mu4e_mcp.py   # no mu needed: JSON-RPC, hardening, HTTP mode\n```\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\n\n## License\n\n[GPL-3.0-or-later](LICENSE) © 2026 Kjetil Rohde Jakobsen. Contributions welcome.\n"}