Cupertino — MCP servers for your everyday Apple apps

Cupertino

20 min read Original article ↗

Put your agent to work
in your everyday Apple apps.

Cupertino puts the Apple apps on your Mac behind MCP, for any agent that speaks it. One Full Disk Access grant, held by a signed menu-bar app you can watch — instead of one for every editor that happens to spawn a server.

macOS 26 or later · 30-minute trial, no account · 30-day refund

MIT servers · unofficial, not affiliated with Apple

What did I promise Marie last week, and is any of it on my calendar?

What your agent called

  • Contacts apple_contacts_search_contacts read
  • Mail apple_mail_search_messages read
  • Mail apple_mail_get_thread read
  • Calendar apple_calendar_list_events read

reads only · one grant · nothing left the Mac

In use

What you actually
ask for.

Every one of these carries a constraint — an account, a date bound, a filter. That is the part a shell script gets slowly or gets wrong, and the reason a server earns its place: it holds what the model would otherwise have to re-derive every session.

Look at how I write in my work inbox, then draft this reply in the same voice.

Mail

Scoped to one account. APPLE_MAIL_ACCOUNTS bounds what can be read, not just what can be written.

Pull together everything about the Atlas launch from my mail, my notes and my calendar. What do I still owe people?

Mail Notes Calendar

Three surfaces, one grant — the payoff of shipping them in one bundle.

Find every unread message from my accountant this quarter.

Mail

A filter. This query shape costs 74 seconds over Apple Events; the index lane answers it in milliseconds.

What is actually on my calendar next week once the repeating meetings are expanded? Block two hours before the release.

Calendar needs the write gate

Repeats expanded, and the window the answer covers reported with it — a short list is otherwise indistinguishable from a free afternoon.

Turn the action items from yesterday's client thread into reminders, due Friday.

Mail Reminders needs the write gate

Reads one surface, writes another. Both write gates off by default.

What did Marc say about the deposit? He texted me sometime last spring.

Messages Contacts

Searches by name rather than by phone number, and reads the messages SQL alone cannot see — since March 2026, that is all of them.

Did anyone ever reply to the invoice I sent on the 3rd?

Mail

The whole pitch, for someone who has never edited a config file.

Writes are off until you turn them on, per surface — and the toggle decides whether the mutating tools are registered at all, so an agent with writes off is never told they exist.

The window

One pane per
surface.

Every Apple app Cupertino brokers gets its own pane: whether it can be driven, whether its store can be read, who is talking to it, and what it can currently say.

Cupertino's Mail pane: automation allowed, the Envelope Index readable, writes enabled, 20 tools · 3 prompts · 3 resources, and five connected clients.
The tool, prompt and resource count comes from asking the server itself, live — so flipping the write toggle above it changes what it reports.

claude

“What did Anna send me about the lease, and is it in my notes?”

apple_mail_search_messages read · index · 97 ms

apple_notes_search_notes read · events · 97 ms

Two different lanes, and it does not matter which. Mail came off the Envelope Index, because asking Mail.app the same thing takes 74 seconds. Notes came off Apple Events, because below roughly 5k notes the grant buys nothing there — so it is not asked for.

The grant

One key to the
whole house.

Full Disk Access is indivisible, and it lands on whatever process launched the server — your editor, your terminal, never Mail itself. A grant for mail is already a grant for Messages, Safari history and SSH keys. Granting it to Mail.app does nothing: the reader needs the permission, not Mail.

Every host asks for its own 4 grants

  • Cursor whole disk
  • Claude Desktop whole disk
  • Visual Studio Code whole disk
  • Terminal whole disk

One signed app holds it 1 grant

  • Cursor
  • Claude Desktop
  • Visual Studio Code
  • Terminal

Cupertino.app

whole disk
granted once

Automation is prompted separately, one row per target app. The bundle identifier io.mgcrea.cupertino is the TCC identity, so the grant survives every update.

