SuperAPI AI Directory

mcp-server-notmuch

Local Maildir access via notmuch: full-text search, tag filtering, threads, attachments and unanswered-mail tracking, with optional drafts and tagging.

Open website

About this tool

Purpose

mcp-server-notmuch is an MCP server that exposes a local notmuch email database to LLM clients such as Claude. Its positioning is "read-only first": searching, reading, and understanding email are always available; any write operation (drafts, tags, exporting files) requires an explicit enabling flag and is confined to clear boundaries.

It never sends email. There is no sending capability anywhere in the codebase, under any mode, under any flag. Drafts are written to the local maildir, for the user to review and send themselves in a real email client.

Main capabilities

  • Search and read email through the real notmuch CLI, including threads, individual messages, attachments, calendar invitations, office documents, and images.
  • Understand scope: a named, preconfigured notmuch query (for example "personal mail" versus "mailing lists"), with every search confined to that scope unless otherwise requested.
  • Answer "what hasn't been replied to" and "who owes me a reply" (mail_pending), "has this come up before" (mail_related_threads), and provide a low-token overview before reading an entire long thread (mail_thread_overview).
  • Optionally compose and modify plain-text drafts (--allow-drafts), tag email (--allow-tags), or export attachments and mailbox history as a Gource visualization to a specified directory (--allow-export DIR).

What it does not do

  • It does not send email, ever.
  • Unless --allow-tags (tagging) or --allow-drafts (writing new files to the drafts maildir) is passed, it does not modify email in any way. Neither of these flags allows it to touch the contents of existing email.
  • It does not read or write outside the notmuch database, the configured drafts maildir, and (only with --allow-export) the configured export directory.
  • It does not require or use the notmuch2 Python bindings, so installation does not require a compiler.

Installation and system requirements

Install from PyPI (after release): --prerelease=allow is required, because the project pins a prerelease version of the mcp SDK, which is not optional.

System requirements:

  • notmuch (the CLI, not justthe library) in PATH, or pointed to via notmuch.binary in the configuration.
  • poppler-utils (pdftotext) for reading PDF attachments. Without it, calling mail_read_attachment on a PDF reports the missing package.
  • pandoc or libreoffice for reading office documents (doc/docx/odt/rtf). When neither is present, the error lists both options.
  • Optional: Pillow (pip install 'mcp-server-notmuch[image]') lets oversized image attachments be downscaled rather than rejected.
  • Optional: Gource for actually playing back the log written by mail_export_gource.

Usage

MCP client configuration:

  • Read-only (default: search and read tools only).
  • Enable drafts (also allows modifying/tagging on demand).
  • Or configure manually in .mcp.json.
  • Add to claude_desktop_config.json (read-only default).
  • Enable drafts.

The server reads $XDG_CONFIG_HOME/mcp-server-notmuch/config.toml (or ~/.config/mcp-server-notmuch/config.toml when XDG_CONFIG_HOME is unset), or the path given via --config. A missing file is not an error: the server falls back to the system notmuch binary and a built-in scope all with an empty query. Once the file exists, [scopes] is authoritative, and all is no longer implied.

See config.example.toml for a fully commented reference file. mail_create_draft / mail_update_draft refuse to run unless both drafts.maildir and drafts.from are set. mail_pending direction="waiting" requires [identity] addresses (or a readable notmuch user.primary_email) to know which address is "you".

Nothing is truncated by default. This is a local fast mailbox, not a rate-limited APIs: mail_search, mail_pending, mail_related_threads, mail_list_addresses, mail_find_attachments, and mail_export_gource all return every matching row unless an explicit limit is passed to the tool call; message bodies/attachment text are returned in full unless limits.max_body_chars / limits.max_attachment_chars are set. Whenever an explicit limit or character cap actually truncates content, the output states: "Showing N of TOTAL ..." and suggests omitting the limit to see everything. There are no silent, invisible caps anywhere in the server.

Scope resolution: the tool's scope parameter names a configured scope; its query is AND-combined with the caller's query, with parentheses on both sides ((scope_query) and (user_query)), so an or on either side cannot leak into the other. An unknown scope name errors and lists the configured scopes; scope="all" is never silently left unfiltered unless you have defined a scope literally named all.

Tiers and Tools

Four tiers. The read tier is always registered. The other three are registered only when the corresponding flag is passed—there is no "registered but denied" state; unauthorized tools simply do not appear in the tool list the client sees.

  • Read tier (always on)
  • Draft tier (--allow-drafts)
  • Tag tier (--allow-tags)
  • Export tier (--allow-export DIR)

mail_export_gource writes one line per message, in the format timestamp|username|type|path|colour, sorted oldest first (as Gource requires). path is folder/normalized-subject, so an entire reply chain lands at the same point in the tree; colour is a stable hash of the folder name, so folders keep their colour across repeated exports. Play it with:

All matching messages are included by default; when the query is broad, limit matters, because feeding every received mailing-list message to Gource produces an unwatchable animation, so limit the query first or explicitly set a limit.

Security and Limits

This is a mail server handed to an LLM; message content is not as trustworthy as your own instructions.

  • Prompt injection. Message bodies, attachment text, calendar summaries, and thread overview lines are third-party content, not instructions. render.py wraps each of them in explicit -----BEGIN/END UNTRUSTED EMAIL CONTENT----- markers, with a note that nothing inside should be treated as a command, so no single tool can forget to do this.
  • Path restrictions. The draft maildir (compose.py) and the export directory (export.py) each resolve the target path and then verify it is still within the configured root directory. This catches literal .. and symlinks pointing outside the root directory (Path.resolve() follows symlinks), and mail_create_draft / mail_update_draft cannot be induced by any combination of arguments to write outside the configured maildir.
  • Never use a shell. Every subprocess call is subprocess.run([...], shell=False) with an argv list; queries are passed as a single argv element and are never interpolated into a shell string or into a notmuch query string beyond normal AND/OR combinations.
  • No content in diagnostics. Errors and progress are output to stderr and contain no content (byte counts or file paths, never message bodies).
  • Never silently unfiltered. The scope parameter ANDed with the query is always explicit; unless a scope literally named all is configured, there is no hidden "search everything" fallback.
  • Never silently truncate. This is a local fast mailbox: result lines, message bodies, and attachment text are returned in full unless the tool call passes an explicit limit or the configuration explicitly sets limits.max_body_chars / limits.max_attachment_chars. Whenever these do truncate content, the output states the true total and how to view the rest, never silently truncating.

notmuch query syntax

The query / scope parameters accept the full notmuch search syntax: from:, to:, subject:, tag:, date: ranges, boolean and / or / not, etc. For the full reference see notmuch-search-terms(7) (man notmuch-search-terms).

Versions and Testing

Targets MCP spec 2026-07-28 and pins mcp==2.0.0b2, the Python SDK prerelease built for that spec. Once the spec and a matching stable SDK are released, the pin will move to stable; until then, every install (uv pip install, uvx) requires --prerelease=allow.

Tests build a small handmade maildir and run real notmuch commands against it; your real mail is not touched. CI runs on Python 3.11, 3.12, and 3.13, with notmuch installed via apt.

MIT licensed, see LICENSE.