Skip to content
Conduit

mcp server

mu4e-mcp

Minimal, dependency-free MCP server exposing local mu (mu4e) email search to Claude. Pure Python stdlib.

nettnaut/mu4e-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 23, 2026

mu4e-mcp

CI

A minimal MCP server that exposes local mu email search to Claude Code. Written in pure Python stdlib — no dependencies beyond mu itself.

What it does

Gives Claude two tools:

ToolDescription
search_emailRun any mu find query and get matching messages as JSON
get_emailFetch the full body and headers of a message by file path (must be inside the maildir)

Example queries you can ask Claude:

  • "Search for unread emails in my work inbox"
  • "Find emails from alice@example.com in the last 7 days"
  • "Show me the body of that email"

For a fuller, plain-language guide to asking Claude to search and read your mail (with example prompts), see USAGE.md.

Requirements

  • Python 3.x (stdlib only)
  • mu installed and indexed (mu init && mu index)
  • Claude Code

Installation

1. Place the server script

mkdir -p ~/.local/mcp
cp mu4e-mcp.py ~/.local/mcp/mu4e-mcp.py
chmod +x ~/.local/mcp/mu4e-mcp.py

2. Register with Claude Code

Add to ~/.claude/settings.json under mcpServers:

{
  "mcpServers": {
    "mu4e": {
      "command": "python3",
      "args": ["/home/<your-username>/.local/mcp/mu4e-mcp.py"]
    }
  }
}

Replace <your-username> with your actual username, or use an absolute path.

3. Restart Claude Code

The MCP server is picked up on startup. After restarting, the search_email and get_email tools will be available.

Query syntax

search_email accepts any mu find query string. Examples:

QueryMeaning
flag:unread AND NOT flag:trashedAll unread, non-trashed mail
from:alice subject:invoiceFrom alice, subject contains "invoice"
maildir:/work/INBOX date:7d..nowWork inbox, last 7 days
flag:unread maildir:/personal/INBOXUnread personal mail

See the mu-find man page for full query syntax.

Notes

  • Email bodies are capped at 8000 characters to avoid flooding context
  • The server returns up to 20 results by default (configurable per query)
  • Requires mu to already be indexed — run mu index if results are stale
  • get_email only reads files inside the maildir (MU_MCP_MAILDIR, default ~/.maildir) after resolving symlinks and ..; anything else is refused
  • Query words are passed to mu find after --, so a query can never smuggle in mu options (e.g. --exec)

Remote access (HTTP mode)

To reach your mail from a client that isn't on this machine — e.g. a claude.ai custom connector — run the server in HTTP mode behind an authenticating proxy:

python3 mu4e-mcp.py --http 127.0.0.1:8766
# MCP endpoint: http://127.0.0.1:8766/mcp
  • Transport: stateless MCP Streamable HTTP — POST /mcp with a JSON-RPC message (or batch), JSON response; notifications get 202. No server-initiated stream, so GET is 405. Requests are handled one at a time.
  • Bind to loopback and put real authentication (e.g. an OAuth proxy) in front. The server warns on a non-loopback bind.
  • MU_MCP_HTTP_TOKEN — optional: require Authorization: Bearer <token> on every request, as a second lock behind the proxy.
  • MU_MCP_HTTP_TOKEN_HEADER — optional: carry the token in this header instead (e.g. X-Upstream-Key: <token>, raw value, no Bearer). For proxies that own Authorization themselves, such as Pomerium in MCP mode. A missing or wrong value gets 403 (not 401, which would make an OAuth proxy try upstream OAuth).
  • MU_MCP_ALLOWED_ORIGINS — comma-separated; requests carrying any other Origin header get 403 (DNS-rebinding guard). Requests without Origin (server-to-server) are allowed.
  • In HTTP mode every tools/call is logged to stderr (tool, argument names, ok, result size — never argument values or results).

Tests

python3 tests/test_mu4e_mcp.py   # no mu needed: JSON-RPC, hardening, HTTP mode

Contributing

See CONTRIBUTING.md.

License

GPL-3.0-or-later © 2026 Kjetil Rohde Jakobsen. Contributions welcome.

↑↓ navigate openesc close