Tool registration

The list gets shorter,
not greyer.

The tool list is a pure function of allowWrites and nothing else, because MCP clients cache it. With writes off the mutating tools are never registered — the host cannot call what it was never told about, so there is nothing to refuse.

always registered · 11

  • apple_mail_search_messages
  • apple_mail_list_messages
  • apple_mail_count_messages
  • apple_mail_query
  • apple_mail_get_thread
  • apple_mail_get_message
  • apple_mail_get_message_source
  • apple_mail_list_attachments
  • apple_mail_list_accounts
  • apple_mail_list_mailboxes
  • apple_mail_diagnostics

write-gated · 10

  • apple_mail_send_message
  • apple_mail_reply_to_message
  • apple_mail_forward_message
  • apple_mail_update_draft
  • apple_mail_set_message_flags
  • apple_mail_move_messages
  • apple_mail_create_mailbox
  • apple_mail_delete_messages
  • apple_mail_check_for_new_mail
  • apple_mail_save_attachment
  • not registered · the host never sees these names

Mutation is one control. On Mail the larger blast radius is reading an entire archive — that is what *_ACCOUNTS is for, and it is enforced in exactly one place so no query path escapes it.

Activity

You can watch it
the whole time.

Every tool call, live, grouped by the client that made it. It is the answer to “what did the assistant just do with my mail?” — and the reason the servers run under an app you can see rather than inside whichever editor happened to spawn them.

The menu bar shows the same thing one level up: which client is talking to which server right now, and how many tools it has called. None of it is sent anywhere. The one connection Cupertino can make is the update check, it is off until you turn it on, and it carries no identifier — which CI checks against the built binary rather than the source.

By default none of it outlives the app. Turn on the audit log and it is kept on disk instead — append-only, each record hashed against the one before it, so an edited or missing line shows. That catches tampering by something that does not know it is a chain; it is not proof against anyone who can write the file, and the app says so in those words rather than implying more.

The app

A window, not
a background process.

Cupertino runs as a menu-bar app you can open. That is the point of holding the servers in one signed process rather than spawning them inside whichever editor asked first: there is something to look at, and it tells you what happened.

Cupertino's chat pane: Mail selected, fifteen of twenty-one tools loaded against a 1,800-token budget, one apple_mail_search_messages call showing the arguments the model chose and the three messages it returned, then a one-sentence answer.

Try it without wiring anything

Pick a surface and ask in plain words. Apple's on-device model calls the same tools an editor would, over the same connection, and every call is shown with the arguments it invented — above the answer, because the calls are the evidence and the sentence is the model's account of them. The model holds 4,096 tokens, so the header says how much of that the loaded tools are spending. Nothing leaves the Mac, and it works before you buy a licence.

Cupertino's log showing one turn: a Contacts search, two Mail calls and a Calendar range query, all reads.

One question, end to end

The same pane as below, seeded with a single turn: “What did I promise Marie last week, and is any of it on my calendar?” Contacts leads, because the question names a person and every other surface is addressed by handle.

Cupertino's activity log: timestamped calls across mail, notes, reminders and calendar, with a refused write to Reminders in red.

The log, live

The same pane again, this time across everything a working day sends at it. Tool, prompt and resource names with the arguments each was called with — message contents and results stay off unless you ask for them — and a refused write shows up as a refusal.

Cupertino's connections pane: eighteen live sessions from claude-ai, claude-code and cursor-vscode across mail, notes, reminders and calendar.

Every session, ungrouped

One row per connection, because each client opens one per server it is wired to — and a client you have run several times over opens several. Three clients is eighteen rows here, and this is the pane that shows them.

Cupertino's Claude Code pane: the path to .claude.json, and a row per surface — cupertino-mail, cupertino-notes, cupertino-reminders, cupertino-calendar, cupertino-contacts — each naming the bridge command it writes and badged as configured, with every installed client listed in the sidebar beside it.

