关于 macOS Apps
macOS Apps 是面向 macOS 的本地 MCP 服务,可通过单一接口访问邮件、日历、提醒事项、备忘录等应用。它以指针和单独限量读取内容,并对写入进行分级、审计及删除预览。
主要功能
- 01访问多种 macOS 应用
- 02搜索结果以指针形式返回
- 03按需单独读取受限内容
- 04写入分级并记录审计日志
- 05删除、移动和发送默认预览
适用场景
- 01查询日历安排与空闲时段
- 02筛选待回复邮件并打开草稿
- 03预览并撤销邮件移动操作
MCP 相关
作为本地 MCP 服务,向客户端暴露 macOS 应用的搜索、读取和写入工具。可按应用关闭适配器,并通过只读模式禁用写入。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
macos-apps-mcp
一个本地 MCP 服务,可在 macOS 上用于 Mail、Calendar、Reminders、Notes、 Messages、Contacts、Photos、Safari、Music 和 Shortcuts。搜索和列表操作会返回指针:一个 id、一行摘要,以及可在相应应用中打开该项目的链接。正文需单独获取,且有长度上限。 写入分级并记录审计日志;删除、移动和发送操作会先预览。仅支持 macOS。使用 Python + FastMCP + uv。请参阅 DESIGN.md 和 CHANGELOG.md。
你可以这样提问
- “这周我的日历上有什么安排?周四下午什么时候有空?”
events、free_busy - “哪些邮件需要回复?为第一封邮件起草一封回复。”
mail_needs_response、mail_reply。回复会在 Mail 中作为草稿打开,不会发送。 - “把收件箱里的新闻邮件移到 Archive。先给我看看。”
mail_search、move_mail、mail_undo。默认会先预览移动操作。mail_undo可以撤销该操作,但受下文所述已知限制影响。
安全与隐私
- 本地运行。 不会收集遥测数据,也不会自行发起网络请求:仅使用本地 unix socket(从 shim 到 daemon)和 127.0.0.1 上的仪表板。调用时,只有三个工具会通过应用访问 Mac 之外的内容:发送工具(默认关闭)通过 Mail 发送邮件,
safari_open打开网页,run_shortcut运行可能会访问网络的 Shortcut。 - 三级权限。 分为读取、写入和外发。发送工具在你按应用启用之前不会提供:
macos-apps-mcp allow-send mail(daemon)或MACOS_APPS_ALLOW_SEND=mail(stdio)。MACOS_APPS_READ_ONLY=1只注册读取工具,且优先于这两项设置。 - 先试运行。
delete_draft、move_mail、trash_mail、mail_undo、send_mail、reply_all、forward_mail、delete_reminder、delete_event、delete_note和update_note的dry_run默认值为 true。其他写入操作在调用时会直接执行。mail_reply只会打开草稿。 - 审计日志。 所有写入操作都会记录到
~/.local/state/macos-apps-mcp/audit.jsonl(若设置了$XDG_STATE_HOME,则记录在其下)。 按 id 更新或删除项目时,也会记录该项目的原始状态。audit工具可读取日志。 - 邮件恢复。 移动或删除 Mail 邮件前,每封邮件都会以
.eml文件的形式复制到~/.local/state/macos-apps-mcp/backup/mail/下。mail_undo可撤销整批操作。没有工具会清空废纸篓。 - 已知限制。 如果 Mail 只下载了部分邮件,备份中不会包含其附件。通过 Gmail 标签文件夹移动或移入废纸篓的操作会被拒绝,因为 Gmail 会在约 2 分钟后将其还原。
sent: true表示 Mail 已接受该邮件,并不代表邮件已送达。所有 Mail 相关说明。 - 仪表板。 在已签名的
.appdaemon 下,本地页面(仅限 127.0.0.1)会显示每项写入操作、其修改内容,以及每批 Mail 操作对应的备份。以下为虚构数据:

前 5 分钟
需要 macOS、Python ≥ 3.12 和 uv。uv 提供 uvx。此软件包已发布到 PyPI。将 MCP 客户端指向 uvx。它会获取软件包并运行,因此无需克隆,也无需指定 venv 路径:
{
"mcpServers": {
"macos-apps": {
"command": "uvx",
"args": ["macos-apps-mcp"]
}
}
}
如果 Claude Desktop 找不到 uvx,请将 "command" 设为它的完整路径:在终端运行 which uvx(默认安装 uv 时会显示 /Users/you/.local/bin/uvx)。
对于 Claude Code:
claude mcp add macos-apps --scope user -- uvx macos-apps-mcp
或者,不使用 claude mcp add,而是安装 Claude Code 插件。它会启动同一个服务,并添加一项技能
来指导你使用 dry-run 工作流。在 Claude Code 中,运行 /plugin marketplace add elfensky/macos-apps-mcp,然后运行
/plugin install macos-apps@macos-apps-mcp。
下文的 CLI 命令(allow-send、dedupe-mail)也以相同方式运行:uvx macos-apps-mcp allow-send。
先运行 doctor。 doctor 工具会报告已授予的权限,且不会打开任何对话框(每项 Automation 授权都会实时读取;未运行的应用会显示 not_running)。你在屏幕前时,doctor(request=True) 会打开 EventKit 和 Automation 对话框。服务需要:
- EventKit:日历和提醒事项。
- Automation:每个应用各打开一个对话框:Mail、Notes、Contacts、Safari、Messages、Music。
- Full Disk Access(可选,不会打开对话框:在“系统设置”→“隐私与安全性”→“完全磁盘访问权限”中,为启动服务的应用开启此权限):Messages 内容、Photos、Mail 索引读取、提醒事项标签和子任务,以及 Notes 快速路径。没有此权限时,Notes 和部分 Mail 读取会使用较慢的路径。其余操作会返回错误或
coverage说明。
使用 stdio 时,macOS 会将每项授权授予启动服务的应用(Claude Desktop、Terminal、VS Code),因此每个客户端都需要单独授权。已签名的 .app 守护进程则为所有客户端共用一组授权:docs/DAEMON.md。
只加载你会用到的应用。设置 MACOS_APPS_ADAPTERS_OFF=photos,music 后,不会为这些应用注册工具,因此客户端上下文中保留的工具会更少。使用守护进程时,请参阅关闭适配器。
从源代码开始(开发): 如果要开发服务本身,请使用此路径。
git clone https://github.com/elfensky/macos-apps-mcp && cd macos-apps-mcp
uv sync
然后将 MCP 客户端指向项目自己的 venv python——这样可以确保运行结果一致,并使用锁定版本的 PyObjC wheel:
{
"mcpServers": {
"macos-apps": {
"command": "/absolute/path/to/macos-apps-mcp/.venv/bin/python",
"args": ["-m", "macos_apps_mcp"]
}
}
}
为什么不选 apple-mcp?
Dhravya Shah 开创的 apple-mcp 是一个适用于所有 Apple 应用的 MCP 服务。它现在已归档。它会直接返回完整的笔记和邮件正文。本服务会返回指针,仅在请求时获取正文,并按等级限制每项写入操作。详见 CREDITS.md。
遇到问题?运行 doctor,并将其输出粘贴到 GitHub issue 中。报告会显示进程路径,发布前请先检查。
工具
读取操作会返回指针;每个适配器的结果都有上限。正文/内容需通过单独的、有上限的获取操作读取。设置 MACOS_APPS_READ_ONLY 后会完全跳过写入/操作(见下文)。
日历和提醒事项 — 读写(EventKit)
| 工具 | 参数 | 说明 |
|---|---|---|
events | when = today | week | YYYY-MM-DD | 列出事件指针 |
free_busy | start、end(ISO),可选的 calendars ID | 返回时间范围内合并后的忙碌时段和空闲间隙;不含事件详情 |
reminders | due = today | overdue | this-week | 一个列表名称 | 以 {results, coverage?} 的形式列出提醒事项;每个结果包含 folder、tags 和 parent(只读;需要 EventKit 和完全磁盘访问权限) |
calendars / reminder_lists | — | 用于指定写入目标的容器(ID + 名称) |
create_event / update_event | 标题、开始时间、结束时间(ISO)、日历、地点、备注、all_day、recurrence、alarms(开始前的分钟数,最多 5 个;全天事件:从当地午夜起算,-540 = 当天 09:00) | update 会按 ID 完全替换;但对于 alarms:省略时保留事件的闹钟,[] 则移除闹钟 |
delete_event | ID、span、dry_run | dry_run 只预览,不会删除;实际删除后会验证事件已删除(仅删除该次事件,不删除整个系列) |
create_reminder / update_reminder | 标题、到期时间、列表名称、备注、priority(0–9)、开始时间、recurrence | update 会按 ID 完全替换 |
create_reminder_list | 名称 | 在默认账户中新建列表;如果名称完全重复,则拒绝创建 |
complete_reminder | ID | 标记为已完成;完成父项时,会在 subtasks 下列出仍未完成的子任务;如果无法读取提醒事项存储,则拒绝执行且不做任何更改(需要 EventKit 和完全磁盘访问权限) |
delete_reminder | ID、dry_run(默认 true)、with_subtasks | 按 ID 删除并确认已删除;如果父项有子任务,则必须设置 with_subtasks=true,否则会拒绝执行,因为删除父项也会删除子任务(需要 EventKit 和完全磁盘访问权限) |
事件和提醒事项指针包含 folder,其值为日历或列表 ID;free_busy(calendars=…) 和写入工具(calendar=、list_name=)均接受此值。
提醒事项的 标签 和 子任务(tags、parent = 父项的 ID)从提醒事项存储中读取,且为只读;需要完全磁盘访问权限。如果无法读取该存储,仍会返回提醒事项,并由 coverage 说明原因。没有公共 API 可以写入标签或嵌套提醒事项,因此这里没有相应工具。
写入操作通过 名称或 Pointer.id 指定容器;如果名称不唯一,则会报错,而不会猜测。重复规则 使用 RFC 5545 RRULE:FREQ、INTERVAL、COUNT、UNTIL、BYDAY(月度和年度规则支持 2TU 或 -1FR 等序数)、BYMONTHDAY、BYMONTH、BYYEARDAY 和 BYSETPOS,例如 FREQ=MONTHLY;BYDAY=2TU;COUNT=6。重复提醒事项必须有到期日期。BYWEEKNO、BYHOUR、BYMINUTE、BYSECOND 和 WKST 会按名称拒绝(EventKit 会保存 BYWEEKNO,但只展开第一个日期);任何超出范围的值或 RFC 5545 禁止的组合也会被拒绝;写入后会再次检查每个部分。不符合规则的开始日期仍会被接受——RFC 5545 将其计为第一次出现——事件结果会说明第一次出现是额外的一次。
邮件 — 先按 ID 读取 + 创建草稿并打开(自动化)
默认情况下禁用发送:读取、create_draft 和 mail_reply 都不会发送邮件——它们会打开撰写窗口供你查看。外发操作(send_mail、reply_all、forward_mail)存在,但只有在操作员通过 MACOS_APPS_ALLOW_SEND 明确选择启用后才会运行;即使如此,dry_run 仍默认为 True。
| 工具 | 参数 | 说明 |
|---|---|---|
mail | subject-OR-sender 子串 | 匹配收件箱;id = 稳定的 RFC822 message-id,message:// 深层链接 |
mail_body | id, mailbox | 单封邮件的纯文本,有长度限制并标记截断;mailbox 是从 mail_search 结果中原样传回的 folder 值(或规范名称)——任何邮箱都可以,不只是收件箱 |
mail_search | subject, from_, to, mailbox, account, since, until, unread, flagged, has_attachments, body, limit | 通过 Envelope Index 进行索引搜索(静态存储——除非 account= 是显示名称,否则不会启动 Mail;显示名称会通过 Mail 解析;UUID 只使用纯 sqlite,未知名称则会报错);每封邮件返回一个结果(优先选择 INBOX);has_attachments 不包括内嵌图片;body 尽力匹配(仅限已索引内容) |
mail_index_bodies | rebuild | 从静态存储的 .emlx 构建/刷新启用后才使用的全文搜索(FTS)正文索引;可恢复,限制大小;跳过尚未下载的邮件(设计如此,覆盖不完整) |
mail_thread | id, limit(默认 100) | 完整会话,按时间从早到晚;包括你发送的邮件;已去重;超过 limit 时舍弃最早的邮件(回复时读取会话) |
mail_overview | — | 列出所有邮箱及其总数和未读数,未读优先;包括 Junk/Trash/All Mail;实时统计(存储的计数会过时),按不同邮件计数;账户名称来自 Mail(会启动它),无法连接时使用 UUID 代替;On My Mac 始终显示名称 |
mail_attachments | mailbox(搜索结果中的 folder,原样传入——或 inbox/sent/drafts/trash/junk),可选 query | 按邮件列出附件名称/大小/下载状态;适用于草稿 |
mail_needs_response | — | 列出可能需要你回复的收件箱邮件,并按 reason 排序(已标记 / 未读且直接发送 / 未回复且直接发送);仅读取邮件头,不读取正文 |
mail_awaiting_reply | days(1–365,默认 3) | 列出你发送时间至少为 days 天且尚未收到回复的邮件(基于真实的 In-Reply-To/References 线程),按时间从早到晚排列,原因是 awaiting-reply |
create_draft | to, subject, body | 打开草稿供审核——绝不发送;返回一个定位符 |
mail_reply | message_id, mailbox, reply_body, include_quote | 原生线程回复(设置 In-Reply-To/References),引用原文并打开供审核——绝不发送 |
drafts | — | 将 Mail 草稿列为指针(id + subject — to 收件人) |
delete_draft | id, dry_run | 按 message-id 删除一封草稿;dry_run 预览操作 |
create_mailbox | name(使用 / 创建嵌套邮箱),account | 创建文件夹;自动创建缺失的父文件夹。没有对应的删除功能——delete <mailbox> 无法通过脚本执行,因此只能在 Mail.app 中删除。在 Gmail 账户中,新文件夹是标签,move_mail 会拒绝操作 |
move_mail | ids(≤25), from_mailbox, to_mailbox, dry_run | 移动/归档/重新归档;移动前会备份并记录每封邮件,因而 mail_undo 可以将其还原。dry_run 默认为 True。跨账户移动后只保留一个副本。如果任一端是 Gmail 标签文件夹(移出标签会留下副本)或源是统一名称,则会拒绝操作 |
trash_mail | ids(≤25), mailbox, dry_run | 软删除,也是唯一可用的删除方式——Mail 的 delete 会将邮件移至账户的 Trash,其脚本字典中的任何功能都无法从那里彻底删除邮件。可通过 mail_undo 撤销;清空 Trash 需在 Mail.app 中自行操作。dry_run 默认为 True |
mail_undo | receipt, dry_run | 反向重放 move_mail/trash_mail 回执;撤销操作本身也会备份,且可以再次撤销。对于移至 Gmail 标签或统一名称的回执,操作会被拒绝,并说明如何手动还原 |
update_mail_status | ids(≤25), mailbox, read, flagged, flag_color | 标记已读/未读、加标记(+颜色)。重新执行并传入相反值即可撤销,因此 dry_run 默认为 False |
mail_duplicates | limit | 仅报告——按邮箱统计冗余副本数量及最严重的案例。清理请使用下方的 dedupe-mail CLI;此工具无法删除邮件 |
send_mail | to, subject, body, cc, bcc, html, from_address, dry_run | 受 MACOS_APPS_ALLOW_SEND 门控;dry_run 默认为 True |
reply_all | message_id, mailbox, body, include_quote, dry_run | 受门控;使用原生线程标头 |
forward_mail | message_id, mailbox, to, dry_run | 受门控;原文和附件均会完整转发——没有附言参数(编写正文会破坏原文和附件,已通过设备验证) |
消息 — 通过 chat.db 获取内容(只读;需要“完全磁盘访问”权限)
| 工具 | 参数 | 说明 |
|---|---|---|
messages_chats | — | 对话列表(id + 名称);不包含内容,无需 FDA |
messages_search | query, limit | 搜索消息文本(解码 attributedBody),按时间从新到旧排列 |
messages_with | contact (phone/email), country, limit | 与某人的近期消息;country = 国家/地区电话区号或 2 字母地区代码(默认使用区域设置,绝不使用 +1) |
message_body | id | 根据 guid 获取一条消息的完整文本 |
备忘录 — 读取 NoteStore.sqlite + 按需获取正文
| 工具 | 参数 | 说明 |
|---|---|---|
notes | title, snippet 或 body words | {results, truncated?, plane?, coverage?} — 匹配的备忘录(id + 摘要),优先显示标题/摘要匹配项,然后是仅正文匹配项;在“完全磁盘访问”权限范围内于内存中搜索正文(跳过已锁定的备忘录);正文词语仅按空格和标点拆分,因此在未加空格的中文或日文文本中,只有通过标题或摘要才能找到词语;忽略变音符号和智能标点差异 |
notes_all | — | 每条有效备忘录(id +“帐户 / 文件夹”+ 摘要),不包括“最近删除”中的备忘录 |
note_bodies | ids (≤ 50) | 按需获取纯文本正文 → [{id, body}] |
create_note | title, body, optional folder name | 返回稳定的 x-coredata id;如果文件夹名称未知或有歧义,则拒绝操作,不会猜测 |
update_note | id, title, body | 按 id 完整替换标题和正文;保留稳定 id,并在写入后验证 |
delete_note | id, expect_title, dry_run | 移至“最近删除”;dry_run 可预览 |
其他只读上下文
| 工具 | 参数 | 返回内容 |
|---|---|---|
contacts | name substring | 联系人卡片(姓名、组织、首个电话号码 + 电子邮件) |
photos | search string | {results, truncated?} 指针(标题 · 文件名 · 日期),按时间从新到旧排列,最多 25 个;匹配文件名、标题、描述、关键词、地点、人物和标签。id 为 Photos uuid;仍接受旧版 <UUID>/L0/001 id |
photo_albums | — | {results, truncated?} 相册指针(id、title (n photos)、文件夹路径),最多 100 个 |
photo_info | photo id | 日期、位置、地点、人物、部分 EXIF 信息、像素尺寸、类型;绝不返回图像字节或缩略图 |
safari_tabs | — | 所有打开的标签页(url + 标题) |
shortcuts | name substring(留空 = 全部) | 用户的 Shortcuts;id = 稳定 UUID,shortcuts:// 深层链接 |
ping / now / doctor | —(doctor 可选用 request=True 触发权限提示) | 健康检查 / 时间 / 权限诊断 |
audit | 可选的 since (ISO datetime) | 最近的写入审计条目(按时间从新到旧排列)——macos-apps-mcp 所做的更改,包含更改前/后的指针(足以手动撤销) |
usage | — | 各工具调用次数 + 从未使用的工具列表,用于清理很少使用的工具 |
操作(写入)
| 工具 | 参数 | 说明 |
|---|---|---|
create_contact | given_name, family_name, organization | |
run_shortcut | name 或 id, 可选的 input_text | 运行一个 Shortcut;返回有长度限制的输出片段 |
safari_open | url | 在新标签页中打开;纯主机名 → https://;仅允许 http/https |
export_photo | photo id, dest_dir(位于允许的根目录内) | 将编辑后的静态图像(未编辑时则为原图)另存为新文件,文件名为 <original stem>_<id prefix>.<ext>,绝不覆盖现有文件,大小上限为 250 MiB;仅存在于 iCloud 的照片状态为 absent(不会下载任何内容);拒绝导出视频;Live Photo 仅导出静态图像;仅新增,不覆盖,并记录审计 |
只读模式
设置 MACOS_APPS_READ_ONLY=1(或 true / yes)以仅注册读取工具——跳过所有写入和操作工具,作为安全部署保护。(读取操作仍可能打开应用 / 读取本地存储。)
关闭适配器
设置 MACOS_APPS_ADAPTERS_OFF=photos,music(以逗号分隔的适配器名称列表),即可不为这些应用注册工具。
在守护进程模式下,文件 ~/.local/state/macos-apps-mcp/adapters_off 保存相同的列表,并在重启后生效。详情请参阅:docs/DAEMON.md。
控制面板
在守护进程模式下,http://127.0.0.1:47632 会显示审计记录、健康状态、适配器开关、使用情况和 Mail 备份。运行 macos-apps-mcp dashboard,即可使用页面密钥打开控制面板。详情请参阅:docs/DAEMON.md。两张截图使用的都是虚构数据。
清理重复邮件
Mail 会累积同一封邮件的冗余副本——在 UI 中拖动会复制而非移动,Gmail 会在标签和「所有邮件」下同时显示同一封邮件,迁移也会在两个账户中留下副本。读取工具已经会将它们合并,因此这里仅涉及服务器上实际存储的内容。向会话请求 mail_duplicates(),查看问题,然后在终端中清理:
macos-apps-mcp dedupe-mail # preview every mailbox — changes nothing
macos-apps-mcp dedupe-mail --verbose # also list the sets it will NOT touch
macos-apps-mcp dedupe-mail --execute --mailbox=<folder-url> # do one mailbox for real
出于与 allow-send 相同的原因,这是 CLI 命令,而不是工具:数千次删除操作要交给一个有 30 秒上限的串行工作进程执行,需要数小时,而这是人类主动发起的工作。它只会合并字节完全相同(大小和发送日期都相同)的副本——Mail 无法指定某个特定副本,因此会保留 Mail 留下的那个;字节相同正是确保安全的条件。不一致的副本组会被计数、保留,并列在 --verbose 输出中。
冗余副本会被移至废纸篓,每次分小批处理,每批都会生成自己的 mail_undo 回执。清空废纸篓需要你在 Mail.app 中操作——这里的任何操作都无法彻底删除邮件。
此操作很慢:每组重复邮件约需 1 分钟;对于真实的 IMAP 账户(Mail 的 whose 查询会扫描整个邮箱),因此大文件夹需要数小时。请在可以一直运行的终端中操作,每次处理一个邮箱。操作可恢复——每次运行都会根据 Mail 的索引重新计算计划,因此中断不会造成任何损失,重新运行只会发现需要处理的项目变少。Mail 无法删除的副本组会被报告出来,不会在后台静默重试。
出站(发送)模式
发送功能默认关闭——服务只会创建草稿,绝不会发送。通过以下命令开启:
macos-apps-mcp allow-send mail # or: messages / mail,messages / all / off
macos-apps-mcp allow-send # print the current setting
此操作会持久保存启用设置并重启守护进程,以便重新注册(注册发生在导入时,因此无法避免重启——之后请重新连接 MCP 客户端)。这是一个CLI 命令,而不是工具:此限制是对您授权的保护,因此模型既不能自行授权发送,也不能在您的会话过程中擅自重启服务。
同样的设置也可通过环境变量 MACOS_APPS_ALLOW_SEND 配置,stdio 服务会读取此变量(只要设置了该变量,其值始终优先于持久化设置):
| 值 | 效果 |
|---|---|
| 未设置(默认) | 完全不注册发送工具 |
mail | 仅启用 Mail 出站功能(send_mail、reply_all、forward_mail) |
mail,messages | 指定适配器(逗号分隔列表) |
1 / true / yes / all | 启用所有适配器的出站功能 |
MACOS_APPS_READ_ONLY 始终优先:同时设置两者时,不会注册任何发送工具。
发送工具接受 dry_run 参数,其默认值为 True**——**这与按 ID 定位的删除操作恰好相反。删除的目标是读取操作已返回的项目;而发送操作会构造收件人,收件人错误才是关键风险。试运行完全不会调用 Mail,并会报告解析后的信封;传入 dry_run=False 才会实际发送。reply_all 是唯一的例外:其试运行会读取原始邮件中的实际收件人(to/cc),但绝不会发送,因为调用方无法预先确定的正是这组收件人。
send_mail / reply_all / forward_mail 成功时返回的结果(sent: True)表示 Mail 已接受邮件——并不表示邮件已送达。经设备验证:格式完全正确的邮件在 send 返回后,仍可能在 Mail 的发件箱中滞留数分钟而未送达;而一封收件人为空、被困住的邮件可能会堵塞发件箱,导致之后有效的发送操作排队等待,最终也无法发出。每个结果还会报告 outbox_pending,即 Mail 当前发件箱中的邮件数量;当该值大于零时,结果会附带一条 note,说明邮件尚未确认送达,并提示检查 Mail ▸ 发件箱。
运行 doctor 工具,检查发送功能是否已实际启用——deployment.outbound 会列出当前启用发送功能的所有适配器(若无则为 []),其值依据上述相同的注册逻辑生成,而非重新读取原始环境变量,因此与已注册的工具始终一致;deployment.outbound_note 会以文字说明当前状态。
在守护进程部署模式下,在 MCP 客户端配置中设置 MACOS_APPS_ALLOW_SEND 不会产生任何效果:客户端的 env 块会传给 shim,而不是在导入时读取该变量的长期运行守护进程;随附的 LaunchAgent plist 也没有 EnvironmentVariables 键。这正是 allow-send 命令存在的原因——它会写入守护进程可读取的状态(~/.local/state/macos-apps-mcp/allow_send,固定写入用户主目录,确保写入该设置的 shell 与读取它的 launchd 进程始终一致),并且重启后仍会保留,不像 launchctl setenv。请参阅 docs/DAEMON.md(“守护进程下的出站(发送)模式”)。
开发
uv sync
uv run pytest # unit tests (mock at the adapter boundary)
uv run pytest -m integration # real macOS / EventKit / TCC — run manually, never in CI
uv run ruff check . # lint (config in pyproject.toml)
uv run ruff format . # format
uv run macos-apps-mcp # run the server (stdio)
ruff(lint + format,行长度为 88)和 pytest 是 CI 的门禁检查——完整工作流见 CONTRIBUTING.md。
相关先行项目与致谢
macos-apps-mcp 建立在先前的工作之上——它借鉴的 Apple Mail MCP、引用的 EventKit/Photos 服务、开创统一 Apple MCP 模式的项目,以及它依赖的 FastMCP / PyObjC / MCP 规范。请参阅 CREDITS.md。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。