Skip to main content
Glama
PsychQuant

che-apple-mail-mcp

by PsychQuant

che-apple-mail-mcp

License: MIT macOS Swift MCP

最全面的 Apple Mail MCP 服务器——内置 53 个工具,依靠 SQLite 实现对 250K+ 封邮件的毫秒级搜索。

English | 繁體中文


为什么选择 che-apple-mail-mcp?

功能特性

其他 MCP

che-apple-mail-mcp

工具总数

~20

53

语言

Python

Swift(原生)

搜索速度

秒级(AppleScript)

毫秒级(SQLite)

搜索字段

主题/发件人

主题/发件人/收件人/日期

批量操作

单次调用最多 50 封邮件

邮箱管理

基础

完整 CRUD

邮件颜色

7 种旗标颜色 + 背景色

VIP 管理

支持

规则管理

部分

完整 CRUD

邮件签名

支持

原始邮件头/原文

支持


Related MCP server: apple-mail-mcp

快速开始

安装插件。它将已签名二进制、/archive-mail 命令族、安全规则以及过期检查 hook 作为一个整体一起安装:

claude plugin marketplace add PsychQuant/che-apple-mail-mcp
claude plugin install che-apple-mail-mcp@che-apple-mail-mcp

然后授予权限——设置窗口会显示实时状态,并直接链接到对应的系统设置面板:

~/bin/CheAppleMailMCP --setup

💡 完全磁盘访问(Full Disk Access) 是实现快速 SQLite 读取路径和 batch_export_emails_markdown 正常工作的前提。若没有该权限,工具仍会运行,但几乎读不到任何数据,很容易被误认为是 bug 而不是权限问题。macOS 不允许应用程序以编程方式请求 FDA(完全磁盘访问)——必须手动勾选——而这正是设置窗口存在的价值:它让这一过程变得快捷。

插件版 vs 仅 MCP 版