Wired into your editor, not inside it

Claude Code, Claude Desktop, Cursor, LM Studio, Windsurf, VS Code and Codex are all configured with one click — merged into the config you already have, backed up first, every other key left alone. Nothing is left to paste. Each client gets a pane showing the file: what would be written, what is under those keys now, and the servers in there that Cupertino did not write. Turn a surface off and the write takes it back out.

Cupertino's Notes pane with Allow writes unchecked, its capabilities card reading seven tools, one prompt and three resources, and Safari dimmed in the sidebar as a switched-off surface.

Writes are a separate switch

The Mail pane from the top of the page, on a surface with writes off. Notes reports seven tools where Mail reports twenty: the mutating tools, and the prompts that end in one, are not registered at all. An agent is not refused — it is never told they exist.

Latency

Read the index.
Then say how fresh it was.

Same query, same mailbox. Apple Events makes Mail.app do the work one message at a time; the index lane reads the Envelope Index directly. Reading the index is the easy half — Mail owns that file and is still writing to it, so every result carries how old the index was and whether the read could see the write-ahead log at all.

Apple Events search 74,000 ms

drawn to scale — 0.13% of the bar above

A search result carries indexAgeSeconds, and a warning when the index could only be opened immutable — which skips the write-ahead log, so the newest mail is exactly the mail most likely to be missing. A fast wrong answer is the failure mode every indexed reader shares; saying so is the part that is optional.

Without the grant, Notes still works fully below roughly 5k notes; Mail search falls back to the 74-second path. Tools that need the index never disappear — the tool list is a pure function of allowWrites and nothing else, so they return a structured degraded result naming what is absent.

Safari

Read the page,
without the skeleton key.

Safari's own way to let a program read a page is a single switch that grants every program access to every tab, and whose state cannot be read back. Cupertino ships a Safari extension instead: you allow it one website at a time, in Safari, and take it back the same way.

RouteWhat it can readTaking it back

Allow JavaScript from Apple Events

Safari's built-in route, and a skeleton key. Any process that can send Apple Events can then run script in any tab. Its state cannot even be read, so nothing can tell you whether it is on.

every site, every tab one hidden switch

Apple's Safari MCP server

safaridriver --mcp, in Safari 27. Built for the page you are developing: it opens its own automation window with none of your logins, history or tabs, so it cannot read what you are browsing and does not try to.

a separate automation window one switch, Developer settings

Accessibility

Not a route at all. Safari renders pages out of process and exposes no web area, so the lane that reaches other apps' windows reaches no part of a web page.

nothing —

A Safari extension

What Cupertino does. Safari asks per website and you answer per website — the permission is visible in the browser you already trust, and revoking it is one click in the same place.

the sites you allow per site, in Safari

Apple's server and this one are not rivals for the same job. Theirs drives a fresh WebKit window for the page you are building; nothing in it is yours yet. This one reads the Safari you already use — what you visited, what is open, what you saved for later — and hands an assistant the page in front of you. Run both: one for the code, one for the browser.

Three lanes, three permissions

History and bookmarks

Full Disk Access

everything about the past

Open tabs

an Automation grant

what is open right now, and nothing it says

Page contents

the extension, per site

what a page actually says

They see almost disjoint things and fail independently, so one of them being unavailable is normal rather than broken. An ungranted Safari server is not a slower one — it is a smaller one.

It is a snapshot, and it says so

The extension captures a page when it loads; nothing reaches into Safari to fetch a fresh copy. So a result is what that page looked like then, and every one carries the time it was taken and how long ago that was. Read something captured twenty minutes ago and it tells you it is twenty minutes old, rather than letting an assistant describe a page you have already left as the one on your screen.

Surfaces

Nine surfaces.
One bundle.

Each of these brokers one Apple app, and all but Simulator are their own npm packages, so a host loads only the tools it wants — and write tools against Mail and against Reminders are not the same risk. They share one bundle and one grant, which is the whole reason they live together.

