关于 che-apple-mail-mcp
che-apple-mail-mcp 是用于操作 Apple Mail 的 Swift MCP 服务,支持搜索、邮件操作、草稿和邮箱管理。它采用 SQLite 搜索路径并支持批量操作,适用于处理大量邮件。
主要功能
- 01基于 SQLite 的邮件搜索
- 02按主题、发件人、收件人和日期搜索
- 03每次最多批量处理 50 封邮件
- 04管理邮箱、规则和 VIP 发件人
- 05撰写、回复、转发邮件及保存草稿
- 06读取邮件头、原始内容和附件
适用场景
- 01搜索大型 Apple Mail 邮件归档
- 02批量移动或标记邮件
- 03准备回复和草稿供人工审核
- 04检查邮件头或保存附件
MCP 相关
向 MCP 客户端提供 Apple Mail 账户、邮箱、邮件撰写、附件及规则操作。README 介绍了 54 个 MCP 工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
我们还没有获取到这个服务的安装包或远程地址,请按项目 README 中的说明安装。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
che-apple-mail-mcp
功能最全面的 Apple Mail MCP 服务 - 提供 54 个工具,借助 SQLite 实现毫秒级搜索,可搜索超过 25 万封邮件。
为什么选择 che-apple-mail-mcp?
| 功能 | 其他 MCP | che-apple-mail-mcp |
|---|---|---|
| 工具总数 | 约 20 个 | 54 个 |
| 语言 | Python | Swift(原生) |
| 搜索速度 | 数秒(AppleScript) | 毫秒级(SQLite) |
| 搜索字段 | 主题/发件人 | 主题/发件人/收件人/日期 |
| 批量操作 | 不支持 | 每次最多处理 50 封邮件 |
| 邮箱管理 | 基础功能 | 完整的 CRUD |
| 邮件颜色 | 不支持 | 7 种旗标颜色 + 背景色 |
| VIP 管理 | 不支持 | 支持 |
| 规则管理 | 部分支持 | 完整的 CRUD |
| 签名 | 不支持 | 支持 |
| 原始标头/源代码 | 不支持 | 支持 |
快速开始
安装插件。它会一并提供已签名的二进制文件、/archive-mail 命令系列、安全规则和过期检测钩子:
claude plugin marketplace add PsychQuant/che-apple-mail-mcp
claude plugin install che-apple-mail-mcp@che-apple-mail-mcp
然后授予权限——设置窗口会显示实时状态,并提供直达相应系统设置面板的链接:
~/bin/CheAppleMailMCP --setup
💡 完全磁盘访问权限是启用快速 SQLite 读取路径和
batch_export_emails_markdown的关键。没有此权限,工具仍会运行,但读取到的内容很少甚至没有,容易让人误以为是程序出错,而非权限问题。macOS 不允许应用以编程方式请求 FDA——必须手动勾选——设置窗口正是为了让这一操作更快捷。
插件与仅 MCP 模式
单独注册 MCP 服务是受支持的进阶用法,但安装内容严格来说更少。请先了解再选择——运行时不会告知你缺少以下内容(#353):
| 插件提供的内容 | 仅 MCP 模式下的情况 |
|---|---|
| 全部 54 个 MCP 工具 | ✅ 有 |
/archive-mail + -migrate / -rebuild-threads / -repair-synthetic-ids / -view | ❌ 不包含邮件归档 SOP |
rules/compose-wrapper-free.md — cite-block 的作用,以及 compose 调用被拒绝意味着什么 | ⚠️ 背景:自 #304 起,包装器在结构上已不可能存在,因此该规则现在解释六种拒绝原因及其处理方法,而非防止静默回退 |
rules/confirmation-triggers.md、rules/false-positive-detection.md | ❌ 对破坏性操作没有确认规范 |
hooks/session-start.sh — 过期检测拦截 | ❌ 升级后,会话可能继续运行过期的二进制文件 |
| Developer ID 签名 + 公证的二进制文件 | ❌ 自行构建的二进制文件采用临时签名;在 macOS 26 上,TCC 无法可靠地为其保留完全磁盘访问/自动化权限,因此权限看似已授予,却会停止生效(#211) |
版本 sidecar → --self-update + #303 过期自检 | ❌ 手动构建的二进制文件旁没有 sidecar,因此该检查始终不会触发 |
进阶:仅注册 MCP 服务(开发用途,或不需要插件)
git clone https://github.com/PsychQuant/che-apple-mail-mcp.git
cd che-apple-mail-mcp
swift build -c release
# --scope user : available across all projects (stored in ~/.claude.json)
# --transport stdio: local binary execution via stdin/stdout
# -- : separator between claude options and the command
mkdir -p ~/bin
cp .build/release/CheAppleMailMCP ~/bin/
claude mcp add --scope user --transport stdio che-apple-mail-mcp -- ~/bin/CheAppleMailMCP
将二进制文件安装到 ~/bin/ 等本地目录。避免使用云同步文件夹(Dropbox、iCloud、OneDrive)——同步活动会导致 MCP 连接超时。
若要让自行构建的二进制文件在重新构建后仍保留 TCC 权限,请使用 Developer ID 为其签名;详见签名与公证。否则,每次构建后都需要重新授予权限。
近期发布
完整详情请参见 CHANGELOG.md。
v2.7.2 (2026-05-10) — attachmentFragment 集群 + 回退功能对齐
- 加固了所有 3 个调用方中的
attachmentFragment缩进,并移除了已废弃的MailController.attachmentScript辅助方法,该方法绕过了 v2.7.0 的竞态缓解延迟(#61、#62) - 附件数量上限为 50,并通过
CHE_MAIL_ATTACHMENT_DELAY_BETWEEN/_TRAILING配置延迟(#63、#64) get_email_metadata的 SQLite 路径现在会在出错时回退到 AppleScript——补齐最后一个读取工具的缺口;所有 8 个优先使用 SQLite 的读取工具现在都有对等回退方案(#71)
v2.7.1 (2026-05-09) — base64 修复 + .partial.emlx + 可观测性
- 严重问题:RFC822 标头/正文分割返回的是相对数组索引,而不是绝对
Data索引,导致部分 Android Gmail 邮件的html_body以"sion: 1.0\n\n<base64>"开头——原始 base64 泄漏到大模型上下文中,并在下游触发 AUP 误报(#72) save_attachment现在会在.partial.emlx正文为空时,从Attachments/<rowId>/<part_id>/<filename>缓存读取——不再因二进制内容被剥离的 IMAP 邮件而静默写入 0 字节文件(#66)- SQLite 快速路径失败时现在会记录到 stderr(
SQLite ... fast path failed for rowId=...; falling through to AppleScript)(#69)
v2.7.0 (2026-05-04) — 缓解 Mail.app 竞态问题
- 多附件 AppleScript 操作之间增加 0.3 秒延迟,并在最后增加 0.5 秒延迟,以缓解快速 IPC 导致 Mail.app 悄然丢弃附件的问题(#60)
v2.6.0 (2026-05-03) — 加强安全性与验证(8 个 PR,16 个问题)
forward_email的纯文本模式现在会嵌入 RFC 3676 格式的>引用原文(与reply_email的 #43 修复保持一致)(#44)- 工具参数类型不匹配时直接报错——不再静默强制转换
bool/[String](#35) - 收件人电子邮件验证会拒绝标头注入(控制字符、缺失或多个
@)(#41) cc_additional会以不区分大小写的方式去重(#34)- 附件路径拒绝列表(
~/.ssh、Keychains、TCC 数据库、浏览器 Cookie),解析符号链接后再检查,并新增MAIL_MCP_ATTACHMENT_ROOTS环境变量允许列表(#38) - 所有 17 个接收 ID 的工具都会在处理程序入口处严格验证
id是否为 Int——防止 AppleScript 谓词注入(#50) - 为
reply_email运行时测试设置门控(#37、#45),并提供冒烟测试矩阵模板(#46、#47)
v2.5.0 (2026-04-17) — 撰写工具的 format 参数
- 全部 4 个撰写工具(
compose_email/create_draft/reply_email/forward_email)新增format: "plain" | "markdown" | "html"参数(解决 #14、#15) - 新增
message-composition能力规范
全部 54 个工具
账户(2)
| 工具 | 描述 |
|---|---|
list_accounts | 列出所有邮件账户 |
get_account_info | 获取账户详情 |
邮箱(4)
| 工具 | 描述 |
|---|---|
list_mailboxes | 列出所有邮箱(文件夹) |
create_mailbox | 创建新邮箱 |
delete_mailbox | 删除邮箱 |
get_special_mailboxes | 获取特殊邮箱名称(收件箱、草稿、已发送、废纸篓、垃圾邮件、发件箱) |
邮件(7)
| 工具 | 描述 |
|---|---|
list_emails | 列出邮箱中的邮件 |
get_email | 获取完整邮件内容 |
search_emails | 按主题/内容搜索 |
get_unread_count | 获取未读邮件数 |
get_email_headers | 获取所有邮件头 |
get_email_source | 获取原始邮件源 |
get_email_metadata | 获取元数据(转发、回复、大小) |
操作(8)
| 工具 | 描述 |
|---|---|
mark_read | 标记为已读/未读 |
flag_email | 标记/取消标记邮件 |
set_flag_color | 设置标记颜色(7 种颜色) |
set_background_color | 设置邮件背景颜色 |
mark_as_junk | 标记为垃圾邮件/非垃圾邮件 |
move_email | 移至其他邮箱 |
copy_email | 复制到其他邮箱 |
delete_email | 删除邮件(移至废纸篓) |
撰写(5)
| 工具 | 描述 |
|---|---|
compose_email | 发送新邮件(支持 cc/bcc/附件——仅接受纯地址:任何收件人列表中包含带显示名称的收件人都会导致发送被拒绝,请使用 create_draft(#404);自 #304 起,format 仅支持 plain;可选的 from_address 用于多账户发件人选择——参见 #131,支持通过经验证的“发件人”弹出窗口使用顺畅路径,#219)。正文始终来自 Mail 自带的编辑器——参见 #175 / check_accessibility;无法顺利执行的调用会以明确的原因失败,不会创建任何内容(#304) |
reply_email | 回复邮件。可选参数:cc_additional、attachments、save_as_draft、format(自 v2.4.0 起)。纯文本模式会嵌入 RFC 3676 格式的 > 引用原文(自 v2.5.0 / #43 起)。新正文会粘贴到 Mail 的原生回复界面中(#218);若 format 不是 plain 或缺少辅助功能授权,则会直接失败,而不会回退(#304) |
forward_email | 转发邮件。可选参数:body 和 format。纯文本模式会嵌入 RFC 3676 格式的 > 引用原文(自 v2.5.0+ / #44 起)。不含正文的转发不会分配任何内容,也不需要辅助功能授权;含正文时遵循与 reply_email 相同的规则(#218 / #304) |
redirect_email | 重定向邮件(保留原发件人) |
open_mailto | 打开 mailto URL |
回复并保存为草稿示例(v2.4.0+)
回复邮件对话,添加其他抄送收件人、附加文件,并保存为草稿,以便发送前由人工审核:
reply_email(
id="<message id from search_emails>",
mailbox="INBOX",
account_name="iCloud",
body="Reply text",
cc_additional=["[email protected]"],
attachments=["/path/to/file.pdf"],
save_as_draft=true
)
草稿(3)
| 工具 | 描述 | |------|-------------| | `list_drafts` | 列出草稿邮件 — 每个条目包含 `subject` 和数字 `id`([#276](https://github.com/PsychQuant/che-apple-mail-mcp/issues/276),增量功能;可用于 `update_draft.draft_id` / `delete_email.id`) | | `create_draft` | 创建草稿(支持附件;多账户发件人选择时可选填 `from_address` — 参见 [#131](https://github.com/PsychQuant/che-apple-mail-mcp/issues/131),通过经过验证的发件人弹出菜单支持干净路径,[#219](https://github.com/PsychQuant/che-apple-mail-mcp/issues/219))。正文由 Mail 自带的编辑器生成 — 参见 [#175](https://github.com/PsychQuant/che-apple-mail-mcp/issues/175) / `check_accessibility` — opt-in 直接写入除外(`CHE_MAIL_EXPERIMENTAL_DIRECT_DRAFT=1`,[#472](https://github.com/PsychQuant/che-apple-mail-mcp/issues/472)),详见下文“辅助功能”;无法干净执行的调用会以明确原因失败,且不会创建任何内容([#304](https://github.com/PsychQuant/che-apple-mail-mcp/issues/304))。`to` / `cc` / `bcc` 中的显示名称收件人(`Name `)会通过撰写窗口中由 AX 寻址的字段填入,并在保存前读取令牌以确认;隐藏的 Bcc 字段会显示出来并保持可见(`bcc_field_revealed: true`);保存后会重新读取 cc/bcc 地址(`recipients_verified: true|false` + `recipients_diff`,始终保留草稿)— [#277](https://github.com/PsychQuant/che-apple-mail-mcp/issues/277) / [#404](https://github.com/PsychQuant/che-apple-mail-mcp/issues/404) | | `update_draft` | 替换现有草稿(upsert,[#276](https://github.com/PsychQuant/che-apple-mail-mcp/issues/276)):通过 `draft_id` 或完全匹配的 `subject_match` 定位 → 创建替换草稿(沿用 `create_draft` 的适用条件和披露要求)→ 删除旧草稿。刻意采用先创建、后删除,并提供创建后的回执(失败时总是倾向于保留草稿 — 最坏情况下可能同时存在两份,但绝不会两份都没有);匹配数为 0 或大于 1 时一律拒绝(会列出候选项)。替换草稿会获得新的 ID。支持 `to` / `cc` / `bcc` 显示名称,与 `create_draft` 相同([#404](https://github.com/PsychQuant/che-apple-mail-mcp/issues/404)) |附件(2)
| 工具 | 描述 |
|---|---|
list_attachments | 列出邮件附件 |
save_attachment | 将附件保存到磁盘 |
VIP(1)
| 工具 | 描述 |
|---|---|
list_vip_senders | 列出 VIP 发件人 |
规则(5)
| 工具 | 描述 |
|---|---|
list_rules | 列出邮件规则 |
get_rule_details | 获取规则详情 |
create_rule | 创建新规则 |
delete_rule | 删除规则 |
enable_rule | 启用/禁用规则 |
签名(2)
| 工具 | 描述 |
|---|---|
list_signatures | 列出邮件签名 |
get_signature | 获取签名内容 |
SMTP(1)
| 工具 | 描述 |
|---|---|
list_smtp_servers | 列出 SMTP 服务 |
同步(2)
| 工具 | 描述 |
|---|---|
check_for_new_mail | 检查是否有新邮件 |
synchronize_account | 同步 IMAP 账户 |
批处理(4)
| 工具 | 描述 | |------|-------------| | `get_emails_batch` | 一次调用最多获取 50 封邮件(逐项报告错误) | | `list_attachments_batch` | 列出最多 50 封邮件的附件 | | `batch_export_emails_markdown` | 在服务端批量导出为原文 Markdown 和附件(冻结的 frontmatter 清单;按 output_dir 串行处理并发操作 — [#193](https://github.com/PsychQuant/che-apple-mail-mcp/issues/193) / [#236](https://github.com/PsychQuant/che-apple-mail-mcp/issues/236)) | | `export_emails_markdown` | **已弃用** — 已更名为 `batch_export_emails_markdown`([#233](https://github.com/PsychQuant/che-apple-mail-mcp/issues/233));别名最早将于 v3.0 移除 |实用工具(4)
| 工具 | 描述 |
|---|---|
extract_name_from_address | 从邮件地址中提取姓名 |
extract_address | 从完整地址中提取邮件地址 |
get_mail_app_info | 获取 Mail.app 信息 |
import_mailbox | 从文件导入邮箱 |
诊断(4)
| 工具 | 描述 |
|---|---|
check_fda | 检查完全磁盘访问权限状态,并立即重试打开此服务的索引读取器;报告服务实际的 SQLite 快速通道状态(已打开/刚恢复/仍不可用) |
check_accessibility | 检查辅助功能权限(撰写/回复 GUI 路径需要此权限;没有此权限时,这些工具会拒绝操作 — 但选择启用的直接写入 create_draft 路径除外,该路径需要完全磁盘访问权限) |
check_automation | 检查自动化权限(向 Mail 发送 Apple Events)— 不触发提示的探测,提供四种状态及修复方法(#293);二进制文件拥有自己的授权,osascript 能正常工作 ≠ 二进制文件已获授权(#288) |
get_mail_log_events | 读取 Mail 自身的统一日志,以查明事件链在哪一步中断(例如:草稿已保存 → 已排队 → 引擎已收到通知 → IMAP APPENDUID 回执)— 只读(#465)。detail=brief(默认值)仅返回静态事件模板、整数参数,以及 [account - mailbox] 行中的账号字母 — 只要模板是编译时字符串(Mail 自身的模板均为如此),就不会包含账号名称、主题或 Message-ID;detail=detailed 会添加原始消息,其中包含账号标识符和收到的邮件内容:请勿将其粘贴到公开问题中。时间窗口 ≤ 60 分钟,时间必须包含 UTC 偏移量;使用 since=next_start、offset=next_offset 和相同的 until 进行分页。目前还没有事件名称筛选功能(#466)。结果为空并不代表操作没有发生。仅在 macOS 27.2 / Mail 16.0 上验证过;需要使用属于管理员组的用户 |
这两个工具都返回一个封套对象 { results, returned, limit, truncated },而不是裸数组(v2.14.0、#204 中已更改)。请从 .results 读取匹配项:
| 字段 | 含义 |
|---|---|
results | 结果对象数组(每个对象的字段与封套格式之前相同)。search_emails 对象包含 id、subject、sender、date_received、account_name、mailbox、to;如果可以解析账户 UUID,还会包含 account_id。list_emails 对象包含 id、subject、sender。 |
returned | results 中的对象数量 |
limit | 应用于查询的有效 limit |
truncated | 当可用结果多于已返回结果时为 true——要获取剩余结果,请提高 limit 或缩小查询范围(在 SQLite 快速路径中此判断是确定的;在 AppleScript 回退路径中则是尽力而为的启发式判断——见下文) |
在 SQLite 快速路径中,truncated 的判断是确定的(内部会获取 limit + 1 条结果);在 AppleScript 回退路径中,则采用尽力而为的 returned == limit 启发式判断。任何采用“枚举 → 批量处理”方式的使用方,都应先检查 truncated,再假定已获取完整结果集。
安装
请从快速开始入手——安装插件是受支持的方式,可为你提供命令、安全规则、陈旧性钩子和已签名的二进制文件。以下内容均属于高级/开发路径:它只会注册 MCP 服务,因此安装内容严格更少(缺少哪些内容,请参阅 Plugin vs MCP-only;运行时不会提示你)。
Claude Desktop bundle 版本元数据
通过 scripts/package-mcpb.sh 构建的新 .mcpb bundle 会在可执行文件旁写入 server/.CheAppleMailMCP.version(#312)。打包程序会查询复制后的二进制文件的 --version,要求其与清单版本一致,并在压缩前写入旁置文件。若查询失败、格式错误或超时,打包程序会拒绝打包;运行中的服务首次使用时不会创建此文件。开发版和已签名发布版路径都使用此共享打包程序。
当较新的 bundle 替换了运行中可执行文件所在位置的文件,而较旧的镜像仍在运行时,此文件可启用现有的陈旧性诊断。警告会写入 MCP 服务的 stderr 日志,并提示你重启宿主。它不会检查 GitHub 上是否有新版本,不会更新未触碰的旧安装,也不会检测安装在其他目录中的新 bundle。此处尚未验证 Desktop 的更新/重启生命周期;版本匹配时不会显示警告。没有此旁置文件的旧 bundle 需要更新后才能获得该文件。
发布时请使用 make release-signed VERSION=vX.Y.Z。
scripts/build-mcpb.sh 仍用于明确标注为未签名的开发构建。
要求
- macOS 13.0+
- Xcode 命令行工具(适用于下文的自行构建方式)
- Apple Mail,且至少配置了一个账户
第 1 步:构建
git clone https://github.com/PsychQuant/che-apple-mail-mcp.git
cd che-apple-mail-mcp
swift build -c release
第 2 步:配置
适用于 Claude Desktop
编辑 ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"che-apple-mail-mcp": {
"command": "/full/path/to/che-apple-mail-mcp/.build/release/CheAppleMailMCP"
}
}
}
适用于 Claude Code(CLI)
# Copy to ~/bin and register (user scope = available in all projects)
mkdir -p ~/bin
cp .build/release/CheAppleMailMCP ~/bin/
claude mcp add --scope user --transport stdio che-apple-mail-mcp -- ~/bin/CheAppleMailMCP
第 3 步:授予权限
最快的方式是使用设置窗口。它会实时显示“完全磁盘访问权限”/“自动化”/“辅助功能”的状态,在你授予权限时重新检查,并为你打开相应的“系统设置”面板:
~/bin/CheAppleMailMCP --setup
也可以手动操作:
自动化(控制 Mail.app):
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation"
- 找到 CheAppleMailMCP,并为 Mail.app 启用权限
- 如果使用 Claude Code,也请添加 Terminal 或 iTerm
完全磁盘访问权限(SQLite 快速路径和 export_emails_markdown 会读取 ~/Library/Mail):
open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
macOS 会将“完全磁盘访问权限”授予负责进程——也就是启动此服务的应用,而非二进制文件本身。对于在终端内由 Claude Code 运行的 MCP 服务,负责进程是终端(Ghostty / Terminal / iTerm),因此请在此处添加你的终端应用并启用它。授予终端一次权限,即可覆盖它启动的所有 MCP 服务。(如果你直接运行二进制文件,或使用 Claude Desktop 应用包,则应添加该二进制文件——~/bin/CheAppleMailMCP——因为此时它就是自己的负责进程。)FDA 拒绝访问的错误消息会列出这些候选项,但不会自动确定唯一正确的应用,因为 macOS 不提供可靠的进程内 API 来实现这一点(#214)。没有“完全磁盘访问权限”时,读取工具会静默回退到较慢的 AppleScript 路径,而仅限 SQLite 的功能(projection、export_emails_markdown)则会失败。对于直接启动的情况,使用 Developer ID 签名的构建版本可让授权在版本升级后继续有效——请参阅签名与公证。
引导式设置(#213)——除了上述手动步骤,二进制文件还附带设置辅助工具:
CheAppleMailMCP --setup会打开一个小窗口,显示实时的“完全磁盘访问权限”状态(定时重新检查,授权后立即切换为“Ready ✅”),并提供按需的自动化检查,以及“打开‘完全磁盘访问权限’设置”/“复制二进制文件路径”按钮。CheAppleMailMCP --check-fda会以无界面方式输出状态(若访问被拒绝,则打开设置面板)——适合在终端或脚本中使用。check_fdaMCP 工具会按需向 Claude 报告相同状态(仅限 SQLite 的功能出错时可调用)。它还会让正在运行的服务重试打开自己的索引读取器,并报告结果(#537):不再将探测已获授权误认为服务能够使用该索引。如果读取器不可用,而重试后成功打开,答复会说明已恢复;如果探测可以读取文件,但服务仍无法打开,则会显示该错误,并提示重新连接服务(在 Claude Code 中使用/mcp)。
这些方法都无法免除唯一需要手动执行的开关操作(Apple 将 FDA 归入仅限手动设置的权限类别,与“辅助功能”/“屏幕录制”并列),但它们能清楚说明“该怎么做?”,并在你启用权限的瞬间提供实时反馈。
辅助功能(撰写,#175/#304)——这是一项独立于“完全磁盘访问”的授权,GUI 撰写路径需要它。Mail.app 会将任何通过 AppleScript 注入的外发邮件正文包在 <blockquote type="cite"> 中,一些移动客户端会将其显示为对你自己文本的引用——而发送者在本地看不到这个引用,因为该包装元素的内联样式没有边框。自 #304 起,生成该包装元素的代码已不复存在:每个撰写工具都从 Mail 自己的编辑器获取正文——compose_email / create_draft 通过 mailto: 交接,reply_email / forward_email 则使用原生回复/转发动词并粘贴——并通过键盘快捷键执行保存/发送/添加附件,这需要辅助功能(系统设置 → 隐私与安全性 → 辅助功能),且必须授予与 FDA 相同的责任进程(你的终端 / Claude Desktop)。check_accessibility MCP 工具和 --setup 窗口中的辅助功能行会报告状态。未获授权时,这些工具现在会失败,而不是回退(forward_email 不带正文时不会写入任何内容,也不需要授权)——它们过去会回退到的旧路径已被移除,因此无法正常执行的调用会返回明确命名的错误,且不会创建任何内容。错误会指向 open_mailto,它完全不需要 TCC 授权(它无法携带附件;你需要自行保存或发送窗口)。只有六种情况会拒绝调用:format 不是 plain;主题为空;未授予辅助功能权限;from_address 不是纯粹的 addr-spec;附件路径包含非 ASCII 字符(#220);以及收件人带有此路径无法填入的显示名称(cc/bcc 始终如此;发送时的 to 也是如此——草稿的 to 显示名称会通过 GUI 填入,#277)。要从非默认账户发送干净的正文,请传入 from_address:GUI 会在 Mail 的“发件人”弹出菜单中选择该账户并读取所选项,如果不匹配便会中止,以免使用错误的发件人(#219)。随旧路径一同移除的功能:format: "markdown" / "html"——目前没有任何路径能够在不使用已删除的正文赋值操作的情况下发送富文本。这是当前实际情况,并非不可能性的证明(#310):粘贴路径(#218)是另一条不会添加包装元素的途径,而 NSPasteboard 可以携带富文本格式,但它生成的 MIME 尚未验证——#306 会对此作出定论;#308 / #309 是替代方案,包括 require_wrapper_free 和 sanitize_links 参数,以及 CHE_MAIL_DISABLE_MAILTO_COMPOSE / CHE_MAIL_DISABLE_PASTE_REPLY 应急开关。相关的两项能力也需明确说明:不显示窗口进行撰写(这些应急开关最初的用途)已不再可行,而且 compose_email 不再能向 Name <addr> 发送邮件——请使用 create_draft,然后自行发送草稿。
一个例外:须选择启用,且属实验性功能:启用 CHE_MAIL_EXPERIMENTAL_DIRECT_DRAFT=1 后,若 create_draft 调用满足直接写入路径的所有条件(message-composition 规范中 Requirement: Direct-write draft path 一节列出的两个封闭列表),便会将草稿直接写入 Mail 的存储中(#472 / #475)。此路径不会打开窗口,也不需要辅助功能权限;但需要“完全磁盘访问”和“自动化”权限。任何不满足条件的调用都会采用上述 GUI 路径,并遵循其中所述的一切要求。
自动化 TCC(-1743)与零 TCC 备用方案
如果 AppleScript 支持的工具报错 AppleScript error (-1743): Not authorized to send Apple events to Mail,说明此二进制文件缺少自动化权限。已签名的 MCP 二进制文件拥有自己的自动化授权——其 TCC 身份由该二进制文件的签名身份决定(#211 的 FDA 经验教训,自动化维度),与终端的身份相互独立。经实测:在 shell 中通过 osascript 控制 Mail,并不意味着该二进制文件已获授权。请前往系统设置 → 隐私与安全性 →
自动化授予权限——找到该二进制文件或其宿主对应的条目(Claude Desktop 扩展位于Claude.app下),然后启用 Mail。如果没有对应条目,说明系统记住了之前的拒绝记录,不会再次提示:运行 tccutil reset AppleEvents,然后重试 Mail 工具以重新触发权限提示。授权按安装分别生效,二进制文件更新可能导致条目失效(#211)。
在授予权限之前,open_mailto 仍可使用:它通过 LaunchServices(零 TCC,#287)打开系统默认邮件客户端中的撰写窗口,且不包含引用块。mailto 无法携带附件(RFC 6068)——请手动拖入文件。
步骤 4:重启 Claude
# For Claude Desktop
osascript -e 'quit app "Claude"' && sleep 2 && open -a "Claude"
# For Claude Code - start a new session
claude
使用示例
自然语言(Claude Desktop)
"List all my mail accounts"
"Show unread emails in Gmail inbox"
"Search for emails about 'quarterly report'"
"Send an email to [email protected] about the meeting"
"Flag important emails in red"
"Create a rule to move newsletters to a folder"
直接调用工具(Claude Code)
"Use list_accounts to show my accounts"
"Use search_emails to find emails containing 'invoice'"
"Use set_flag_color to mark email ID 12345 as blue"
"Use check_for_new_mail to refresh"
标记颜色与背景颜色
标记颜色(set_flag_color)
| 索引 | 颜色 |
|---|---|
| 0 | 红色 |
| 1 | 橙色 |
| 2 | 黄色 |
| 3 | 绿色 |
| 4 | 蓝色 |
| 5 | 紫色 |
| 6 | 灰色 |
| -1 | 清除 |
背景颜色(set_background_color)
blue、gray、green、none、orange、purple、red、yellow
性能与存储
SQLite + .emlx 快速路径
大多数读取工具优先使用 Apple Mail 的本地 Envelope Index(SQLite)和磁盘上的 .emlx 邮件文件,而不是 AppleScript IPC;如果 SQLite 路径无法满足请求,则会透明回退到 AppleScript:
| 工具 | SQLite/.emlx 路径 | AppleScript 回退 |
|---|---|---|
get_email | ✓ | ✓ 出现任何错误时 |
get_emails_batch | ✓(逐项) | ✓(逐项) |
get_email_headers | ✓ | ✓ 出现任何错误时 |
get_email_source | ✓ | ✓ 出现任何错误时 |
search_emails | ✓ | ✓ 读取器不可用时 |
list_attachments | ✓ | ✓ 出现任何错误时 |
save_attachment | ✓ | ✓ 出现任何错误时 |
get_email_metadata | ✓ | ✓ 出现任何错误时(自 #71 起) |
get_mail_log_events 有意不在此表中:它通过 /usr/bin/log 读取 Mail 的统一日志(事件:发生了什么以及何时发生),而不是 Envelope Index(状态)。它不需要完全磁盘访问权限,但需要用户属于管理员组;它绝不能作为判断邮件是否存在的依据——参见 .claude/rules/r-must-direct-db.md。
对于 save_attachment 的读取路径,快速路径比 AppleScript 快 10–100 倍(根据 #12 的测量结果)。其他工具的提速比例取决于请求形式;通常,批量读取量越大,收益越明显。
快速路径需要:
- 为宿主进程授予完全磁盘访问权限(系统设置 → 隐私与安全性 → 完全磁盘访问权限)
- Apple Mail 的本地存储位于
~/Library/Mail/V10/... - 邮件已同步到本地
.emlx存储
EWS / Exchange 账户有意绕过快速路径
Apple Mail 中的 Exchange(EWS)账户不会生成 .emlx 文件——邮件正文保存在服务器上,按需获取。对于这些账户,全部 8 个读取工具(自 #71 起也包括 get_email_metadata)都会透明地降级为 AppleScript IPC(功能正确,但速度较慢)。表现如下:
- 批量获取 500 封 EWS 邮件,会明显慢于获取 500 封 IMAP/Gmail 邮件
- 这不是 bug——这是 Apple Mail 存储架构的限制(参见 #9)
诊断快速路径绕过问题
对于非 EWS 账户,如果快速路径失败,失败信息会记录到 stderr(自 #69 起)。在终端中运行二进制文件并查看 stderr,以便区分:
EnvelopeIndexReader init failed: ...——服务器启动时无法访问数据库(常见原因:缺少“完全磁盘访问”权限)。这不是最终状态(#537):在索引打开之前,读取工具会使用 AppleScript;至少在上次尝试 30 秒后运行的读取工具会再次尝试打开索引,而check_fda会立即重试。EnvelopeIndexReader retry failed: ...——重试未能打开索引。如果重试以完全相同的方式失败,就不会再次记录日志,因此未设置 Mail 的环境(例如仅使用 Exchange 的环境)只会记录一次,而不是每 30 秒记录一次。... (requested by check_fda)表示这是你请求的重试;这类重试总会记录日志。EnvelopeIndexReader retry recovered ...——索引在之前失败后已成功打开;从下一次调用起,读取工具会再次使用 SQLite 快速路径。SQLite get_email fast path failed for rowId=N: ...——单封邮件处理失败(例如,只有.partial.emlx、MIME 格式错误或文件尚未同步)
前 3 种日志行末尾都会显示 FDA probe: <result> (errno N)。这是 check_fda 使用的普通 fopen 检查,记录的是 SQLite 失败时的结果,因此可以将两者进行比较:denied 或 no-mail-data 表示文件被拒绝访问或不存在;如果打开失败时显示 granted,则表示探测结果与 SQLite 打开结果不一致(请连同日志行一起反馈)。请注意,如果打开持续失败的原因不在服务器本身(例如仍未授予访问权限),重试也无法解决问题。
这两种情况都会透明地回退到 AppleScript,并在日志行中显示 ... falling through to AppleScript,因此行为保持不变,同时恢复了可观测性。
故障排除
| 问题 | 解决方案 |
|---|---|
| 服务器已断开连接 | 使用 swift build -c release 重新构建 |
| 无权发送 Apple 事件 | 在“系统设置”>“自动化”中添加权限 |
| Mail.app 无响应 | 确保 Mail.app 正在运行且已配置账户 |
| 命令超时 | 大型邮箱需要更长时间;尝试使用特定搜索。如果发送邮件、保存草稿或更改邮件的调用超时,其在 Mail 中的操作结果未知:重试前请参阅超时后:可以和无法确定的情况 |
| 批量获取速度低于预期 | 查看 stderr 中是否有 ... falling through to AppleScript 行。EWS/Exchange 账户总是回退(参见性能与存储);其他账户记录了回退信息,则表示存在可修复的 .emlx 问题 |
save_attachment 失败,并报错 -1728 "Can't get account" 或 -1719 "Invalid mailbox index" | 自 #173 起,这两种错误都会附带可操作的提示,指出失败的引用对象(账户 / 邮箱 / 邮件)。常见原因:两个 Mail.app 账户使用相同的 display_name,或采用电子邮件格式的 account_name 对应多个账户——参见下文的账户消歧。 |
超时后:可以知道和无法知道的事项
调用超时时,工具无法得知 Mail 后续是否完成了操作。结束 osascript 子进程(或放弃进程内调用)并不会撤销 Mail 已接受的 Apple Events,因此保存或发送操作可能已经发生。错误信息对此有所说明。本节列出之后可以查看的内容,以及每项检查无法证明的事项。这里没有任何内容可以让工具报告“已发送”或“未发送”:只有正面观察结果才算证据,缺少某一行绝不能解读为“什么都没发生”。
| 操作 | 工具报告的内容 | 可以查看的内容 | 无法说明的内容 |
|---|---|---|---|
发送:compose_email、reply_email、forward_email | send state is UNKNOWN、NOT retrying(工具绝不会自行重新发送) | 使用你所用的主题,通过 search_emails 搜索,设置 field="subject";读取每一行的 mailbox 和 date_received。匹配的行可以证明该邮箱中存在一封主题相同的邮件 | 缺少某一行并不能证明没有邮件发出:索引是异步写入的,有些账户不会保留“已发送”副本,而且 search_emails 可能无故返回空结果(#549、#536)。匹配的行也可能是较早发送的同主题邮件。不要自动重新发送;由用户决定 |
保存草稿:create_draft、带有 save_as_draft 的 reply_email | 超时错误,提示保存或发送操作可能已经发生 | 如上所述使用 search_emails,或使用 list_drafts(它会向 Mail 发出请求,因此也可能再次超时) | 缺少某一行并不能证明草稿未保存。再次保存可能会留下两份草稿;请删除多余的一份 |
在 update_draft 中删除旧草稿 | deleted_old: false,并附有说明删除结果未知的 note | list_drafts,或使用草稿主题通过 search_emails 搜索 | 如果仍列出旧 ID 对应的行,只能说明索引仍将其列出;缺少某一行反映的是索引的视图,并非 Mail 的确认。在查看之前,不要重试更新或删除操作 |
设置值:mark_read、flag_email、set_flag_color、set_background_color、mark_as_junk | 超时错误 | 重复相同调用会设置相同值(每项都设置绝对值,而非切换状态)。get_email_metadata 会报告 read 和 flagged | 超时的调用之后仍可能完成;如果你原本想设置不同的值,请事后检查状态 |
search_emails 会读取 Envelope Index,且需要完整磁盘访问权限;没有该权限时,它会通过 AppleScript 向 Mail 发出请求,因此可能再次超时。此处未归类其他写入工具(move_email、copy_email、delete_email、邮箱和规则管理等):重试前请先检查状态。
账户消歧
当两个账户共享相同的 display_name 时,Mail.app 的 AppleScript account "<display_name>" 选择器并不唯一。这在 iCloud 全收件别名将 Gmail 地址转发回自身,或 Google Workspace 账户与个人 Gmail 账户重叠时很常见。任何通过 AppleScript 路由的工具(save_attachment 回退路径、get_email、mark_read 等)此时都可能非确定性地选错账户 → 出现 -1728 / -1719 错误。
解决方法:在传入 account_name 的同时传入 account_id(Mail.app 的全局唯一 UUID)。提供该值后,save_attachment 会改用 Mail.app 的 account id "<UUID>" 选择器,从而避免歧义:
// Tool call: save_attachment with account_id
{
"id": "273214",
"mailbox": "[Gmail]/全部郵件",
"account_name": "[email protected]",
"account_id": "C38E0583-47F8-4468-BE70-43155C15549D", // ← disambiguates
"attachment_name": "report.pdf",
"save_path": "/tmp/report.pdf"
}
查找 account_id:
- 来自
search_emails结果 —results数组中的每个对象(一个SearchResult)都包含account_id字段和account_name。该字段通过MailboxURL.decode从 SQLitemailboxes.url的 authority 中解码账户 UUID 填充——Mail.app 的存储约定会将账户 UUID 编码在邮箱 URL 的 authority 中;不存在直接的SELECT mailboxes.account_id。建议:直接传递该字段。 - 手动获取 — 读取
~/Library/Mail/V10/MailData/Signatures/AccountsMap.plist。顶层键是 UUID;AccountURL值的 authority 中包含经过百分号编码的匹配电子邮件地址。 - 在 AppleScript 中获取 —
tell application "Mail" to get id of every account会返回 UUID 列表。
向后兼容性:account_id 是可选项。省略或留空时,工具会回退到旧版 account "<display_name>" 路径——行为与 #101 之前完全相同——但有一个 save_attachment 例外(#173):当 account_name 包含 @(即采用电子邮件地址形式,SQLite 路径工具如 search_emails 会输出这种形式)时,save_attachment 会先在 AccountsMap 中反向查找,并静默升级为 account id "<UUID>" 选择器(升级操作会记录到 stderr)。只有一个匹配项 → 使用该 UUID;同一地址对应多个账户(iCloud catch-all + Gmail)→ 返回可执行的错误,列出所有候选项,而非原始 -1728;没有匹配项 → 使用旧版显示名称路径,保持不变。边缘情况:如果某个 Mail 账户的描述确实包含 @,且恰好与另一个账户的电子邮件地址相同,现在会优先在电子邮件命名空间中解析——请显式传入 account_id,以固定选择器。其他工具仍采用严格的 #101 之前回退逻辑(跨工具全面检查见 #176)。
适用范围:所有通过 AppleScript 路由、且按账户引用邮件的工具均接受 account_id。该功能最初用于 save_attachment(#101);随后 #104 的全面检查为以下 13 个单条消息/移动/转发/邮箱工具添加了该功能:
save_attachment(#101)——先行实现- PR-A — 5 个单条消息修改工具:
mark_read、flag_email、set_flag_color、set_background_color、mark_as_junk - PR-B — 3 个移动/删除工具:
move_email、copy_email、delete_email - PR-C — 3 个消息转发工具:
reply_email、forward_email、redirect_email - PR-D — 2 个邮箱 CRUD 工具:
create_mailbox、delete_mailbox
此后,支持范围已超出 #104 中的集合:
- #176 — 将电子邮件地址→UUID 的
resolveAccountIdForTool解析入口推广到全部 14 个通过 AppleScript 路由的写入处理程序(因此,电子邮件地址形式的account_name会解析为 UUID 选择器,而不仅仅是接受account_id)。 - #180 — 通过
resolveMailboxRef/resolveMsgRef,将account_id接入读取工具的 AppleScript 回退路径(list_emails/search_emails/get_email/ headers / source / metadata / attachments /get_unread_count)(此前延期的 PR-E 现已完成)。 - #179 —
get_special_mailboxes接受account_id/account_name,用于获取每个账户对应的特殊邮箱实际名称。 - #191 — 账户级操作工具
check_for_new_mail和synchronize_account增加了account_id这一备用选项(synchronize_account可单独接受account_id)。
仍未支持 account_id(已跟踪):get_account_info / list_mailboxes(#202)。
compose_email / create_draft 不会出现 display_name 冲突缺陷——它们会创建新的发件,而不是按账户引用现有邮件,因此不会生成 account "<display_name>" 选择器。现在可通过可选的 from_address 参数选择多个账户的发件人(#131)——传入你配置的任一 Mail.app 邮箱地址("[email protected]" 或 RFC 5322 格式的 "Alice <[email protected]>"),即可设置外发邮件的 sender;省略该参数则使用 Mail.app 的默认账户。使用 list_accounts 可查看正在运行的 Mac 上配置的地址。
不支持通过 account_id 跨账户移动/复制(#129 — 根据 #127 验证)。move_email 和 copy_email 接受单个 account_id,并将其传递给源 msgRef 和目标 mailboxRef。这种架构选择是正确的(移动操作限定在同一账户内,因为 Mail.app 的 AppleScript 动词 move msg to <mailboxRef> 要求目标邮箱相对于单一账户上下文指定)。Mail.app 的 UI 允许通过拖放跨账户移动,但由 AppleScript 路由的 move_email / copy_email 工具无法复现此操作——如果调用 move_email 时传入一个账户的 account_id,却期望目标 to_mailbox 在另一个账户中解析,系统会静默选择名称相同的错误账户邮箱(如果两个账户恰好都有该邮箱),或报错 -1719 "Invalid mailbox index"。如果需要在另一个账户下保留邮件内容的副本,可以通过 save_attachment + compose_email 手动重新构建——请注意,这并非真正的移动/复制:原始元数据(Message-ID、收到日期、标记、标签)和邮件身份都不会保留。
开发测试
在装有 Xcode Command Line Tools、Python 3 和 jq 的 macOS 上,运行 make test。它会先运行 Swift 测试套件,再运行插件测试套件;任何失败都会导致命令失败。运行 make test-plugin 则只运行插件测试套件,以便更快地迭代。
插件运行器会发现 plugin/tests/test-*.sh 和 test-*.py,检查必需的钩子测试套件是否存在,并在缺少依赖项时明确报错。每个匹配的文件都必须是独立的 Bash 或 Python 测试套件;请将共享辅助程序放在其他名称的文件中或子目录下。插件测试使用隔离的测试夹具和模拟进程/下载。
此 Makefile 配置本身不会添加 GitHub Actions 工作流。
技术细节
- 框架:MCP Swift SDK v0.10.0
- 读取路径:SQLite(Envelope Index)+
.emlx文件解析器;对于 EWS / 无法解析的.emlx,则回退到 AppleScript - 写入/状态路径:通过
NSAppleScript使用 AppleScript - 传输方式:stdio
- 平台:macOS 13.0+(Ventura 及更高版本)
签名与公证
分发的二进制文件经过 Developer ID 签名和公证,这并非只是表面功夫。快速读取路径需要 完全磁盘访问权限(FDA),而 macOS TCC 会将 FDA 授权关联到二进制文件的指定要求。对于临时签名的二进制文件,该要求是 cdhash,因此每次版本更新都会使授权失效,你必须在每次发布后重新将二进制文件添加到“完全磁盘访问权限”列表中。稳定的 Developer ID 签名则会将授权关联到签名身份,因此版本更新后授权仍然有效(#211)——带来持久性的正是签名,而非公证。
对于隔离状态下启动的情况,公证很重要:例如通过浏览器下载,或安装 .mcpb(Claude Desktop)时,Gatekeeper 会在首次启动时评估二进制文件。插件包装器的 curl + exec 路径不会设置隔离属性,因此 Gatekeeper 不会在此路径触发。我们仍然会进行公证,以确保发布的资源无论通过何种方式运行都安全可靠。
首次授权仍需手动完成。 FDA(
kTCCServiceSystemPolicyAllFiles)没有可通过程序调用的请求 API——应用只能通过深层链接将你带到设置面板。签名能让首次授权永久有效,但不会自动授予权限。
一次性设置(维护者)
# 1. Developer ID Application cert in your login keychain (needs an Apple Developer account)
security find-identity -p codesigning -v # find your identity
# 2. notarytool keychain profile (prompts for an app-specific password — never pass it on the CLI)
xcrun notarytool store-credentials <profile-name> \
--apple-id <your-apple-id> --team-id <your-team-id>
# 3. Export both for the signed targets
export DEVELOPER_ID='Developer ID Application: Your Name (TEAMID)'
export NOTARY_PROFILE='<profile-name>'
在自己的机器上安装开发版本(快速——无需公证)
make install-signed # build + Developer ID sign + copy to ~/bin
使用此方式,即可在自己的 Mac 上获得稳定的 FDA 授权,无需等待 Apple 公证:自己的证书在本地可以正常启动,且未来重新构建后授权仍然有效。只需为 ~/bin/CheAppleMailMCP 授予一次“完全磁盘访问权限”即可。
发布分发版本(已签名 + 已公证 + 已发布)
make release-signed VERSION=vX.Y.Z # wraps scripts/release.sh with REQUIRE_CODESIGN=1
此操作会构建通用(arm64 + x86_64)二进制文件、为其签名并进行公证(与 Apple 往返处理需 1–15 分钟),然后将其上传到 GitHub 发布页。没有证书的 Fork 仍可使用 SKIP_CODESIGN=1 ./scripts/release.sh vX.Y.Z 发布未签名的开发版本。
贡献
欢迎贡献!请随时提交拉取请求。
许可证
MIT 许可证——详情请参阅 LICENSE。
作者
由 Che Cheng(@kiki830621)创建
如果你觉得这个项目有用,请考虑点个星标!
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。