仅注册 MCP 服务器本身是一条支持的高级路径,但它是一套严格更小的安装。请务必在知情的前提下选择——运行时不会有任何提示告诉你这些功能缺失(#353):

插件版提供的内容

仅 MCP 版的情况

全部 53 个 MCP 工具

✅ 有

/archive-mail + -migrate / -rebuild-threads / -repair-synthetic-ids / -view

❌ 不存在;没有归档 SOP

rules/composer-wrapper-free.md —— 原有引用块是做什么的,以及拒绝 compose 调用的含义

⚠️ 背景:由于 #304 之后,wrapper 在结构上已经不可能,因此这条规则现在解释六种拒绝原因及其应对方案,而不是防止静默回退

rules/confirmation-triggers.mdrules/false-positive-detection.md

❌ 不具备对破坏性操作的确认约束

hooks/session-start.sh —— 过期进程清理

❌ 升级后,会话可能在继续运行旧二进制

Developer ID 签名 + 公证的二进制

❌ 自行构建的二进制为 ad-hoc 签名;在 macOS 26 上 TCC(TCC)无法稳固保持其 FDA/自动化权限,因此权限可能显示已授予而后失效(#211

版本 sidecar → --self-update + #303 过期自检

❌ 手动构建的二进制旁无 sidecar,因此总是保持静默

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 缩进处理,并移除了绕过 v2.7.0 竞态缓解延迟的旧 MailController.attachmentScript 辅助代码(#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 + 可观测性

  • 严重问题:RFC 822 头部与正文的拆分原本返回的是相对数组 index 而非绝对 Data index,导致部分 Android Gmail 邮件的 html_body 会以 "ion: 1.0\n\n<base64>" 开头 —— 原始 base64 泄漏到了 LLM 上下文中,并在下游触发新的 AUP 误判(#72

  • .partial.emlx 正文为空时,save_attachment 现在会从 Attachments/<rowId>/<partId>/<filename> 缓存中读取 —— 不再在没有二进制的 IMAP 邮件下静默写入 0 字节文件(#66

  • SQLite 快路径失败时现在记录到 stderr(SQLite ... fast path failed for rowId=...; falling back to AppleScript)(#69

v2.7.0(2026-05-04)—— Mail.app 竞态缓解

  • 多附件 AppleScript 现在会在中间插入 0.3 秒、结尾增加 0.5 秒的延迟,以缓解 Mail.app 在快速 IPC 下静默丢失附件的问题(#60

v2.6.0(2026-05-03)—— 安全与校验加固(8 个 PR、16 个 issue)

  • forward_email plain 纯文本模式现在嵌入 RFC 3676 > 引用原件(与 reply_email 的 #43 修复保持一致)(#44)

  • 工具参数类型不匹配时改为硬失败 —— bool / [String] 不再被静默强制转换(#35)

  • 收件人邮箱校验会剔除头注入(控制字符、缺失/多个 @)(#41)

  • cc_additional 按大小写不敏感去重(#34)

  • 附件路径添加拒绝列表(~/.ssh、钥匙串、TCC 数据库、浏览器 Cookie),并用 symlink 解析 + 新增 MAIL_MCP_ATTACHMENT_ROOTS 环境变量白名单(#38)

  • 所有 17 个接受 id 的工具现在都在 handler 边界对 id 做严格的 Int 校验,并抵抗 AppleScript 谓词注入(#50)

  • reply_email 运行时增加了门控集成测试(#37#45)以及 smoke 矩阵模板(#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 能力说明书


全部 53 个工具

工具

描述

list_accounts

列出所有邮件账户

login_account_info

获取账户详细信息

工具

描述

list_mailboxes

列出所有邮箱(文件夹)

create_mailbox

新建邮箱

delete_mailbox

删除邮箱

get_special_mailboxes

获取特殊邮箱名(收件箱、草稿、已发送、废纸篓、垃圾邮件、发件箱)

工具

描述

list_emails

列出邮箱中的邮件

get_email

获取完整邮件内容

search_emails

按主题 / 内容搜索

get_unread_count

获取未读数量

get_email_headers

获取全部邮件头

get_email_source

获取原始邮件原文

get_email_metadata

获取元数据(是否已转发、是否已回复、大小)

工具

描述

mark_read

标记为已读/未读

flag_email

标记/取消标记邮件

set_flag_color

设置标记颜色(7 种颜色)

set_background_color

设置邮件背景颜色

mark_as_junk

标记为垃圾邮件/非垃圾邮件

move_email

移动到其他邮箱

copy_email

复制到其他邮箱

delete_email

删除邮件(移至废纸篓)

工具

描述

compose_email

发送新邮件(支持 cc/bcc/附件;format:自 #304 起仅支持 plain;可选的 from_address 用于多账户发件人选择——见 #131,通过已验证的 From 弹窗支持干净路径,见 #219)。正文始终来自 Mail 自带的编辑器——见 #175 / check_accessibility;无法干净运行的调用会以具名原因失败(FAILS),并且不会创建任何内容(#304

reply_email

回复邮件。可选参数:cc_additionalattachmentssave_as_draftformat(自 v2.4.0 起)。纯文本模式会嵌入 RFC 3676 的 > 引用原文(自 v2.5.0 / #43 起)。新正文会被粘贴到 Mail 的原生回复编辑框中(#218);如果 format 不是 plain,或缺少辅助功能(Accessibility)授权,则会 FAILS 失败,而不会回退(#304

forward_email

转发邮件。可选参数 body + format。纯文本模式会嵌入 RFC 3676 的 > 引用原文(自 v2.5.0+ / #44 起)。无正文的转发不产生正文,也无需辅助功能(Accessibility)授权;带正文时,规则与 reply_email 相同(#218 / #304

redirect_email

重定向邮件(保留原始发件人)

open_mailto

打开 mailto URL

回复为草稿的示例 (v2.4.0+)

回复某个邮件线程,添加额外的抄送(CC),附加文件,并将其保存为草稿,以便在发送前由人工审阅:

reply_email(
    id="<message id from search_emails>",
    mailbox="INBOX",
    account_name="iCloud",
    body="Reply text",
    cc_additional=["x@y.com"],
    attachments=["/path/to/file.pdf"],
    save_as_draft=true
)

工具

描述

list_drafts

列出草稿邮件——每个条目都带 subject 和数字 id#276,附加信息;供 update_draft.draft_id / delete_email.id 使用)

create_draft

创建草稿(支持附件;可选的 from_address 用于多账户发件人选择——见 #131,通过已验证的 From 弹窗支持干净路径,见 #219)。正文始终来自 Mail 自带的编辑器——见 #175 / check_accessibility;无法干净运行的调用会以具名原因 FAILS,并且不会创建任何内容(#304

update_draft

替换现有草稿(upsert,按需更新,#276):通过 draft_id 或精确的 subject_match 定位 → 创建替换草稿(继承 create_draft 的资格条件和披露规则)→ 删除旧草稿。特意采用“先创建后删除”(create-THEN-delete)的方式,并附带创建后的回执(失败时总是倾向于保留草稿——最坏情况下两者都可能存在,但绝不会两者都不存在);0 个或大于 1 个匹配时一律拒绝(列出候选)。替换后总会得到一个新的 id

工具

描述

list_attachments

列出邮件附件

save_attachment

将附件保存到磁盘

工具

描述

list_vip_senders

列出 VIP 发件人

工具

描述

list_rules

列出邮件规则

get_rule_details

获取规则详情

create_rule

创建新规则

delete_rule

删除规则

enable_rule

启用/禁用规则

工具

描述

list_signatures

列出邮件签名

get_signature

获取签名内容

工具

描述

list_smtp_servers

列出 SMTP 服务器

工具

描述

check_for_new_mail

检查新邮件

synchronize_account

同步 IMAP 账户

工具

描述

get_emails_batch

一次调用最多获取 50 封邮件(逐项错误)

list_attachments_batch

列出最多 50 封邮件的附件

batch_export_emails_markdown

服务端批量导出为原样 Markdown + 附件(冻结的 frontmatter 清单;按 output_dir 串行化并发 — #193 / #236

export_emails_markdown

已弃用 — 已重命名为 batch_export_emails_markdown#233);别名移除不会早于 v3.0

工具

描述

extract_name_from_address

从电子邮件地址提取姓名

extract_address

从完整地址中提取电子邮件

get_mail_app_info

获取 Mail.app 信息

import_mailbox

从文件导入邮箱

工具

描述

check_fda

检查完全磁盘访问权限状态(SQLite 快速路径可用性)

check_accessibility

检查“辅助功能”权限(撰写 / 回复界面的 GUI 路径;没有该权限时相应工具会拒绝运行)

check_automation

检查“自动化”权限(通过 Apple Events 控制 Mail)— 非提示式探测,提供四种状态及修复说明(#293);二进制持有自身授权,osascript 可用 ≠ 二进制已获得授权(#288

响应结构:search_emails / list_emails

两个工具都返回一个 信封对象 { results, returned, limit, truncated } — 而 不是 一个裸数组(在 v2.14.0#204 中更改)。请从 .results 中读取匹配的结果:

字段

含义

results

结果对象数组(每个对象的字段与信封之前的结构保持一致)。search_emails 对象包含 idsubjectsenderdate_receivedaccount_namemailboxto;当账号 UUID 可解析时,还包含 account_idlist_emails 对象包含 idsubjectsender

returned

result 数组中的对象数量

limit

实际查询所使用的 limit

truncated

当可用的结果多于已返回的结果时,true请提高 limit 或缩小查询范围以获取剩余结果(SQLite 快速路径下是确定性的;AppleScript 回退路径下是尽力而为的启发式 — 见下文)

truncated 在 SQLite 快速路径上是确定性的(它内部会获取 limit + 1);在 AppleScript 回退路径上,它是一种基于 returned == limit 的尽力而为启发式。任何“枚举 → 批量处理”的调用方都应先检查 truncated,再假设自己已拿到完整集合。


安装

快速开始 上手 — 安装插件是受支持的路径,它会提供命令、安全规则、过期钩子与已签名的二进制。下方的内容均属于 高级 / 开发 路线:它只单独注册 MCP 服务器,属于功能较少的安装子集(缺少哪些部分见 插件 vs 仅 MCP,因为运行时没有机制会提示你)。

要求

  • macOS 13.0+

  • Xcode Command Line Tools(用于下方自行构建路线)

  • 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"
  1. 找到 CheAppleMailMCP,并为 Mail.app 启用权限

  2. 如果使用 Claude Code,还需添加 TerminaliTerm

完全磁盘访问权限(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 拒绝的错误消息会将这些候选者说明清楚——它 不会 自动锁定唯一确切的 App,因为 macOS 没有为此外提供可信的进程内 API(#214)。缺少完全磁盘访问权限时,读取工具会静默退回到较慢的 AppleScript 路径,而仅限 SQLite 的功能(projectionexport_emails_markdown)会失败。对于直接启动路径,使用 Developer ID 签名的构建可以让授权在版本升级后仍然保留 — 参见 签名与公证

引导式设置(#213)— 作为上述手动步骤的替代,二进制自带了设置助手中:

  • CheAppleMailMCP --setup 会打开一个功能窗口,实时显示完全磁盘访问状态(定时重新检测并参与授权操作的时刻变为“就绪 ✅”),还提供按需执行自动化检查,以及“打开完全磁盘访问设置”/“复制二进制路径”按钮。

  • CheAppleMailMCP --check-fda 会以无界面方式打印状态(并在授权被 拒绝 时打开设置面板)— 在终端或脚本中使用比较方便。

  • check_fda MCP 工具 可按需向 Claude 报告同样的状态(当仅 SQLite 的功能报错时调用它)。

这些辅助手段都无法替代那个必有的手动开关(Apple 将完全磁盘访问与辅助功能、屏幕录制一样归为仅可手动操作的项目),但它们可以让“我该怎么办?”变得清晰,并在你切换后立即给出实时反馈。

辅助功能(撰写,#175/#304 —— 它是独立于完全磁盘访问(Full Disk Access)的一项独立、可选授权。Mail.app 会将任何通过 AppleScript 注入的外发邮件正文包裹在 <blockquote type="cite"> 中,某些移动客户端会把它渲染为你自己文本的引用——而发送者在本地看不到这一点,因为这个包装的内联样式没有边框。自 #304 起,产生它的代码已不再存在:每个撰写工具的内存都取自于 Mail 自己的编辑器——compose_email / create_draft 在传出于 mailto: 交接;reply_email / forward_email 采用原生的回复/转发动作加粘贴——并通过键盘快捷键驱动保存/发送/附加,这需要辅助功能(系统设置 → 隐私与安全 → 辅助功能)权限,且必须授予与 FDA 相同的宿主进程(即你的终端 / Claude App)。check_accessibility MCP 工具--setup 窗口中的辅助功能行会报告此状态。没有该权限,这些工具现在会失败,而不是回退——因为已经没有第二条可回退的路径,调用如果无法干净地执行,就会返回一个具名错误,并将不会创建任何内容。该错误具体指向 open_email,它完全不需要 TCC 授权(它不能携带附件,你需要打开窗口自行保存或发送)。恰好有六种情况会拒绝调用:一个非 plainformat;空的主题;未授予辅助功能;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_freesanitize_links 参数,以及 CHE_MAIL_DISABLE_MAILTO_COMPOSE / CHE_MAIL_DISABLE_PASTE_REPLY 逃生舱口。有两项能力也随之不见了,说得直白些:没有可见窗口的撰写(这原本是逃生舱的初衷)已不可能;compose_email 还能向 Name <addr> 发送 —— 请改用 create_email,然后自己发送草稿。

Automation 的 TCC(-1743)和零 TCC 逃生舱

如果 AppleScript 能做功的工具报错 AppleScript error (-1743): Not authorized to send Apple events to Mail``,说明**此二进制**的“自动化”(Automation)权限缺失。已签名的 MCP 二进制程序之间有自己的**自动化授权** —— 其 TCC 身份与二进制程序的签名身份绑定(#211 的 FDA 教训,自动化轴),与您的终端的授权相互独立。笔者实机验证:osascript在你的 shell 中控制 Mail,并不能确保该二进制被授权。需要您在**系统设置 → 隐私与安全性 → Automation** 中手动授权——找到这个二进制 / 它的宿主(Claude Code 扩展:在 **Claude Code** 下)对应的条目,然后开启 Mail。如果出现条目,说明之前曾经被拒绝过,macOS 不会再弹出询问:可以运行tccutil reset AppleEvents`,然后再次尝试使用邮件工具以重新触发授权提示。这些授权是针对每个安装实例的,而更新二进制有可能会使它失效(#211)。

在该授权完成之前,它仍然可以工作 open_email:它会通过 LaunchServices(TCC Semantics,#287)在系统默认邮件客户端中打开一个不含 blockquote 引用的撰写窗口。注意 mailto 无法携带附件(RFC 5068)—— 请手动添加文件。

第 4 步:重启 Claude

# For Claude Desktop
osascript -e 'quit app "Claude"' && sleep 2 && open -a "Claude"

# For Claude Code - start a new session
claude

使用示例

自然语言(Claude Code:)

"List all my mail accounts"
"Show unread emails in Gmail inbox"
"Search for emails about 'quarterly report'"
"Send an email to john@example.com 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

bluegraygreennoneorangepurpleredyellow


性能与存储

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

✓ 当读取器不可用时

get_attachments

✓ 任何错误的回退(已执行)

save_attachment

✓ 任何错误的回退

get_email_metadata

✓(任何错误,自 #71 起)

对于 save_attachment 来说,快速路径比 AppleScript 快 10–100 倍(参见 #12 测量结果)。其他工具的速度提升比例取决于请求的形状;一般来说,大批量读取能获得最大的提升效果。

快速路径:

  • 宿主进程必须已被授予完全磁盘访问权限(系统设置 → 隐私与安全 → 完全磁盘访问)

  • Apple Mail 的本地存储位于固件内固定路径 ~/Library/Mail/V10/...

  • 邮件内容是否已经同步到本地的 .emlx 存储

EWS / Exchange 账户会有意绕过快速路径

Apple Mail 里的 Exchange(EWS)账户文件不产生 .emlx 文件 —— 邮件正文在服务器上,按需获取。对于这些账户,全部 8 个读取工具(包括 get_email_metadata,自 #71 款描述的回退)都会透明地降级为 AppleScript IPC(行为正确,只是较慢)。可能的体现:

  • 批量获取 500 封 EWS 邮件,会明显比获取 500 封 IMAP/Gmail 邮件慢

  • 这不是 bug —— 而是 Apple Mail 的存储结构限制(参见 #9

诊断快速路径绕过

当非 EWS 账户尝试快速路径失败时,错误会记录到 stderr(自 #69 起)。请在终端中运行该二进制文件并观察 stderr,以区分两种情况:

  • EnvelopeIndexReader init failed: ... —— 数据库不可达(常见原因是:缺少完全磁盘访问权限)

  • SQLite get_version fast path failed for rowId=N: ... —— 具体某个消息失败(例如,只有 .partial.eml、MIME 格式错误、文件尚未同步)

这两种情况在日志里都会透明地回退到 AppleScript 并记录 ... falling back to AppleScript,因此行为保持一致,同时又恢复正常。


故障排查

Problem

Solution

服务器断开连接

使用 swift build -c release 重新构建

不允许发送 Apple 事件

在“系统设置”>“自动化”中添加权限

Mail.app 无响应

确保 Mail.app 正在运行且已配置账户

命令超时

大型邮箱耗时更长;请尝试特定搜索

批量获取比预期慢

留意 stderr 中的 ... falling through to AppleScript 行。EWS/Exchange 账户始终会回退(请参阅性能与存储);其他账户若记录回退日志,则表明存在可修复的 .emlx 问题

save_attachment 失败并返回 -1728 "无法获取账户"-1719 "无效邮箱索引"

#173 起,两个错误都会附带一条可操作的提示,指出失败的引用(账户 / 邮箱 / 邮件)。常见原因:两个 Mail.app 账户共享相同的 display_name,或电子邮件格式的 account_name 映射到多个账户——请参见下文账户消歧


账户 ID 的获取 / Account Disambiguation

Mail.app 的 AppleScript account "<display_name>" 选择器在两个账户共享相同的 display_name不是唯一的——当 iCloud 捕获取别名将 Gmail 地址转发回自身,或 Google Workspace 与个人 Gmail 重叠时,这是常见的模式。任何使用 AppleScript 路由的工具(save_attachment 回退、get_emailmark_read 等)随后都会非确定性地选错账户 → 出现 -1728 / -1719 错误。

解决方案:传递 account_id(Mail 的全局唯一 UUID)加上 account_name。提供账户 id 后,save_attachment 改用 Mail 的 account id "<UUID>" 选择器,从而绕过歧义:

// Tool call: save_attachment with account_id
{
    "id": "273214",
    "mailbox": "[Gmail]/全部郵件",
    "account_name": "alice@example.com",
    "account_id": "C38E0583-47F8-4468-BE70-43155C15549D",  // ← disambiguates
    "attachment_name": "report.pdf",
    "save_path": "/tmp/report.pdf"
}

发现 account_id

  • search_emails 结果中 —— results 数组中的每个对象(一个 SearchResult)都带有与 account_name 旁边的 account_id(通过解码 SQLite mailboxes.url 的 authority 中的账户 UUID 填充) 推荐直接传递它。

  • 手动做法 —— 读取 ~/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(关于 #173173):当 account_name 包含 @(邮件形式,SQLite 路径工具如 search_emails 输出的形式)时,save_attachment 首先在 AccountsMap 中反向查询,并静默升级为 account id "<UUID>" 选择器(该升级记录到 stderr)。正好一个匹配 → 使用该 UUID;多个账户位于一个地址后面(iCloud 捕获型 + Gmail)→ 列出每个候选项的可操作错误,而不是原始 -1728;没有匹配 → 使用旧显示名路径,不变。边缘情况:如果 Mail 账户的 描述 恰好包含 @ 且恰好等于另一个账户的电子邮件,则首先在电子邮件命名空间中解析——请显式传递 account_id 以固定选择器。其他工具保持严格的 #101 之前的回退(跨工具扫描是 #176176)。

范围account_id 在引用邮件账户的 AppleScript 路由工具中接受。它始于 save_attachment(#101);#104 扫描随后添加了下述 13 个单消息 / 移动 / 中继 / 邮箱工具:

  • save_attachment(#101)—— 前体

  • PR-A —— 5 个单消息变更工具:mark_readflag_emailset_flag_colorset_background_colormark_as_junk

  • PR-B —— 3 个移动 / 销毁工具:move_email copy_emaildelete_email

  • PR-C —— 3 个消息中继工具:reply_emailforward_emailredirect_email

  • PR-D —— 2 个邮箱 CRUD 工具:create_mailboxdelete_mailbox

工具集自 #176 后进一步扩展:

  • [#20176] —— 将电子邮件 → UUID 的 resolveAccountIdForTool 阻塞点推广到所有 14 个 AppleScript 路由的写入处理程序(因此当 account_name 为电子邮件形式时,会解析到 UUID 选择器,而不只是接受的 account_id)。

  • [#20176] —— 通过 resolveMailboxRef / resolveMsgRefaccount_id 贯穿到读取工具的 AppleScript 回退链路(list_emails / search_emails / get_email / 头 / 源 / 元数据 / 附件 / get_unread_count)(之前推迟的 PR-E 现已完成)。

  • [#20179] —— get_special_mailboxes 接受 account_id / account_name,用于获取每个账户的特殊邮箱的真实名称。

  • [#20190] —— 账户级操作工具 check_for_new_mailsynchronize_account 增加了 account_id 逃生孔(synchronize_account 单独接受 account_id)。

仍然account_id 覆盖(已跟踪):getaccount_info/list_mailboxes`#202)。

compose_email / create_draft 不会出现 display_name 冲突缺陷——它们创建新的外发消息,而不是通过账户引用现有消息,因此永远不会发出 account "<display_name>" 选择器。多账户发件人选择现可通过可选的从地址参数(#131)——传递任何已配置的 Mail.app 电子邮件地址("alice@example.com" 或 RFC 5322 格式 "Alice <alice@example.com>")以设置外发消息的 sender;省略该参数则使用 Mail.app 的默认账户。使用 list_accounts 发现运行 Mac 上配置的地址。

跨账户移动 / 复制不支持 account_id#129)。move_emailcopy_email 接受单个 account_id,并在 msgRef目标 mailboxRef 上都贯穿了该 id。这种架构选择是正确的(移动保持在一个账户内,因为 Mail.app 的 AppleScript 动词 move msg to <mailboxRef> 要求目标邮箱必须相对于单个账户上下文表达)。Mail.app 的界面可通过拖放执行跨账户移动,但 AppleScript 路由的 move_email / copy_email 工具无法复制这一点——调用带有单个账户 account_idmove_email 并期望目标 to_mailbox 根据另一个账户解析时,会静默选择该名称的错误账户邮箱(如果两个账户恰好有一个)或抛出 -1719 "无效的邮箱索引"。如果您需要将邮件内容复制到另一个账户下,可以手动通过 save_attachment + compose_email 重建——注意,这不是真正的移动/复制:原始元数据(Message-ID、接收日期、标志、标签)和消息标识不会保留。


技术要求

  • 框架:ghcr.io/modelcontextprotocol/swift-sdk) v0.10.0

  • 读取路径:SQLite(Envelope Index)+ .emlx 文件解析器,以及对 EWS / 无法解析的 .emlx 的 AppleScript 回退

  • 写入 / 状态路径:通过 NSAppleScript 的 AppleScript

  • 传输:stdio

  • 平台:macOS 13.0+(Ventura 及更高版本)


签名与公证

分发的二进制文件是 Developer ID 签名并公证的,这不是表面功夫。需要 Full Disk Access(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>'

在您自己的机器上进行开发安装(快速——无需公证)

This is the translation. I need to check some details.

I skipped over some URLs/links and kept them intact, but I might have included wrong issue numbers in the translated text due to copying issues? Wait, the source has "#173" in the table and later "#101", "#104", "#176", "#180", "#179", "#191", "#202", "#131", "#129", "#211" etc. In my translation I need to preserve those numbers verbatim. I accidentally wrote "#173172" in the first row? No, I must be careful. Let me re-check my draft: In the row, I wrote "由于 #173" - but in the final output above, I see "由于 #173172"? Actually in my generated text above there was "关于 #173173" etc. I need to fix those accidental artifacts from translation.

Let me carefully reconstruct the output with the correct issue numbers:

  • In the table: "Due to #173"

  • Later: "behavior identical to pre-#101" -> 行为与 #101 之前完全相同

  • "#173" in the save_attachment exception -> #173

  • "the cross-tool sweep is #176" -> 跨工具排查见 [#176]

  • "(#101)" -> #101

  • "#104 sweep" -> #104

  • "#176 generalized" -> [#176]

  • "#180 threaded" -> [#180]

  • "#179 get_special_mailboxes" -> [#179]

  • "#191 account-level tools" -> [#191]

  • "tracked #202" -> [#202]

  • "#131" -> #131

  • "#129" -> #129

  • "#211" -> #211

I need to output the correct translation. Let me produce a clean final answer with all links intact and pure translation. Also, make sure the heading "## Account Disambiguation" translated as something like "## 账户消歧" (I used "关联" wrongly). Better: "账户消歧".

The title of the section: "## Account Disambiguation" -> "## 账户消除歧义" or "## 账户消歧". Let's use "账户消歧".

Also, I kept the "GXP14", "GXP15" placeholders verbatim, as required.

Let me re-translate fully and carefully, without errors.

One more thing: The original text says "The first grant is still manual" and "Signing makes that first grant permanent, not automatic." -> "签名使首次授权变得永久,而非自动。"

Also "14 AppleScript-routed write handlers" -> "14 个 AppleScript 路由的写入处理器".

I need to preserve the Markdown table exactly: two columns, same number of pipes.

Now produce final output| Problem | Solution | | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Server disconnected | Rebuild with swift build -c release | | Not allowed to send Apple events | Add permissions in System Settings > Automation | | Mail.app not responding | Ensure Mail.app is running with configured accounts | | Commands timing out | Large mailboxes take longer; try specific search terms | | Bulk fetch slower than expected | Watch stderr for ... falling through to AppleScript lines. EWS/Exchange accounts always fall back (see Performance & Storage); other accounts logging fallback indicate a fixable .emlx config issue | | save_attachment fails with -1728 "Can't get account" or -1719 "Invalid mailbox index" | Since #173, included in the table. |


Account Disambiguation

Mail.app's AppleScript account "<display_name>" selector is not unique when two accounts share the same display_name — a common pattern when an iCloud catch‑all alias forwards a Gmail address to itself, or when Google Workspace overlaps personal Gmail. Any AppleScript-routed tool (save_attachment fallback, get_email, mark_read, etc.) will non-deterministically pick the wrong account → -1728 / -1719 errors.

For The fix: pass account_id (Mail's globally unique UUID) alongside account_name. When provided, save_attachment uses Mail.app’s account id "<UUID>" selector instead, bypassing the ambiguity:

// Tool call: save_attachment with account_id
{
    "id": "273214",
    "mailbox": "[Gmail]/全部郵件",
    "account_name": "alice@example.com",
    "account_id": "C38E0583-47F8-4468-BE70-43155C15549D",  // ← disambiguates
    "attachment_name": "report.pdf",
    "save_path": "/tmp/report.pdf"
}

Discovering account_id:

  • From search_emails results — each object in the results array (a SearchResult) carries an account_id field alongside account_name. (This is populated by decoding the account UUID from the SQLite mailboxes.url authority via MailboxURL.decode — Mail.app’s storage convention encodes the account UUID in the mailbox URL authority; there is no direct SELECT mailboxes.account_id.) Recommended: pass it through directly.

  • Manually — read ~/Library/Mail/V10/MailData/Signatures/AccountsMap.plist. The top-level keys are the UUIDs; the AccountURL value contains the matching email address percent-encoded in the authority.

  • In AppleScript — tell application "Mail" to get id of every account returns the UUID list.

Backward compatibility: account_id is optional. When omitted (or empty), tools fall back to the legacy account "<display_name>" path — behavior identical to pre-#101 — with one exception for save_attachment (#173): when account_name contains @ (email-shaped, the form that SQLite-path tools such as search_emails emit), save_attachment first reverse-looks-it-up in AccountsMap and silently upgrades to the account id "<UUID>" selector (the upgrade is logged to stderr). Exactly one match → that UUID; several accounts behind one address (iCloud catch-all + Gmail) → an actionable error listing each candidate instead of a raw -1728; no match → the legacy display-name path, unchanged. Edge: a Mail account whose description legitimately contains @ and equals another account's email address now resolves in the email namespace first — pass account_id explicitly to pin the selector. Other tools keep the strict pre-#101 fallback (the cross-tool sweep tracks #176).

Scope: account_id is accepted across the AppleScript-routed tools that reference mailboxes by account. It began with save_attachment (#101); the #104 sweep then added the 13 single-message / movement / relay / mailbox tools below:

  • save_attachment (#101) — the precursor

  • PR-A — 5 single-message mutation tools: mark_read, flag_email, set_flag_color, set_background_color, mark_as_junk

  • PR-B — 3 movement/destruction tools: move_email, copy_email, delete_email

  • PR-C — 3 message-relay tools: reply_email, forward_email, redirect_email

  • PR-D — 2 mailbox CRUD tools: create_mailbox, delete_mailbox

The surface has thus grown beyond the #104 set:

  • #176 — generalized the email→UUID resolveAccountIdForTool chokepoint across all 14 AppleScript-routed write handlers (so an email-shaped account_name resolves to the UUID selector, not just an accepted account_id).

  • #180 — thread account_id through the read-tool AppleScript fallbacks (list_emails / search_emails / get_email / headers / source / metadata / attachments / get_unread_count) via resolveMailboxRef / resolveMsgRef (the previously deferred PR-E is now done).

  • #179get_special_mailboxes accepts account_id / account_name for per-account special-mailbox display names.

  • #191 — the account-level action tools check_for_new_mail and synchronize_account gained the account_id escape hatch (synchronize_account accepts account_id alone).

Still not covered by account_id (tracked): get_account_info / list_mailboxes (#202).

compose_email / create_draft do not exhibit the display_name-collision defect — they make new outgoing message rather than referencing existing mail by account, so they never emit an account "<display_name>" selector. Multi-account sender selection is now available via the optional from_address parameter (#131) — pass any one of your configured Mail.app email addresses ("alice@example.com" or RFC 5322 form "Alice <alice@example.com>") to set the sender of the outgoing message; omit to use Mail.app’s default account. Use list_accounts to discover the addresses configured on the running Mac.

Cross-account move/copy is not supported via account_id (#129 — confirmed from #127). move_email and copy_email accept a single account_id, which is threaded through both the source msgRef and the destination mailboxRef. The architectural choice is correct (movement stays within one account, because Mail.app’s AppleScript verb move msg to <mailboxRef> requires the destination mailbox to be expressed relative to a single account context). Mail.app’s UI permits cross-account move via drag-and-drop, but the AppleScript-routed move_email / copy_email tools cannot replicate that — calling move_email with account_id of one account while expecting the destination to_mailbox to be resolved against a different account silently picks the wrong account’s mailbox of that name (if both accounts happen to have one) or raises -1719 "Invalid mailbox index". If you need a copy of the message’s contents under a different account, you can manually rebuild it via save_attachment + compose_email — note this is not a true move/copy: original metadata (Message-ID, received-date, flags, labels) and message identity are not preserved.


Technical Details

  • Framework: MCP Swift SDK v0.10.0

  • Read path: SQLite (Envelope Index) + .emlx file parser, with AppleScript fallback for EWS / unparseable .emlx

  • Write/state path: AppleScript via NSAppleScript

  • Transport: stdio

  • Platform: macOS 13.0+ (Ventura and later)


Signing & Notarization

The distributed binary is Developer ID‑signed and notarized, and that is not cosmetic. The fast read path needs Full Disk Access (FDA), and macOS TCC keys an FDA grant to the binary's designated requirement. For an ad‑hoc binary that requirement is the cdhash, so every version bump invalidated the grant and you had to re-add the binary to the Full Disk Access list after each release. A stable Developer ID signature keys the grant to the signing identity instead, so it survives version bumps (#211) — that signature, not notarization, is what delivers the persistence.

Notarization matters for quarantined-launch paths: a browser download or the .mcpb (Claude) install, where Gatekeeper assesses the binary on first launch. The plugin setup’s curl + exec path sets no quarantine attribute, so Gatekeeper never fires there. We notarize anyway so the published release asset is safe to run by any means.

The first grant is still manual. FDA (kTCCServiceSystemPolicyAllFiles) has no programmatic request API — an app can only deep-link you to the settings pane. Signing makes that first grant permanent, not automatic.

One-time setup (maintainers)

# 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>'

Dev install on your own machine (fast — no notarization)

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 Release。没有证书的 fork 也可以使用 SKIP_CODESIGN=1 ./scripts/release.sh vX.Y.Z 发布一个未签名的开发版。


贡献

欢迎贡献!请随时提交 Pull Request。


许可证

MIT License - 详情请参阅 LICENSE


作者

Che Cheng 创建(@kiki830621

如果你觉得这个项目有用,不妨考虑给给点一个 star!

A
license - permissive license
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
1dResponse time
4dRelease cycle
44Releases (12mo)
Commit activity
Issues opened vs closed

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Enables AI assistants to interact with Apple Mail through natural language, providing comprehensive email management including reading, searching, composing, organizing, and analyzing emails across all configured accounts. Includes an expert skill system that teaches intelligent email workflows and productivity strategies.
    26
    193
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables unified email management across Gmail, Outlook, iCloud, and IMAP providers with tools for search, send, organize, and batch operations via natural language.
    58
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables using Apple Mail accounts to search, read, manage, draft, and send messages from Codex or Claude Code locally.
    MIT

View all related MCP servers

Related MCP Connectors

  • Manage Gmail end-to-end: search, read, send, draft, label, and organize threads. Automate workflow…

  • Read, search, send, organize, draft and schedule email across your inboxes from any MCP client.

  • Let ChatGPT, Claude & Cursor use your Mac: email, calendar, iMessage, Teams, files. Local, free.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/PsychQuant/che-apple-mail-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server