The deep one. Search, read, threads, attachments and message source across accounts and mailboxes; ten mutating tools behind the write gate.

11 always registered · 10 write-gated

@mgcrea/mcp-apple-mail

Search, read and attachments across every folder and account, with six mutating tools behind the write gate.

7 always registered · 6 write-gated

@mgcrea/mcp-apple-notes

Lists, due dates and search across every account, with five mutating tools behind the write gate.

6 always registered · 5 write-gated

@mgcrea/mcp-apple-reminders

Ranges, search, free time between events, and repeating events expanded properly, with three mutating tools behind the write gate.

7 always registered · 3 write-gated

@mgcrea/mcp-apple-calendar

Turns a phone number into a name, and creates or edits a card. Reads need no Automation prompt at all; only the two write tools do.

5 always registered · 2 write-gated

@mgcrea/mcp-apple-contacts

Reads iMessage, SMS and RCS straight from chat.db — including the messages SQL cannot see — saves attachments, and sends, behind the write gate.

6 always registered · 2 write-gated · 1 opt-in

@mgcrea/mcp-apple-messages

The places you saved: favourites, Guides and recents, with real coordinates and addresses — including the ones filed in no Guide, which the app itself only shows in a union view. Saves and removes favourites behind the write gate, and — when Cupertino is hosting it — saves places and files them into Guides through the app's own interface.

8 always registered · 5 write-gated

@mgcrea/mcp-apple-maps

History, live tabs, the Reading List — and, through a Safari extension you enable per website, what a page actually says, plus clicking and typing on it. Opens a URL or saves one for later behind the write gate.

8 always registered · 5 write-gated · 1 opt-in

@mgcrea/mcp-apple-safari

An iOS Simulator is a Mac window, and Simulator.app bridges the simulated device's accessibility tree into it. This reads an iOS app's own controls as named, pressable elements in iOS points — the same coordinates the simctl-based tools use — and behind the write gate presses, taps, swipes and types, with no WebDriverAgent runner to keep alive. Scoped to the Simulator alone; no switch widens it.

4 always registered · 7 write-gated

—

Mail without the grant — Accounts, mailboxes and writes only — search falls back to the 74-second path.

Notes without the grant — Fully usable below roughly 5k notes with no grant at all — only attachment bytes need it.

Reminders without the grant — Workable over Apple Events — the file lane is what makes search scale.

Calendar without the grant — Nothing — the only surface with no Apple Events read path fast enough to be a fallback.

Contacts without the grant — Nothing — but it asks for the Contacts permission rather than the whole disk, and unlike Full Disk Access that one prompts.

Messages without the grant — Nothing at all. Every read through Messages' own scripting interface fails, so this is the one surface with no second lane to degrade to.

Maps without the grant — Nothing at all — Maps is not scriptable, so the grant is the only way in.

Safari without the grant — Live tabs and page contents — the only things in the whole bundle that work with no Full Disk Access at all. Tabs need an Automation grant; page contents need the extension, allowed per website.

Simulator without the grant — Which simulators CoreSimulator knows and which are booted, read from its own plists. Everything on the device's screen needs Accessibility, and refuses by name until it is given.

Capabilities

Three more,
switched off.

These broker the system rather than an app, and the contract is different in every way that matters. Each arrives off until you switch it on, each needs its own permission rather than the shared one, and none of them is an npm package — nor could be. The grant lives in the app, so a published package would hold nothing.

A picture of a window your assistant is already working with — Mail, Safari, Calendar — written to disk and handed back as a path. Only the apps Cupertino brokers: never an arbitrary app, window, display or region. Nothing is raised or focused, and a window sitting behind another app still captures its own content.

2 always registered · 0 write-gated · 1 opt-in

Without the grant — Nothing — Screen Recording is the only way in, and it takes effect on relaunch.

