notmuchproxy

notmuchproxy

Enables LLMs to search and read email from a notmuch archive, providing tools for searching threads, retrieving messages, and listing tags through an MCP endpoint.

Category
访问服务器

README

notmuchproxy

ci

Give an LLM read-only access to your email.

notmuchproxy is a small API server over a notmuch email archive. It exposes the same four tools two ways:

  • an OpenAPI/REST API (schema at /openapi.json), usable as an Open WebUI tool server
  • an MCP endpoint (streamable HTTP at /mcp/), usable from Claude Code, claude.ai, and any other MCP client

There is no UI and no write path: the server only ever reads the archive, so the worst an over-eager LLM can do is search your email too enthusiastically.

The tools

Tool REST endpoint Description
search_email GET /search?q=... Search threads with notmuch query syntax (from:, to:, subject:, tag:, date:, free text)
get_thread GET /threads/{thread_id} Every message in a thread, oldest first, bodies as plain text
get_message GET /messages/{message_id} A single message by Message-ID
list_tags GET /tags All tags in the archive

Plus an unauthenticated GET /healthz. Everything else requires Authorization: Bearer $NOTMUCHPROXY_API_KEY — including the MCP endpoint. The MCP tools are derived from the OpenAPI schema at startup, so the two surfaces can't drift apart.

Configuration

Everything is environment variables:

Variable Required Description
NOTMUCH_DATABASE yes¹ path to the notmuch database root (the directory containing .notmuch); the docker image defaults it to /mail
NOTMUCHPROXY_NOTMUCH_BIN no notmuch executable (default: notmuch)
NOTMUCHPROXY_EXCLUDE_TAGS no comma-separated tags (e.g. spam,deleted) whose messages are excluded from all results — searches (even explicit tag:spam queries), threads, single messages, and the tag list. Useful for noise and for keeping adversarial spam content away from the model.
NOTMUCHPROXY_CORS_ORIGINS no comma-separated origins allowed for CORS; * (the default) allows any origin, empty string disables CORS. Needed when a browser calls the API directly, e.g. tool servers added in Open WebUI's user settings. The auth token remains the actual access control.

Authentication

Pick exactly one mechanism (the server refuses to start with both or neither); it applies to both the REST API and the MCP endpoint.

Static bearer token — simplest; what Open WebUI's OpenAPI tool servers and Claude Code's --header flag speak. claude.ai custom connectors can not use this mode (they only support OAuth).

Variable Description
NOTMUCHPROXY_API_KEY the bearer token clients must present

OIDC via an external identity provider — works with any OIDC IdP (authentik, Keycloak, Google, ...). notmuchproxy presents a spec-compliant MCP authorization server to clients — including the dynamic client registration claude.ai requires — while acting as an ordinary OIDC client of your IdP upstream (your IdP does not need to support DCR itself). Tokens issued through the flow are accepted on both the MCP and REST endpoints.

Variable Description
NOTMUCHPROXY_OIDC_CONFIG_URL the IdP's OIDC discovery URL, e.g. https://auth.example.com/application/o/notmuchproxy/.well-known/openid-configuration for authentik
NOTMUCHPROXY_OIDC_CLIENT_ID client id of the app registered at the IdP
NOTMUCHPROXY_OIDC_CLIENT_SECRET client secret of that app
NOTMUCHPROXY_PUBLIC_URL public base URL of this server, e.g. https://notmuch.example.com — used for OAuth callbacks and discovery metadata; claude.ai requires HTTPS

IdP setup (authentik example): create an OAuth2/OpenID provider with a confidential client and redirect URI $NOTMUCHPROXY_PUBLIC_URL/auth/callback, scopes openid profile email. Who may authorize is controlled by your IdP's own policies (in authentik, bind the application to users/groups).

¹ optional if the host has a notmuch config that already points at the database.

Running in production

The server is distributed as a docker image. Mount your maildir — which must already contain the .notmuch index — read-only at /mail:

docker run -d -p 8000:8000 \
  -e NOTMUCHPROXY_API_KEY=some-long-random-string \
  -v /path/to/your/mail:/mail:ro \
  ghcr.io/igor47/notmuchproxy:latest

