Cupertino
Cupertino
让你的智能体为你日常使用的 Apple 应用干活。
针对你 Mac 上已有的 Apple 应用的 MCP 服务器,以及一个签名应用,它一次性授予这些服务器权限,而不是每个服务器各授一次——面向任何会说 MCP 的智能体,而非某一个宿主。
非官方。 与 Apple 无关。这些只是驱动你 Mac 上已有的应用。
使用场景
看看我在工作收件箱里是怎么写的,然后用同样的语气起草这封回复。
从我的邮件、笔记和日历中汇总所有与 Atlas 发布相关的内容。我还欠别人什么?
把昨天客户邮件线程中的待办事项变成提醒事项,截止日期设在周五。
每一条都带着一个约束——账户、日期界限、过滤器。这正是朴素的 osascript 路径要用 74 秒才能回答、或者会答错的部分,也是服务器能占有一席之地的原因:它保存了模型否则每个会话都得重新推导的东西。测量数据在 docs/verify.md;替代方案的代价在 docs/alternatives.md。
最后一条需要在 Reminders 上打开写入门控。写入按功能面默认关闭,直到你打开;这个开关决定变更类工具是否会被注册——关闭写入的智能体根本看不到它们存在。
Related MCP server: apple-mcp
功能面
功能面 | 包 | 状态 |
已实现 — 18 个工具,搜索/读取/附件 + 门控写入 | ||
Notes | 已实现 — 12 个工具,搜索/读取/附件 + 门控写入 | |
Reminders | 已实现 — 11 个工具,列表/搜索/日期 + 门控写入 | |
Calendar | 已实现 — 9 个工具,范围/搜索/重复 + 门控写入 | |
Contacts | 已实现 — 7 个工具,将句柄解析为姓名 + 门控写入 | |
Messages | 已实现 — 6 个工具,聊天/搜索/解码文本 + 门控发送 | |
Safari | 已实现 — 6 个工具,历史记录/标签页/阅读列表,只读 | |
— | 共享:osascript 边界、TCC 感知的错误、只读 SQLite |
每个功能面都是独立的服务器、独立的 npm 包,因此宿主只加载它想要的工具。它们共享同一个 bundle 和同一个 Full Disk Access 授权——这正是它们整合在一起的全部原因——参见 docs/distribution.md。
快速开始
每个服务器都发布在 npm 上,可以直接用 npx 运行——对于 Claude Code,就是在项目旁边放一个 .mcp.json:
{
"mcpServers": {
"apple-mail": {
"command": "npx",
"args": ["-y", "@mgcrea/mcp-apple-mail"]
},
"apple-notes": {
"command": "npx",
"args": ["-y", "@mgcrea/mcp-apple-notes"]
}
}
}这些包采用 MIT 许可证,不需要许可证密钥。它们真正需要的是一个权限;在 npm 上,你把这个权限授予启动它们的任何东西——你的编辑器、你的终端——这正是签名版 Cupertino.app 想要避免的代价:把一次 Full Disk Access 授权交给一枚已公证的二进制文件,而不是每个宿主各持一次。参见 docs/licensing.md。
或者从源码运行它们:
git clone https://github.com/mgcrea/mcp-cupertino.git
cd mcp-cupertino
pnpm install
pnpm build然后用绝对路径将你的宿主指向 packages/<surface>/dist/cli.js。
写入默认关闭,除非你主动要求——参见配置。
改而通过菜单栏应用运行时,所有服务器都经由 bridge 路由,因此 Full Disk Access 授予的是 Cupertino,而不是启动服务器的那个编辑器:
make run # build Cupertino.app, point it at packages/*/dist, launch it
make smoke # handshake every server through the bridge仓库里已检入的 .mcp.json 就是按那条路径接线的。单独运行 make 会列出所有目标。
注意服务器名称并不相同。像上面那样手工接线时,服务器叫 apple-mail,运行在宿主进程所拥有的任何授权之下。由 Cupertino 接线时它叫 cupertino-mail,因为该条目指向应用的 bridge,运行在应用的授权之下。两种部署各有两个名字,你可以同时拥有两者。应用只会碰它自己的 cupertino-* 键——属于其他服务器的 apple-mail 条目会被原样保留。
Cupertino 是机器配置,而不是项目依赖,因此它应当放在按用户划分的配置中:Claude Desktop、Cursor、LM Studio 和 Windsurf 各一个文件,CLI 则使用 --scope user——应用的复制命令用的就是它。故意不用 --scope project,因为那会写入一个本应提交的 .mcp.json——该条目是指向某台 Mac 上某个 bundle 的绝对路径,背后是某个人的 Full Disk Access 授权;把它交给队友毫无用处,给他们也不明智。
哪些客户端会被写入、哪些只得到一条命令,与流行度无关。应用只会在配置是严格 JSON 且服务器位于 mcpServers 下时进行合并;Visual Studio Code 的配置是 JSONC,Codex 的是 TOML,重新序列化任何一种都会删掉手写维护的文件中的注释。Claude Code 的 ~/.claude.json 是严格 JSON,但它包含凭据,且正在运行的会话会并发地写入它,所以从菜单栏做读-改-写可能会丢掉别人的改动。上述三者得到的是一条可粘贴的命令。ChatGPT 则完全不在其中:它只接受远程 HTTP 连接器,根本无法启动本地 stdio 服务器。
这个仓库自己的 .mcp.json 是例外,它特意把服务器命名为 cupertino-*-dev:它指向 apps/apple/.build,所以在应用上开发就意味着让开发构建和已安装构建并存。Claude Code 会把两个作用域中同名的服务器报告为冲突,而不是选择其中一个,因此这个后缀正是让两者都能继续可用的原因。
Cupertino.app 需要 macOS 26 或更高版本——它的图标是 Icon Composer bundle,更老的系统无法渲染。服务器本身是纯 Node,没有这样的最低版本要求;只有菜单栏应用有。
权限
两个独立的 macOS 授权,它们落在启动服务器的那个进程上——你的编辑器、你的终端或 Cupertino——绝不会落在 Mail、Notes 或 Reminders 本身上。
授权 | 用途 |
Full Disk Access | 索引通道:Mail 搜索、附件字节 |
Automation(按目标应用,弹出提示) | Apple Events 通道:账户、邮箱、所有写入 |
Contacts(弹出提示) | Contacts 功能面——它的存储不依赖 Full Disk Access |
System Settings → Privacy & Security → Full Disk Access → 添加启动应用的那个程序,然后重启它。把它授予 Mail.app 没有作用;需要该权限的是读取方,而不是 Mail。
没有 Full Disk Access 时哪些仍可用:
功能面 | 没有该授权时 |
仅账户、邮箱和写入——搜索回退到 Apple Events,约需 74 秒 | |
Notes | 约 5k 条笔记以下完全可用;只有附件字节需要该授权 |
Reminders | 可用,但全天日期和子任务需要 store——没有 store 连容器都无法列出 |
Calendar | 什么都不能用。 这是唯一一个没有足够快的 Apple Events 读取路径可作回退的功能面——一次 90 天范围查询就要 3.4 秒——所以每次读取都需要该授权。写入仍然可用。 |
Contacts | 什么都不能用——但它并不需要 Full Disk Access。它的存储位于独立的 Contacts 权限之后,macOS 会弹出提示请求该权限,而不是让你去找设置面板。写入还需要 Automation。 |
Messages | 完全不能用。 不存在 Apple Events 读取路径——即使 Messages 正在运行,它也会回答“Application isn't running”——所以没有第二条通道,也没有降级模式。仍然可以尝试发送,但没有 store 来挑选目标或进行核对,它通常根本无从寻址。 |
Safari | 实时标签页,而且只有这些。 这是唯一一个两条通道互不为回退的功能面:Apple Events 看到的是当下打开的内容,文件通道看到的是其余的一切。历史记录、书签和阅读列表都需要该授权。 |
需要索引的工具不会在索引缺失时消失——工具列表是 allowWrites 的纯函数,不依赖其他任何东西,因为 MCP 客户端会缓存它。它们会返回一个结构化的 degraded 结果,指出缺失了什么。apple_mail_diagnostics/apple_notes_diagnostics 会报告哪条通道处于可用状态,以及如何授予缺失的权限。
工具
读取工具始终会被注册。写入工具在启用写入之前是不可见的——而不仅仅是被拒绝。
始终可用 | 写入门控 |
|
|
|
|
|
|
Notes
始终可用 | 写入门控 |
|
|
|
Reminders
始终可用 | 写入受限 |
|
|
|
日历
始终可用 | 写入受限 |
|
|
|
list_events 会展开重复事件,因此每周例会每周返回一次。每个结果都带有展开已知覆盖的时间窗口,并在范围超出时设置 truncated,而不是返回较短的结果——事件列表过短与空闲时段无法区分。
通讯录
始终可用 | 写入受限 |
|
|
|
resolve_handles 将电话号码和电子邮件地址转换为姓名,这正是"信息"界面可读的原因。请阅读每个结果上的 status,而不是假设一定返回了姓名:unknown 很常见且不是错误,ambiguous 表示两个联系人共享该号码,因此不返回姓名而不是猜测。
没有删除工具。 通讯录的脚本字典没有任何删除命令,而且由于存储按策略为只读,所有写入都通过 Apple Events 进行。参见 docs/contacts.md。
信息
始终可用 | 写入受限 |
|
|
|
只有一个写入工具,因为字典只有一个可用命令。 sdef 列出了 send、login 和 logout;另外两个会让用户在其拥有的每台设备上退出 iMessage。没有可暴露的编辑、删除、标记已读或回复动词。
这是唯一一个 Apple Events 仅作为写入通道、别无其他的界面——通过它的每次读取都会失败,因此在 APPLE_MESSAGES_ALLOW_WRITES 关闭时不会发送任何 Apple Event,也不会请求任何自动化授权。
send_message 优先使用 list_chats 中的 chatRef 而不是原始句柄,因为"信息"拒绝为脚本枚举参与者:现有对话是唯一可以可靠寻址的目标。Apple Events 不会返回其发送内容的标识符,因此结果会与 chat.db 进行对账——reconciliation: "matched" 携带真实的 message 引用,而 "pending" 表示"信息"已接受发送但尚未写入该行。Pending 不是失败,重试会发送两次消息。
在所有历史消息中,约有 3% 的消息将其文本仅保存在 SQL 无法访问的归档 NSArchiver blob 中——而且 Apple 在 2026 年初停止写入纯文本列,因此对于近期内容,这一比例约为 100%。此服务器会解码这些内容;每个结果上的 textSource 会说明由哪条通道回答。参见 docs/messages.md。
Safari
始终可用 | 写入受限 |
| — 无 |
|
只读,写入列有意留空。 打开 URL 或添加到阅读列表是一个会导航真实可见浏览器的 Apple Event,此界面上从未探测过任何写入。
list_tabs 是整个工具包中唯一无需完全磁盘访问即可工作的工具——它只需要自动化授权,而且 Safari 必须正在运行。标签页的 history 字段为 null 表示在历史记录中未找到,绝不是"从未访问过":只有约 55% 的打开标签页能匹配到历史记录行,因为重定向、会话参数和从未提交到历史记录的页面都会产生一个根本不在其中的 URL。
没有 do JavaScript 工具。 该动词需要"允许 Apple Events 中的 JavaScript",这是 Safari 开发者菜单中的一个开关,不是 TCC 授权,且其自身状态无法可靠读取。发布它意味着报告一个表面健康、但其最强大的能力却静默失败的界面。参见 docs/safari.md。
所有名称均以 apple_mail_ / apple_notes_ / apple_reminders_ / apple_calendar_ /
apple_contacts_ / apple_messages_ / apple_safari_ 为前缀。
配置
仅环境变量——这些服务器自身不持有任何机密,因此没有配置文件。前缀为 APPLE_MAIL_、APPLE_NOTES_、APPLE_REMINDERS_、APPLE_CALENDAR_、APPLE_CONTACTS_、
APPLE_MESSAGES_ 或 APPLE_SAFARI_。
变量 | 默认值 | 说明 |
|
| 是否注册变更类工具 |
| 全部 | 账户白名单(名称或 UUID)——读取侧的控制 |
|
| 附件唯一允许写入的目录 |
|
| 任何列表的上限 |
|
|
|
|
| 每次 Apple Events 调用的超时时间 |
|
| 向 stderr 输出详细日志 |
allowWrites 控制变更操作,但在"邮件"上更大的影响范围是读取整个归档——这正是 *_ACCOUNTS 的用途,且它只在唯一一处强制执行,因此没有任何查询路径能绕过它。"邮件"还接受 *_ROOT、*_ENVELOPE_INDEX、*_DEGRADED_MAX_MESSAGES、*_BODY_MAX_BYTES、
*_BODY_SCAN_MAX、*_BODY_SCAN_BYTES 和 *_MAILBOX_CACHE_TTL_MS;参见 packages/mail/src/config.ts。
应用
菜单栏是 Cupertino 的全部界面——没有 Dock 图标,也没有主窗口。
区域 | 回答的问题 |
完全磁盘访问 | 是否已授权,以及打开正确"系统设置"面板的按钮 |
每个界面一个面板 | 每个应用的自动化状态、同意提示和写入开关 |
连接 | 当前哪个客户端正在与哪个服务器通信,以及它已调用了多少工具 |
MCP 客户端 | 一键配置 Claude Desktop、Cursor、LM Studio 和 Windsurf;为 Claude Code、VS Code 和 Codex 提供可复制命令 |
活动… | 打开一个窗口,实时列出每次工具调用 |
活动窗口仅记录工具名称——绝不记录参数、消息内容或结果。它回答了"助手刚才对我的邮件做了什么?",也是这些服务器运行在一个可见应用下、而不是运行在生成它们的编辑器中原因。
每个界面的写入默认关闭,直到你打开它们,而开关决定变更类工具是否注册——关闭写入的助手甚至看不到它们存在。
为什么是单一应用
完全磁盘访问是一个不可分割的整盘授权。按界面授权不会带来任何隔离,而且每次都要多跑一趟"系统设置",因此每个界面都打包在一个名为 Cupertino 的已签名、已公证应用中。docs/distribution.md 还记录了为什么 Mac App Store 无法承载其中任何内容,以免这个问题被重新提起。
但授权并不是该应用持有的唯一东西,也不是在应用下而非编辑器下运行服务器的唯一原因:
授权落在 Cupertino 上 | 而不是落在生成服务器的编辑器上,也不会波及该编辑器运行的每个扩展和任务 |
可见的审计轨迹 | 活动窗口实时列出每次工具调用;编辑器内的服务器是不可观测的 |
写入按界面关闭 | 开关决定变更类工具是否注册 |
| "邮件"上的影响范围是归档,而不是变更 |
结果说明可信度 |
|
四个界面,一次授权 | 这才是上述不可分割性的真正回报 |
docs/alternatives.md 是该列表的诚实版本:还有哪些工具可以为助手读取 Apple 邮件,以及那些工具在哪些方面更领先。
文档
如何分发,以及为什么不走 App Store | |
覆盖哪些 surface,以及各自的开销 | |
什么开源、什么出售、什么换来信任 | |
还有谁能读取 Apple Mail,以及我们在哪里落败 | |
正文搜索这一路径,以及它是怎么定下来的 | |
Apple Notes 的 phase-0 测量结果 | |
Apple Reminders 的 phase-0 测量结果 | |
Apple Messages:测量、解码器、发送 | |
Apple Calendar 的 phase-0 测量结果 | |
Safari 的 phase-0 测量结果 | |
Mail 观察到的 | |
对照真实索引校验 Mail 服务器 |
进行中
pnpm build # every package
pnpm test # every package
pnpm typecheck
pnpm lint
pnpm format营销网站是独立 workspace,采用手动部署:
pnpm --filter @mgcrea/cupertino-website dev # astro dev
pnpm --filter @mgcrea/cupertino-website build # static build它由 .idea/design/ 中的设计画布构建而成,并从 packages/*/src/tools/(而不是本文件)读取工具数量——参见 apps/website。
Swift 部分则直接是 xcodebuild,由 Makefile 指名调用,而不是由 Makefile 包装:
make app # build Cupertino.app (Debug)
make run # build, point at packages/*/dist, launch
make smoke # handshake every server through the bridge
make stop # quit and remove the socket应用的截图是抓拍出来的,不是手工截的——apps/apple/Screenshots/ 存放配置和已提交的 golden 基线,网站负责渲染输出:
make screenshots # capture, gate against the goldens, compose both sets
make screenshots-check # gate only — fails if the UI drifted
make screenshots-update # accept the captures as the new goldens (review the diffs first)
make screenshots-selftest # prove the gate fails when it should一次运行会在每次拍摄时接管指针和当前激活的应用,所以运行期间不要使用这台机器。它需要终端有屏幕录制权限,而不是 Cupertino 自己有。make screenshots-doctor 会检查这一点,以及另外两件否则会静默失败的事情:说明字体是否能解析,输出尺寸是否符合应用商店的接受范围。
截图展示的是 apps/apple/Cupertino/DemoSeed.swift 里的 fixture 数据,而不是这台 Mac 上的真实状态:在 -ScreenshotMode 下,应用不启动 host,只植入自己的日志和会话,然后通过查表回答权限与商店问题。没有这些,截图就会泄露某一台笔记本的 TCC 状态,并把它的主目录打印到营销网站上。
Phase-0 探针是仓库范围、只读的。它们需要被测 surface 的权限,并把输出压缩为计数、耗时和 DDL:
pnpm probe:mail # Envelope Index — needs Full Disk Access
pnpm probe:mail-body # which lane can search message bodies — needs Full Disk Access
pnpm probe:notes # Notes — the Apple Events half runs without it
pnpm probe:reminders # Reminders — the store path is a glob, so finding it is itself privileged
pnpm probe:messages # chat.db — no Apple Events read lane exists, so this one needs the grant
# --send-target=<handle> also checks the send lane's targeting, without sending
pnpm probe:calendar # settles whether Calendar has a file lane at all
pnpm probe:safari # History.db, and the Reading List hiding inside Bookmarks.plist
pnpm probe:contacts # the resolver Messages needs — its own TCC grant, not Full Disk Access每一个被探测的 surface 如今都对应一个包。Safari 是只读的——它不注册任何修改类工具;这是记录在案的决定而不是遗漏:打开 URL 或加入阅读列表会真实地驱动可见的浏览器,因此从未被探测。参见 docs/safari.md。Messages 只注册了一个写入工具 send_message,这已经是它脚本字典的全部能力。
每个探针都是降级处理而不是直接退出——应用没有运行,或权限未授予,都会被报告为一条 finding——并且除非传入 --launch,否则它们不会启动任何应用。其共享机制在 scripts/lib/probe-kit.mjs。
Release 按包分别打标签,所以标签名就能表示它发布什么:mail-v1.1.0、reminders-v1.1.0、calendar-v1.1.0、core-v1.1.0。应用则打 app-v1.1.0,走自己的发布轨道——在 GitHub Release 上附一个已签名、公证的 Cupertino.zip,以及它的 SHA-256。参见 docs/distribution.md。
仓库名是
mcp-cupertino;npm 包保持@mgcrea/mcp-apple-*,因为那是人们在 npm 上搜的东西。两个名字都不是关键。真正要紧的是 bundle identifierio.mgcrea.cupertino,因为改动它会让每个用户的完全磁盘访问权限失效。
许可证
有两份,因为两半并不相同。
部分 | 许可证 |
| MIT — 库,可自由引入 |
| source-available — 可读、可审计、可编译;二进制再分发权保留 |
已签名、公证的构建产物 | 出售,遵守随附的 EULA |
Cupertino 请求完全磁盘访问权限,因此源码必须保持可读——这正是这样的授权应当被容忍的,而阅读源码的意义所在。不过,运行它就是另一回事:许可证检查写在源码里,所以任何构建都会要一个 key,无论是你的还是我们的。出售的是公证过的二进制及其背后的维护。而服务器端都是 MIT 许可,完全不需要 key 就能自行运行。完整论述见 docs/licensing.md。
This server cannot be installed
Maintenance
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
- AlicenseNot gradedqualityCmaintenanceA local MCP server that provides a secure bridge for automating macOS applications like Notes, Calendar, and Mail through AppleScript. It uses template-based execution and policy-based allowlists to enable safe, structured interaction with system tools.MIT
- AlicenseBqualityDmaintenanceMCP server for macOS Apple apps. Enables read/write access to Notes, Reminders, Calendar, Contacts, and Safari using SQLite and JXA, all running locally.428122MIT
- AlicenseAqualityCmaintenanceMCP server for privacy-gated local Apple data access including Mail, Messages, Notes, Calendar, Contacts, Photos, Reminders, Voice Memos, and iCloud Drive.301MIT
- AlicenseNot gradedqualityAmaintenanceA collection of MCP servers for Apple macOS apps (Mail, Contacts, Notes, Memory, Messages, Calendar, Reminders) enabling AI assistants to read, search, create, and update data via JXA, SQLite, and EventKit.12MIT
Related MCP Connectors
Search, read, and write your Apple Notes from ChatGPT/Claude via a local Mac agent + MCP relay.
Personal assistant MCP server with search, execute, packages, jobs, secrets, and integrations.
MCP connector for iMessage & Contacts via a local Mac agent + Vercel relay
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/mgcrea/mcp-cupertino'
If you have feedback or need assistance with the MCP directory API, please join our Discord server