Your Mac's speakers and microphone. List the audio devices, read and set the volume, mute, switch to your headphones — none of which needs any permission — and, behind a separate switch that is off by default, record a memo to a file. Recording is never hidden: macOS shows its orange indicator, naming Cupertino, for as long as it runs.

3 always registered · 4 write-gated · 3 opt-in

Without the grant — Devices, volume and mute all work with no grant at all. Only recording needs the Microphone permission.

The lane for apps that have no other one. Maps ships no scripting dictionary at all, so Apple Events cannot write to it — not slowly, not at all — and the accessibility interface is the only way a place gets saved. Reads a window as named, addressable controls rather than pixels, and behind the write gate, presses them. Scoped to the apps Cupertino brokers unless you widen it, which is the switch that lets it drive an app you are building.

8 always registered · 12 write-gated

Without the grant — Which apps are running, and nothing else. Accessibility is the grant that matters here rather than Full Disk Access, and every read past the app list refuses by name until it is given.

Status

Servers
Open source, MIT. All 12 run from source today. ↗

App
Source-available, Developer ID signed, notarized and stapled, hardened runtime throughout. ↗

Network
Makes one network connection, and only if you ask for it: the update check. No analytics, no licence server, no phone-home. Verified in CI against the built binary, not the source. ↗

Platform
macOS only. It drives the apps on your Mac; there is nothing to port.

Distribution
Not on the App Store — the sandbox denies ~/Library/Mail whatever you grant. A stapled zip and a Homebrew cask instead. ↗

Release
The signed app ships from GitHub releases. The MCP servers are MIT and published on npm under @mgcrea, so a host can load one without the app. The app carries them too, and the Full Disk Access grant lives in the app either way. ↗

Price

Pay once.
Then it is yours.

One Full Disk Access grant, held by a notarized binary from an identifiable developer, instead of four grants handed out to whichever editors happened to spawn a server. The servers themselves stay MIT and free forever.

  • A 30-minute trial, every surface at full function. Press the button; nothing arms itself.
  • Every 1.x release, free. 2.0 is a new purchase; nothing lapses in between.
  • Every Mac you own or control. No activation limit and nothing counts your installs.
  • 30 days to change your mind, for any reason or none.
  • No account, no login, no phone-home. The key verifies on your Mac and nowhere else.

One price, and it covers all 12 surfaces — the whole set the probes mapped. There is no ladder here any more and no rise announced: what a licence costs is what this page says it costs on the day you read it, and buying it fixes your side of that for every 1.x release.

Run the servers on their own and pay nothing. The servers are MIT and always were — they need no key, and they do the work. What a licence buys is the app: the signed and notarized binary that holds one Full Disk Access grant on their behalf, and the work of keeping it working. Its source is public for a reason that has nothing to do with price: nobody should grant Full Disk Access to software they cannot read. Read it, build it, run your own — the licence is explicit that any build asks for a key, rather than leaving you to find out at the first run.

The trial is held in memory and never written to disk, so quitting Cupertino and opening it again starts another one. Nothing stops you and nothing pretends to. When the window closes it closes properly: the servers it started are stopped, and your assistant reports a dropped connection. A deadline named on the button that started it is not a window if it cannot shut.

Also from Magenta Creations

Bastion

One MCP server, running once.

Cupertino holds one grant for the Apple apps. Bastion does the same job for every other server you connect: one supervised process per profile instead of one per editor, credentials in the Keychain, and every tool call recorded.

Armada

Every coding agent, on one screen.

A menu bar app for the agents on the other end of those servers: every Claude Code and Codex session on your Mac, what each one is doing, how full its context window is, and how much of each plan is left, read locally from what the agents already write to disk.

One app. One grant.

Signed with a Developer ID and notarized by Apple. One payment covers every 1.x release, on every Mac you own — or run every surface for 30 minutes first and decide afterwards.

brew install --cask mgcrea/tap/cupertino

macOS 26 or later · 30-day refund · SHA-256 · release notes