Indexing (notmuch new) is not done by this container — keep running it wherever your mail is delivered. The container picks up index updates automatically since xapian readers don't block writers.

docker compose, as a non-root user

The image runs as a built-in non-root user (uid 1000) by default. If your maildir is owned by a different user, override user: so the container can read the mount — no rebuild needed:

services:
  notmuchproxy:
    image: ghcr.io/igor47/notmuchproxy:latest
    restart: unless-stopped
    # run as the uid/gid that owns your maildir (`id -u`/`id -g`);
    # omit entirely if uid 1000 can read your mail
    user: "1000:1000"
    ports:
      - "8000:8000"
    environment:
      NOTMUCHPROXY_API_KEY: ${NOTMUCHPROXY_API_KEY:?set this in .env}
    volumes:
      - /path/to/your/mail:/mail:ro

The app never writes to the archive (and the :ro mount enforces that), so read permission on the maildir is all it needs.

Connecting clients

Open WebUI

In static mode, add an OpenAPI tool server (Admin Settings → Tools):

  • URL: http://your-host:8000
  • Auth: Bearer, key = your NOTMUCHPROXY_API_KEY

Open WebUI fetches /openapi.json (which is unauthenticated, like /healthz) to discover the tools, then sends the bearer token on each call.

In OIDC mode, use Open WebUI's MCP tool server type instead, pointed at https://your-host/mcp with OAuth 2.1 auth — it performs the same discovery and login flow as claude.ai.

Tool servers added under Admin Settings are called from the Open WebUI backend, but ones added in a user's own Settings → Tools are called directly from the browser — that path needs CORS, which is enabled for all origins by default (lock it down with NOTMUCHPROXY_CORS_ORIGINS=https://your-webui-host).

claude.ai (OIDC mode only)

Settings → Connectors → Add custom connector, URL https://your-host/mcp. Claude discovers the OAuth endpoints, registers itself dynamically, and sends you through your IdP's login/consent in the browser. No client id/secret needs to be entered on the claude.ai side.

Claude Code

Static mode:

claude mcp add --transport http notmuch http://your-host:8000/mcp \
  --header "Authorization: Bearer $NOTMUCHPROXY_API_KEY"

OIDC mode — omit the header; Claude Code runs the OAuth flow in your browser:

claude mcp add --transport http notmuch https://your-host/mcp

Other MCP clients

Any client that speaks streamable HTTP can connect to http://your-host:8000/mcp, authenticating with the static bearer token or the OAuth flow depending on the server's configured mode.

Development

Tooling is managed by mise; the notmuch CLI must be on your PATH (it's in every distro's repos).

mise install        # python + uv
mise run install    # create venv, sync deps
mise run test       # run the test suite (builds a throwaway notmuch archive)
mise run check      # ruff lint + format check + pyright (CI mode)
mise run check:fix  # same, but auto-fix what's fixable
mise run dev        # serve on :8000 against generated fixtures (key: dev-key)

Other tasks: mise run fixtures (regenerate the local dev archive), mise run docker:build, mise run docker:test (run the suite inside docker), mise run docker:run. See mise tasks for the full list.

CI runs the same mise tasks, then runs the suite again inside the docker image (against Debian's notmuch rather than the host's) before pushing to ghcr.io/igor47/notmuchproxy on pushes to main and v* tags.

Architecture notes

  • notmuch access: shells out to the notmuch CLI using --format=json output, via a thin wrapper in src/notmuchproxy/notmuch.py. No Python bindings, so there is no libnotmuch version-matching to worry about; the database path is passed via the NOTMUCH_DATABASE environment variable.
  • one definition, two protocols: the FastAPI routes are the source of truth; fastmcp's FastMCP.from_fastapi() converts the OpenAPI schema into MCP tools at startup and dispatches tool calls to the routes in-process.
  • bodies: text/plain parts are preferred; HTML-only messages get a naive tag-stripped rendering. Attachments are listed by filename but not served.
  • fixtures: python -m notmuchproxy.fixtures <dir> generates a small synthetic maildir + notmuch index, used by the tests and mise run dev.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选