
JMAP mail
io.github.kOlapsis由 GitHub 帳號 kOlapsis 發布v0.3.0更新於 Oct 25, 2026
Read, organize, draft and send email on any JMAP server, with OAuth sign-in and zero configuration
概覽
讓助理連接 JMAP 信箱,可搜尋、閱讀、整理、起草與寄送郵件,透過 OAuth 登入且無需手動設定伺服器。
- 可以做什麼
- 搜尋與閱讀郵件:依寄件者、主旨、信箱、日期、標記與大小檢索,讀取單封郵件或整個對話,並查看附件。
- 整理郵件:標記已讀或加旗標、在信箱間移動、建立資料夾,以及移至垃圾桶或永久刪除。
- 起草與寄送:撰寫新郵件或含引用的回覆並加入附件,在啟用寄送功能後可寄出。
- 管理帳號與信箱:列出已連接帳號並設定預設帳號,列出信箱與計數,列出寄件身分。
- 伺服器端規則:在伺服器宣告支援時,讀取或修改休假自動回覆,並管理 Sieve 篩選腳本。
- 適用情境
- 適合讓助理在 JMAP 伺服器(例如 Stalwart)上的既有信箱中工作,用於分類、檢索、彙整對話、起草回覆或歸檔郵件,而不必提供靜態 API 權杖。
- 執行需求
- 需要一個具備支援標準中繼資料與動態用戶端註冊之 OAuth 伺服器的 JMAP 郵件伺服器,以及用來登入的電子郵件地址。可作為本機程序執行(二進位檔、Homebrew、Go 或 MCP 套件),也可自架為位於 TLS 反向代理之後的 Docker/HTTP 橋接服務。寄送郵件需將 JMAP_MCP_ENABLE_SEND 設為 true。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 JMAP mail,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
jmap-mcp
[CI] [Release] [Go Reference] [License]
An MCP server that gives an AI assistant (Claude.ai, Claude Code, Claude Desktop or any MCP client) access to a mailbox hosted on a JMAP server (RFC 8620, RFC 8621), first of all Stalwart.
The user gives an email address and signs in on their mail server's own page. Nothing else is configured: no server URL, no token, no API key.
Why
- Sovereignty. Your mail stays on your server. The bridge is self-hosted, stores no email, and keeps only encrypted OAuth tokens.
- Zero configuration. The JMAP server is found from the email address (DNS SRV, then
/.well-known/jmap), its OAuth server from standard metadata, and the bridge registers itself dynamically. Secrets are generated on first start. - Clean OAuth, no token passthrough. MCP clients get tokens issued by the bridge for the bridge; mail-server tokens never leave it, as the MCP authorization spec requires. It works as a Claude.ai custom connector with the Client ID left empty.
- Any JMAP server. Nothing is Stalwart-specific: any server with JMAP and an OAuth server supporting RFC 8414 metadata and RFC 7591 dynamic registration works.
Existing JMAP MCP servers (mikluko/jmap-mcp, wyattjoh/jmap-mcp, agent-jmap-mcp, fastmail-blade-mcp) all need a server URL and a static token in their configuration, and none can be added to Claude.ai as a connector.
Tools
A small surface designed for an agent: compact JSON results, pagination everywhere, bodies converted to text and truncated with an explicit flag.
The resource jmap-mcp://status describes the connected accounts, the discovered servers and the
capabilities they announce.
Email content is treated as untrusted data: it is returned to the model as data, never interpreted, and the tool descriptions tell the model that emails may contain instructions it must ignore.
Choices that are not secret (which account, which sender identity, confirmation of a send or a
permanent deletion) are asked through MCP elicitation when the client supports it. Otherwise the
tool returns an error listing the choices, and the model calls it again with the argument set
(account, identity, confirm). Credentials never go through elicitation or tool arguments.
How it works
In HTTP mode (jmap-mcp serve) the bridge is at the same time:
- a protected resource (RFC 9728 metadata,
401withWWW-Authenticate, audience checked on every request); - a minimal authorization server for MCP clients (RFC 8414 metadata, PKCE S256, RFC 8707 resource indicators, RFC 7591 dynamic registration, Client ID Metadata Documents, rotating refresh tokens);
- an OAuth client of the mail server: its sign-in page asks for the email address, discovers the server, registers itself there once per server, and redirects the user to the mail server's authorization code flow with PKCE. Only after the mail server returns does the bridge issue its own code and tokens.
A single deployment serves users of different mail servers. Each user gets an isolated MCP server instance and can only reach their own accounts.
In local mode (jmap-mcp, stdio) there is no third party: the process is the OAuth client of
the mail server, like a mail application. It registers as a public client, opens the browser on a
loopback redirect, and falls back to the device flow (RFC 8628)
when no browser can be opened, showing the code to type.
Installation
1. Docker behind Traefik
The image is ghcr.io/kolapsis/jmap-mcp (linux/amd64 and linux/arm64, distroless, non-root, data
in the /data volume). With a Traefik that already has a websecure entrypoint and an ACME
resolver, copy docker-compose.yml, set your host name, resolver and network,
and start it:
Traefik sets the X-Forwarded-* headers from which the bridge derives its public URL. Set
JMAP_MCP_PUBLIC_URL only if your proxy does not send them.
Image tags: X.Y.Z, X.Y, X and latest for releases, main and sha-<commit> for the
latest build of the main branch. Both are built by GitHub Actions and pushed to ghcr.io.
Without Traefik, deploy/caddy has a Caddy setup that obtains the certificate
itself:
The MCP address is https://mcp.example.com/mcp.
2. Add it to Claude.ai
Settings › Connectors › Add custom connector. Give it a name and the URL
https://mcp.example.com/mcp. Leave Client ID and Client Secret empty in the advanced
settings: Claude.ai registers itself dynamically (its redirect URI is
https://claude.ai/api/mcp/auth_callback). Click Connect, enter your email address on the bridge
page, then sign in on your mail server.
The free plan is limited to one custom connector. On Team and Enterprise plans, an owner adds the connector under Organization settings › Connectors.
3. Claude Code with the remote bridge
Then run /mcp in Claude Code and choose the server to authenticate in the browser.
4. Claude Code and Claude Desktop, local (stdio)
Install the binary, from the releases, with
Homebrew (brew install --cask kolapsis/tap/jmap-mcp, macOS and Linux) or with Go:
Claude Code:
or in .mcp.json:
Claude Desktop: install jmap-mcp.mcpb from the release (double-click it, or Settings ›
Extensions), or add to claude_desktop_config.json the full path of the binary:
On first use, ask the assistant to connect your mailbox. It asks for your email address
(elicitation, or the connect_account tool), opens your browser on the mail server's sign-in page,
and the account is ready. Without a graphical session (SSH), it shows a URL and a code to enter on
any device. Tokens are stored in the data directory (~/.config/jmap-mcp on Linux), encrypted with
a key kept in the OS keyring, or in a 0600 key file when no keyring is available. Several accounts
can be connected.
To send email in local mode, add "env": {"JMAP_MCP_ENABLE_SEND": "true"} to the server entry.
5. Binary with systemd
The unit listens on 127.0.0.1:8080 with a dynamic user and its state in /var/lib/jmap-mcp. Put
a TLS reverse proxy in front of it (any proxy that sends X-Forwarded-Proto and
X-Forwarded-Host), or set JMAP_MCP_PUBLIC_URL.
Preparing the mail server
Discovery. The JMAP server is found from the email domain, as RFC 8620 §2.2 describes: an SRV
record _jmap._tcp.example.com pointing at the JMAP host, otherwise
https://example.com/.well-known/jmap. If your web site is not served by the mail server, add the
SRV record:
The OAuth server is found from the JMAP host: RFC 9728 resource metadata, then RFC 8414 and OpenID
Connect discovery. It must be under the same registrable domain as the JMAP host (for example
jmap.example.com and auth.example.com); otherwise the bridge refuses the server.
Client registration with Stalwart. Nothing to do with the default settings: the bridge
registers itself as a public client with PKCE, which Stalwart accepts anonymously
(anonymousClientRegistration, on by default in Stalwart 0.16). Stalwart only issues confidential
clients to authenticated callers, so the bridge does not get a client secret this way; PKCE protects
the authorization code instead.
If the operator turned anonymous registration off, declare the client statically:
- In Stalwart, Management › Directory › OAuth Clients, create a client with the redirect URI
https://mcp.example.com/callback(and a secret if you want a confidential client). - Get the admin token of the bridge:
docker compose exec jmap-mcp /jmap-mcp admin-token(orjmap-mcp admin-token --data-dir /var/lib/jmap-mcp). - Open
https://mcp.example.com/admin, log in with any user name and the token as password, and enter the mail server (an email address or its URL), the client ID, the secret and the authentication method. They are stored encrypted in the bridge database.
The local mode registers itself as a public client, which Stalwart allows by default. When the
authorization server does not allow it, declare a client with jmap-mcp client add (see the next
section).
Mail server delegating to an external identity provider
Some JMAP servers leave sign-in to an OpenID provider, for example Stalwart with an OIDC directory behind Authentik. jmap-mcp then signs the user in at that provider and presents the provider's access token to the JMAP server.
Discovery. jmap-mcp finds the JMAP session from the email domain (SRV record or
/.well-known/jmap), then reads the RFC 9728 metadata of the JMAP host at
/.well-known/oauth-protected-resource: its authorization_servers must name the provider's
issuer. The provider's own metadata comes from RFC 8414 or OpenID Connect discovery. Stalwart 0.16
keeps naming itself in these metadata even with an OIDC directory, so have the reverse proxy answer
/.well-known/oauth-protected-resource and its subpaths with the issuer of the provider. The
provider must be under the same registrable domain as the JMAP host (mail.example.com and
auth.example.com).
Scopes and audience. jmap-mcp requests openid, email and offline_access when the provider
advertises them, and the JMAP mail scope when it is advertised (providers usually do not have it).
The JMAP server finds the account from the email claim (claimUsername: email in Stalwart). The
access token must also carry the audience the JMAP server expects (requireAudience in Stalwart):
use the client ID the JMAP server is configured for, otherwise the server refuses the token.
Static client. Identity providers rarely offer dynamic client registration. Declare jmap-mcp on
the provider as a public client with PKCE S256 and the authorization_code and refresh_token
grants, with the redirect address of each mode you use:
-
HTTP mode:
https://mcp.example.com/callback. Then enter the client on the/adminpage of the bridge, with the authentication methodnonefor a public client. -
Local mode: the loopback address on any port (RFC 8252),
http://127.0.0.1:<port>/callback. Providers usually accept it as a regular expression, for example^http://127[.]0[.]0[.]1:[0-9]+/callback$. Then run:The command accepts an email address or the JMAP session URL, finds the provider and stores the client in the data directory; a running server uses it at the next
connect_account. For a confidential client, add--client-secret(or setJMAP_MCP_CLIENT_SECRET) and, if needed,--auth-method client_secret_post(the default with a secret isclient_secret_basic).
As long as no client is declared, the sign-in stops with a message that names the provider and gives these steps.
Example with Authentik. Stalwart's OIDC directory points at the Authentik application
stalwart-webui (issuerUrl: https://auth.example.com/application/o/stalwart-webui/,
requireAudience: stalwart-webui, claimUsername: email). This application is a public client
with PKCE and the scopes openid, email, profile and offline_access, and its tokens carry the
audience Stalwart expects, so jmap-mcp reuses it: add the redirect addresses above to it, then
declare the client ID stalwart-webui with the method none. Authentik grants consent implicitly,
so once the loopback pattern is allowed, any program on a user's computer can obtain a token for
that user without them noticing; allow it only if the local mode is needed.
Configuration
Every option is optional and only concerns the deployment. Each flag can also be set with the
environment variable JMAP_MCP_<FLAG> (for example JMAP_MCP_ENABLE_SEND=true).
Commands: jmap-mcp or jmap-mcp stdio (local mode), jmap-mcp serve (HTTP mode),
jmap-mcp client add (static client for the local mode), jmap-mcp admin-token,
jmap-mcp healthcheck, jmap-mcp version.
Exposed endpoints
Everything below must be reachable through the reverse proxy, on the same host name.
Security
- Only tokens issued by the bridge are accepted, and only for its own resource URL; mail-server tokens never leave the bridge. Issued tokens are opaque random values stored as keyed hashes. Access tokens last one hour; refresh tokens rotate on every use and only work for their client, and reusing an old one revokes the whole chain. Replaying an authorization code revokes the tokens issued from it.
- PKCE S256 is mandatory. Redirect URIs must be HTTPS, loopback HTTP or a private-use scheme, and
match the registration exactly (any port for loopback).
stateis echoed and theissparameter (RFC 9207) is sent and checked. On the mail-server leg,state,issand thenonceof the ID token are checked, and the flow is bound to the browser with a cookie. - A JMAP host can only send users to an authorization server of its own registrable domain, so a
hostile domain cannot collect tokens issued by another mail server. The user is identified by the
issuer and the
subof the ID token (nonce, issuer and audience checked), not by what the JMAP server reports. Mail tokens are only sent to the host they were requested for, never after a redirect to another host. - Every outbound request (discovery, OAuth, JMAP, client metadata documents) goes through a guard that accepts only HTTPS, resolves the name and refuses private, loopback and link-local addresses at connection time, and limits redirects.
- Secrets are generated on first start: a master key in the data directory (or the OS keyring in local mode), from which the database encryption key (AES-GCM), the token hashing key and the attachment URL key are derived. Mail-server tokens and client secrets are encrypted at rest.
- The OAuth endpoints are rate limited per client IP;
X-Forwarded-Foris only trusted when the request comes from a private address, that is from your reverse proxy. HTML pages use no JavaScript and send a strict Content Security Policy,X-Frame-Options: DENYandReferrer-Policy: no-referrer. Attachments are always served as downloads (application/octet-stream, sandbox CSP). - Sending email is off by default; when on, every message (full body and attachment names),
permanent deletion, Sieve change and out-of-office change is confirmed by the user, and an
approval only covers the exact content it showed. In local mode, connecting an account whose
address came from the model is confirmed too. With clients that cannot elicit, the model must pass
confirm=trueafter asking the user, as the specification requires; keep tool approval enabled in such clients, since a prompt-injected model could set it.
Report vulnerabilities as described in SECURITY.md.
Data
The data directory holds master.key (unless the OS keyring is used) and jmap-mcp.db (SQLite).
Back up both together: the database cannot be decrypted without the key. Deleting them disconnects
every account; users simply sign in again.
Development
The integration tests start stalwartlabs/stalwart, complete its setup through its API, create
accounts, import sample messages and run the real OAuth flows (browser, device, HTTP bridge with
the MCP SDK client). They also switch Stalwart to an OIDC directory backed by a minimal OpenID
provider (internal/testenv/idp.go) to cover the static client of both modes. The unit tests replay JMAP exchanges recorded from those runs; refresh them
with JMAP_MCP_RECORD=$PWD/internal/tools/testdata/read go test -tags integration -run TestStalwartReadTools ./internal/tools
(and write / TestStalwartWriteTools).
The layout: internal/jmapc (JMAP client and discovery), internal/mailauth (OAuth client of the
mail server), internal/mailview (compact email views), internal/tools (MCP tools),
internal/stdio and internal/httpd (the two modes), internal/store (encrypted SQLite),
internal/netguard (SSRF guard).
See CONTRIBUTING.md.
Choice of JMAP library
mikluko/jmap provides the method and object types. The transport is completed in
internal/jmapc without forking: its upload sends the wrong Content-Type, its download hardcodes
the name and type, and its session decoding fails on capabilities it does not know.
Decisions
Points where the initial specification turned out to be inaccurate, or where a choice had to be made, checked against the actual software in October 2026:
- Stalwart client registration. The Stalwart reference gives
anonymousClientRegistrationa default oftrue(another page saysfalse), and Stalwart 0.16.27 accepts anonymous registration, but only of public clients: a confidential client always needs an authenticated caller with theoauthClientRegistrationpermission. The bridge therefore asks for a confidential client, falls back to a public client with PKCE, and offers the/adminpage for a statically declared client. - Finding Stalwart's OAuth server. Stalwart serves the JMAP session anonymously (HTTP 200) and
only challenges API calls, and the session host found from the email domain can differ from the
OAuth issuer (
example.comversusmail.example.com). The bridge reads the RFC 9728 document at the session origin before trying RFC 8414 and OpenID discovery there. - Elicitation with MCP 2026-07-28. Go SDK v1.8.0 implements the 2026-07-28 protocol, which
forbids server-initiated elicitation requests during a tool call: the server must return the
questions in the result and the client retries (SEP-2322, multi round-trip requests). The tools
use that mechanism and fall back to classic
elicitation/createfor clients on older versions. - Registry name. The registry grants
io.github.<owner>/*with the exact case of the GitHub owner and compares names case-sensitively, so the server is published asio.github.kOlapsis/jmap-mcp, notio.github.kolapsis/jmap-mcp. - Binary in the registry. The registry has no plain binary package type. The binary is
published as an MCP bundle (
.mcpb: macOS universal and Windows x64, for Claude Desktop) and the image as an OCI package (local mode with a persistent volume, signing in with the device flow). - Default account. Asking which account to use on every call would make several accounts
unusable. The account is asked once, when several are connected and none is the default, and
the answer becomes the default (
list_accountschanges it). - Local token storage. The local mode uses the same encrypted SQLite store as the HTTP mode; the OS keyring holds the master key rather than each token.
- Issued tokens. Opaque tokens stored as keyed hashes rather than signed JWTs: they can be revoked, and refresh token reuse can be detected.
- Sieve and out-of-office. Writing or activating a filtering rule can redirect or drop mail, and an automatic reply sends text to everyone who writes: both require the same confirmation as sending.
- Same-domain authorization server. Following a JMAP host's metadata to any authorization server would let a hostile domain receive tokens issued by another server (and, in HTTP mode, claim another user's identity). The JMAP host and the issuer must share their registrable domain, and the user identity comes from the ID token. A provider whose JMAP host and OAuth server sit on unrelated domains is therefore not supported.
Releasing
Every push to main builds the image and pushes ghcr.io/kolapsis/jmap-mcp:main (workflow
image.yml). Maintainers publish a version by pushing a vX.Y.Z tag. The release workflow runs GoReleaser (archives, checksums, SBOMs,
multi-arch image on GHCR with semver and latest tags, Homebrew cask pushed to
kOlapsis/homebrew-tap with the deploy key stored in the
HOMEBREW_TAP_DEPLOY_KEY secret), builds and uploads jmap-mcp.mcpb, then publishes server.json to the MCP registry with
GitHub OIDC.
One-time setup: make the jmap-mcp package public in the organization's GitHub packages after the
first release (the registry only verifies public images), then re-run the mcp-registry job. Set
the repository topics so that directories such as Glama find it:
License
來源:README.md,提交 7b38c57
工具
0版本歷史
3- v0.3.0最新Oct 25, 2026
- v0.2.0Oct 24, 2026
- v0.1.1Oct 23, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分