关于 JMAP mail
JMAP mail 是可自托管的 MCP 服务,让 AI 助手访问 JMAP 邮件服务器,读取、整理、起草和发送邮件。它根据邮箱地址发现服务器并通过 OAuth 登录,无需手动配置 URL 或令牌。
主要功能
- 01根据邮箱地址发现 JMAP 服务
- 02搜索和读取邮件及会话
- 03整理邮件并管理附件
- 04创建草稿并发送经确认的邮件
- 05管理休假回复和 Sieve 规则
适用场景
- 01按发件人、主题或日期搜索邮箱
- 02移动邮件并更新已读或标记状态
- 03起草带附件的回复
- 04管理自动休假回复和过滤规则
MCP 相关
它通过本地 stdio 或受保护的 HTTP 桥接服务连接 MCP 客户端与 JMAP 邮箱。服务器处理 OAuth 凭据,并向客户端提供邮件工具和状态资源。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
jmap-mcp
一款 MCP 服务,可让 AI 助手(Claude.ai、Claude Code、Claude Desktop 或任意 MCP 客户端)访问托管在 JMAP 服务(RFC 8620、RFC 8621)上的邮箱,首先支持 Stalwart。
用户提供电子邮件地址,并在其邮件服务自己的页面上登录。无需配置其他任何内容:没有服务 URL、令牌或 API 密钥。
为什么选择它
- 自主掌控。 邮件留在你的服务上。此桥接服务可自托管,不存储任何邮件,只保留加密的 OAuth 令牌。
- 零配置。 根据电子邮件地址查找 JMAP 服务(先使用 DNS SRV,再使用
/.well-known/jmap),从标准元数据查找其 OAuth 服务,并动态注册桥接服务。密钥会在首次启动时生成。 - 纯净的 OAuth,不转发令牌。 MCP 客户端获取由桥接服务为其签发的令牌;邮件服务令牌不会离开桥接服务,符合 MCP 授权规范的要求。 将 Client ID 留空时,它可以作为 Claude.ai 自定义连接器使用。
- 支持任意 JMAP 服务。 它并非 Stalwart 专属:任何支持 JMAP,且其 OAuth 服务支持 RFC 8414 元数据和 RFC 7591 动态注册的服务都可以使用。
现有的 JMAP MCP 服务(mikluko/jmap-mcp、wyattjoh/jmap-mcp、agent-jmap-mcp、fastmail-blade-mcp)都需要在配置中提供服务 URL 和静态令牌,而且都无法作为连接器添加到 Claude.ai 中。
工具
为 Agent 设计的精简界面:紧凑的 JSON 结果、全程支持分页、正文转换为文本,并通过明确标志指示是否已截断。
| 工具 | 功能 |
|---|---|
list_accounts | 已连接的账户和默认账户(set_default 可更改默认账户)。 |
connect_account | 仅限本地模式:通过电子邮件地址连接账户(使用浏览器或设备代码)。 |
list_mailboxes | 邮箱及其角色(收件箱、已发送、草稿、垃圾箱、归档、垃圾邮件)、路径、邮件总数和未读数。 |
list_identities | 发件人身份。服务器公布发送功能时注册。 |
search_emails | 按文本、发件人、收件人、主题、邮箱、日期、未读状态、标记状态、附件和大小搜索;支持排序、位置和数量限制。返回摘要。 |
get_email | 标头、纯文本正文(没有文本部分时会将 HTML 转换为文本)、附件列表。支持 max_body_length。 |
get_thread | 按顺序显示对话,可返回摘要或完整邮件。 |
get_attachment | 文本和小型图片直接内嵌;其他附件返回有效期为 15 分钟的签名 URL(HTTP 模式)或私有临时文件(本地模式)。 |
update_emails | 标记为已读、未读、已加标记或已取消标记。 |
move_emails | 将邮件移至通过 ID、角色或名称指定的邮箱。 |
delete_emails | 默认移至垃圾箱;permanent 会在用户确认后永久删除。 |
create_draft | 新建邮件或回复:In-Reply-To、References、Re: 主题、引用内容、回复所有收件人,以及通过 JMAP 上传的附件。 |
send_email | 发送草稿,或撰写并发送邮件。除非启用 --enable-send,否则此功能不可用;每封邮件都需用户确认。 |
vacation_response | 读取或更改外出自动回复(更改需确认)。服务器公布此功能时注册。 |
sieve_scripts | 列出、读取、编写、启用、停用和删除筛选规则(RFC 9661);更改需确认。服务器公布此功能时注册。 |
资源 jmap-mcp://status 描述已连接的账户、发现的服务器及其公布的功能。
电子邮件内容被视为不可信数据:它会作为数据返回给模型,绝不会被解释执行;工具说明也会告知模型,邮件中可能包含必须忽略的指令。
对于非机密选项(选择哪个账户、使用哪个发件人身份,以及确认发送或永久删除),如果客户端支持,系统会通过 MCP elicitation 询问。否则,工具会返回列出选项的错误,模型随后使用设置好的参数(account、identity、confirm)再次调用工具。凭据不会通过 elicitation 或工具参数传递。
工作原理
HTTP mode mail server
┌──────────────┐ OAuth 2.1 (bridge tokens) ┌──────────────┐ OAuth (mail tokens) ┌──────────────┐
│ MCP client │ ──────────────────────────▶ │ jmap-mcp │ ────────────────────▶ │ OAuth server │
│ Claude.ai… │ ◀── MCP over HTTP ────────▶ │ (bridge) │ ◀──── JMAP ─────────▶ │ JMAP API │
└──────────────┘ └──────────────┘ └──────────────┘
在 HTTP 模式(jmap-mcp serve)下,桥接服务同时是:
- 受保护的资源(RFC 9728 元数据、通过
401和WWW-Authenticate响应头发起认证挑战、对每个请求检查受众); - 面向 MCP 客户端的精简授权服务器(RFC 8414 元数据、PKCE S256、RFC 8707 资源指示符、 RFC 7591 动态注册、客户端 ID 元数据 文档、轮换刷新令牌);
- 邮件服务器的 OAuth 客户端:其登录页面会要求用户输入电子邮件地址、发现服务器、 在每台服务器上注册一次,并将用户重定向到邮件服务器的 PKCE 授权码流程。只有邮件 服务器返回后,桥接服务才会发放自己的授权码和令牌。
单个部署可为使用不同邮件服务器的用户提供服务。每位用户都会获得一个隔离的 MCP 服务 实例,且只能访问自己的账户。
在 本地模式(jmap-mcp,stdio)下没有第三方:进程本身就是邮件服务器的 OAuth
客户端,与邮件应用类似。它会注册为公共客户端,在浏览器中打开本地回环重定向地址;
如果无法打开浏览器,则会回退到设备流程(RFC 8628),
并显示需要输入的代码。
安装
1. 在 Traefik 后运行 Docker
镜像为 ghcr.io/kolapsis/jmap-mcp(linux/amd64 和 linux/arm64,distroless,以非 root
用户运行,数据存储在 /data 卷中)。如果 Traefik 已配置 websecure 入口点和 ACME
解析器,请复制 docker-compose.yml,设置主机名、解析器和网络,
然后启动:
curl -O https://raw.githubusercontent.com/kOlapsis/jmap-mcp/main/docker-compose.yml
# edit mcp.example.com, the certresolver name and the network
docker compose up -d
curl https://mcp.example.com/healthz
Traefik 会设置 X-Forwarded-* 响应头,桥接服务据此推导其公开 URL。只有在代理未发送
这些响应头时,才需要设置 JMAP_MCP_PUBLIC_URL。
镜像标签:发布版本使用 X.Y.Z、X.Y、X 和 latest;主分支的最新构建使用
main 和 sha-<commit>。两种镜像均由 GitHub Actions 构建并推送到 ghcr.io。
不使用 Traefik 时,deploy/caddy 中提供了可自行获取证书的 Caddy
配置:
cd deploy/caddy # edit mcp.example.com in the Caddyfile
docker compose up -d
MCP 地址为 https://mcp.example.com/mcp。
2. 添加到 Claude.ai
设置 › 连接器 › 添加自定义连接器。为连接器命名,并填写 URL
https://mcp.example.com/mcp。在高级设置中,将 客户端 ID 和 客户端密钥 留空:
Claude.ai 会自行动态注册(其重定向 URI 为
https://claude.ai/api/mcp/auth_callback)。点击“连接”,在桥接服务页面输入电子邮件地址,
然后在邮件服务器上登录。
免费套餐仅限添加一个自定义连接器。Team 和 Enterprise 套餐的所有者可在“组织设置” › “连接器”下添加连接器。
3. 使用远程桥接服务配置 Claude Code
claude mcp add --transport http mail https://mcp.example.com/mcp
然后在 Claude Code 中运行 /mcp,并选择服务器,在浏览器中完成身份验证。
4. Claude Code 和 Claude Desktop,本地模式(stdio)
从发布页面下载二进制文件,通过
Homebrew(brew install --cask kolapsis/tap/jmap-mcp,适用于 macOS 和 Linux)或通过 Go 安装:
go install github.com/kOlapsis/jmap-mcp@latest
Claude Code:
claude mcp add mail -- jmap-mcp
或者在 .mcp.json 中:
{
"mcpServers": {
"mail": { "command": "jmap-mcp", "args": ["stdio"] }
}
}
Claude Desktop:从发布页面安装 jmap-mcp.mcpb(双击该文件,或前往 Settings ›
Extensions),或者在 claude_desktop_config.json 中添加二进制文件的完整路径:
{
"mcpServers": {
"mail": { "command": "/usr/local/bin/jmap-mcp", "args": ["stdio"] }
}
}
首次使用时,请让助手连接你的邮箱。它会询问你的电子邮件地址(通过 elicitation 或 connect_account 工具),在浏览器中打开邮件服务器的登录页面,随后即可使用该账户。没有图形会话时(SSH),它会显示一个 URL 和一段代码,你可以在任意设备上输入代码。令牌存储在数据目录中(Linux 上为 ~/.config/jmap-mcp),使用保存在操作系统密钥环中的密钥加密;如果没有可用的密钥环,则使用权限为 0600 的密钥文件加密。可以连接多个账户。
若要在本地模式下发送邮件,请在服务条目中添加 "env": {"JMAP_MCP_ENABLE_SEND": "true"}。
5. 使用 systemd 运行二进制文件
sudo install -m 755 jmap-mcp /usr/local/bin/
sudo cp deploy/systemd/jmap-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now jmap-mcp
该单元使用动态用户,在 127.0.0.1:8080 上侦听,并将状态保存在 /var/lib/jmap-mcp 中。在前面配置 TLS 反向代理(任何会发送 X-Forwarded-Proto 和 X-Forwarded-Host 的代理均可),或设置 JMAP_MCP_PUBLIC_URL。
准备邮件服务器
发现。 按照 RFC 8620 §2.2 的说明,通过电子邮件域名查找 JMAP 服务:查找指向 JMAP 主机的 SRV 记录 _jmap._tcp.example.com;若不存在,则使用 https://example.com/.well-known/jmap。如果你的网站并非由邮件服务器提供服务,请添加 SRV 记录:
_jmap._tcp.example.com. 3600 IN SRV 0 1 443 mail.example.com.
OAuth 服务通过 JMAP 主机查找:依次使用 RFC 9728 资源元数据、RFC 8414 和 OpenID Connect 发现机制。它必须与 JMAP 主机位于同一可注册域名下(例如 jmap.example.com 和 auth.example.com);否则,桥接程序会拒绝该服务。
使用 Stalwart 注册客户端。 使用默认设置时无需进行任何操作:桥接程序会通过 PKCE 将自身注册为公共客户端,Stalwart 支持匿名注册(anonymousClientRegistration,默认在 Stalwart 0.16 中启用)。Stalwart 只会向经过身份验证的调用方签发机密客户端,因此桥接程序无法通过此方式获取客户端密钥;授权码改由 PKCE 保护。
如果运营者关闭了匿名注册,请静态声明客户端:
- 在 Stalwart 中,前往 Management › Directory › OAuth Clients,创建一个客户端,并设置重定向 URI 为
https://mcp.example.com/callback(如果需要机密客户端,也可设置密钥)。 - 获取桥接程序的管理员令牌:
docker compose exec jmap-mcp /jmap-mcp admin-token(或jmap-mcp admin-token --data-dir /var/lib/jmap-mcp)。 - 打开
https://mcp.example.com/admin,使用任意用户名和该令牌作为密码登录,然后输入邮件服务器(电子邮件地址或其 URL)、客户端 ID、密钥和身份验证方法。它们会加密后存储在桥接程序数据库中。
本地模式需要动态注册公共客户端,这是 Stalwart 的默认设置。
配置
所有选项都是可选的,且仅与部署有关。每个标志也可以通过环境变量 JMAP_MCP_<FLAG> 设置(例如 JMAP_MCP_ENABLE_SEND=true)。
| 标志 | 默认值 | 用途 |
|---|---|---|
--listen | :8080 | HTTP 监听地址(serve)。 |
--data-dir | $XDG_CONFIG_HOME/jmap-mcp(镜像中为 /data) | 数据库和主密钥。 |
--public-url | 根据 X-Forwarded-Proto、X-Forwarded-Host、X-Forwarded-Prefix 确定 | 桥接服务的公共基础 URL。 |
--log-level | info | debug、info、warn 或 error。日志中绝不会包含机密、验证码或邮件正文。 |
--enable-send | false | 注册 send_email。 |
--allow-private-networks | false | 允许邮件服务器使用私有或环回地址(例如位于同一 LAN 上)。这会停用部分 SSRF 防护。 |
命令:jmap-mcp 或 jmap-mcp stdio(本地模式)、jmap-mcp serve(HTTP 模式)、jmap-mcp admin-token、jmap-mcp healthcheck、jmap-mcp version。
暴露的端点
以下所有端点都必须通过反向代理访问,且使用相同的主机名。
| 路径 | 用途 |
|---|---|
/mcp | MCP Streamable HTTP 端点(需要 bearer token)。 |
/.well-known/oauth-protected-resource、/.well-known/oauth-protected-resource/mcp | 受保护资源元数据(RFC 9728)。 |
/.well-known/oauth-authorization-server、/.well-known/oauth-authorization-server/mcp | 授权服务器元数据(RFC 8414)。 |
/register | 动态客户端注册(RFC 7591)。 |
/authorize | 授权端点及用于输入电子邮件地址的页面。 |
/callback | 从邮件服务器登录流程返回。 |
/token | 令牌端点(授权码、刷新令牌)。 |
/revoke | 令牌撤销(RFC 7009)。 |
/attachments/{token} | 有签名且短时有效的附件下载。 |
/admin | 静态客户端声明(需要管理员令牌)。 |
/healthz | 健康检查。 |
/ | 简短说明页面。 |
安全性
- 只接受由桥接服务签发的令牌,且令牌只能用于其自身的资源 URL;邮件服务器令牌绝不会离开桥接服务。签发的令牌是不透明的随机值,以带密钥的哈希形式存储。访问令牌有效期为一小时;刷新令牌每次使用后都会轮换,且仅适用于对应客户端,重用旧令牌会撤销整个令牌链。重放授权码会撤销由该授权码签发的令牌。
- 必须使用 PKCE S256。重定向 URI 必须为 HTTPS、环回 HTTP 或私有用途方案,并且必须与注册信息完全匹配(环回地址的端口可以不同)。
state会原样返回,并会发送和检查iss参数(RFC 9207)。在邮件服务器这一侧,会检查state、iss和 ID 令牌的nonce,并通过 Cookie 将流程绑定到浏览器。 - JMAP 主机只能将用户引导至其自身可注册域名下的授权服务器,因此恶意域名无法收集由其他邮件服务器签发的令牌。用户身份由签发方和 ID 令牌的
sub确定(会检查 nonce、签发方和受众),而非由 JMAP 服务器报告的信息确定。邮件令牌只会发送到请求它们的主机,绝不会在重定向到其他主机后发送。 - 每个出站请求(发现、OAuth、JMAP、客户端元数据文档)都会经过防护机制:仅接受 HTTPS,在连接时解析域名并拒绝私有、环回和链路本地地址,同时限制重定向次数。
- 首次启动时会生成密钥:在数据目录中生成主密钥(或在本地模式下存入操作系统密钥环),并据此派生数据库加密密钥(AES-GCM)、令牌哈希密钥和附件 URL 密钥。邮件服务器令牌和客户端密钥会以加密形式静态存储。
- OAuth 端点会按客户端 IP 进行速率限制;只有请求来自私有地址(即你的反向代理)时,才会信任
X-Forwarded-For。HTML 页面不使用 JavaScript,并设置严格的 Content Security Policy、X-Frame-Options: DENY和Referrer-Policy: no-referrer。附件始终以下载形式提供(application/octet-stream,sandbox CSP)。 - 默认禁用发送邮件;启用后,每封邮件(完整正文和附件名称)、永久删除、Sieve 更改和外出通知更改都必须经用户确认,且批准仅适用于所显示的确切内容。在本地模式下,如果账户地址来自模型,连接该账户也必须经过确认。对于无法征求确认的客户端,模型必须先询问用户,再按规范传入
confirm=true;这类客户端应保持启用工具审批,因为被提示词注入的模型可能会设置该参数。
请按照 SECURITY.md 中的说明报告漏洞。
数据
数据目录包含 master.key(除非使用操作系统密钥环)和 jmap-mcp.db(SQLite)。请一并备份两者:没有密钥就无法解密数据库。删除它们会断开所有账户;用户只需重新登录。
开发
go test ./... # unit tests, including replayed Stalwart exchanges
go test -tags integration ./... # starts Stalwart in Docker
golangci-lint run ./...
集成测试会启动 stalwartlabs/stalwart,通过其 API 完成设置、创建账户、导入示例邮件,并运行真实的 OAuth 流程(浏览器、设备、使用 MCP SDK 客户端的 HTTP 桥接)。单元测试会重放这些运行过程中记录的 JMAP 交互;使用 JMAP_MCP_RECORD=$PWD/internal/tools/testdata/read go test -tags integration -run TestStalwartReadTools ./internal/tools 刷新记录(以及 write / TestStalwartWriteTools)。
目录结构:internal/jmapc(JMAP 客户端和发现)、internal/mailauth(邮件服务器的 OAuth 客户端)、internal/mailview(精简邮件视图)、internal/tools(MCP 工具)、internal/stdio 和 internal/httpd(两种模式)、internal/store(加密的 SQLite)、internal/netguard(SSRF 防护)。
另请参阅 CONTRIBUTING.md。
JMAP 库的选择
| 库 | 评估 |
|---|---|
git.sr.ht/~rockorager/go-jmap | aerc 使用的库,涵盖核心功能、邮件和推送。最近一次发布为 2025 年 2 月的 v0.5.3,不支持 Sieve。 |
github.com/mikluko/jmap | 上述库的维护分支(v0.26.0,2026 年 2 月):覆盖范围相同,包括 EmailSubmission、Identity 和 VacationResponse,并支持 Sieve 脚本(RFC 9661)。已选用。 |
github.com/rhyselsmore/go-jmap | 较新且设计良好,但声明为“尚不可用”,且不支持 EmailSubmission 或 Identity。 |
mikluko/jmap 提供方法和对象类型。传输层在不分叉的情况下于
internal/jmapc 中补全:其上传会发送错误的 Content-Type,下载会将名称和类型写死,
而会话解码遇到其不认识的能力时会失败。
决策
在根据 2026 年 10 月的实际软件进行核对后,发现初始规范不准确或必须作出选择的事项:
- Stalwart 客户端注册。 Stalwart 参考文档将
anonymousClientRegistration的默认值设为true(另一页面则称为false),而 Stalwart 0.16.27 接受匿名注册,但仅限公共客户端:机密客户端始终需要具有oauthClientRegistration权限的已认证调用方。因此,桥接程序会请求注册机密客户端,失败时回退到使用 PKCE 的公共客户端,并提供/admin页面供静态声明的客户端使用。 - 查找 Stalwart 的 OAuth 服务端。 Stalwart 会匿名提供 JMAP 会话(HTTP 200),仅在调用 API 时要求进行身份验证;此外,根据电子邮件域名找到的会话主机可能与 OAuth 颁发者不同(
example.com与mail.example.com)。桥接程序会先读取会话源站上的 RFC 9728 文档,再尝试在那里进行 RFC 8414 和 OpenID 发现。 - 使用 MCP 2026-07-28 进行征询。 Go SDK v1.8.0 实现了 2026-07-28 协议,该协议禁止在工具调用期间由服务端发起征询请求:服务端必须在结果中返回问题,由客户端重试(SEP-2322,多轮往返请求)。这些工具使用此机制,并在旧版本客户端上回退到经典的
elicitation/create。 - 注册表名称。 注册表会按 GitHub 用户名的确切大小写授予
io.github.<owner>/*,并区分大小写地比较名称,因此服务端发布为io.github.kOlapsis/jmap-mcp,而不是io.github.kolapsis/jmap-mcp。 - 注册表中的二进制文件。 注册表没有普通二进制包类型。二进制文件以 MCP bundle(
.mcpb:macOS 通用版和 Windows x64 版,适用于 Claude Desktop)的形式发布;镜像则以 OCI 软件包的形式发布(本地模式使用持久卷,并通过设备授权流程登录)。 - 默认账户。 每次调用都询问要使用哪个账户,会导致多个账户无法使用。当已连接多个账户且未设置默认账户时,只询问一次账户,并将答案设为默认账户(
list_accounts会更改默认账户)。 - 本地令牌存储。 本地模式使用与 HTTP 模式相同的加密 SQLite 存储;操作系统密钥环保存主密钥,而不是逐个保存令牌。
- 签发的令牌。 不透明令牌以带密钥的哈希形式存储,而非签名 JWT:这样可以撤销令牌,并检测刷新令牌重用。
- Sieve 和外出自动回复。 编写或启用筛选规则可能会转发或丢弃邮件,而自动回复会向所有发件人发送文本:两者都需要与发送邮件相同的确认。
- 同域授权服务器。 如果允许 JMAP 主机的元数据指向任意授权服务器,恶意域名就能接收由其他服务器签发的令牌(并在 HTTP 模式下冒充其他用户)。JMAP 主机和颁发者必须共享同一个可注册域名,用户身份则取自 ID 令牌。因此,JMAP 主机与 OAuth 服务端位于互不相关域名上的提供方不受支持。
发布
每次推送到 main 都会构建镜像并推送到 ghcr.io/kolapsis/jmap-mcp:main(工作流
image.yml)。维护者通过推送 vX.Y.Z 标签来发布版本。发布工作流会运行 GoReleaser(归档文件、校验和、SBOM、带有 semver 和 latest 标签的 GHCR 多架构镜像、推送到
kOlapsis/homebrew-tap 的 Homebrew cask,部署密钥存储在
HOMEBREW_TAP_DEPLOY_KEY 密钥中),构建并上传 jmap-mcp.mcpb,然后使用 GitHub OIDC 将
server.json 发布到 MCP 注册表。
一次性设置:首次发布后,将组织的 GitHub packages 中的 jmap-mcp 软件包设为公开(注册表只验证公开镜像),然后重新运行
mcp-registry 作业。设置仓库主题,以便 Glama 等目录能够找到它:
gh repo edit kOlapsis/jmap-mcp --description "MCP server giving AI assistants OAuth access to JMAP mailboxes (Stalwart and others)" \
--homepage https://github.com/kOlapsis/jmap-mcp --add-topic mcp,mcp-server,jmap,stalwart,email
许可证
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。