Native Swift MCP server for Apple apps. Compiled binary with embedded TCC usage descriptions — Calendar, Reminders, Contacts, and Maps talk to EventKit / Contacts / MapKit directly. Mail, Messages, Notes still go through AppleScript (no public framework exists) but run in-process via NSAppleScript instead of spawning osascript. Photos combines PhotoKit (structured search, albums, favorites, export/import) with the same in-process AppleScript for title/description/keyword metadata and free-text search. Music playlists go through Music.app's scripting dictionary in an osascript subprocess (MusicKit cannot edit playlists on macOS).
Download the latest .mcpb from GitHub Releases and double-click to install in Claude Desktop. First run prompts for TCC permissions (Calendar, Reminders, Contacts, full disk if reading chat.db, Automation for Music on the first music call).
Requires macOS 14+ on Apple Silicon. Intel users can build from source ( swift build -c release).
Reads and writes are separate tools, so a client can allow the read-only
*_read tools without also allowing sends, edits or deletes. Each *_read
tool takes an operation; every write tool does exactly one thing and takes
no operation.
calendar_read — calendars, list, search, get. list/ search
only ever look inside one window: fromDate + toDate, or fromDate +
daysAhead (pass one, not both; default 7 days for list, 30 for
search, maximum 366). Every response names the window it searched and,
when more matched than limit held, the total and the offset for the
next page — so an empty answer means nothing was scheduled in that window,
never that the calendar is clear beyond it. A window longer than the
maximum is refused rather than quietly shortened. Each occurrence of a
recurring event gets its own id ( <series id>#occ=<original start>);
get and the write tools act on exactly that occurrence, and a bare
series id is refused for a recurring event rather than applied to its
first occurrence.calendar_read freeTime — open slots within working hours across every
calendar on the Mac (work Exchange/Google accounts included), or one with
calendarName. workdayStart/ workdayEnd (default 09:00– 17:00),
weekdaysOnly (default true), minDurationMinutes (default 30); window
default today plus 7 days, maximum 31. A day the window cuts short says
so. Busy means not marked free, not cancelled and
not declined by you; all-day events block only with includeAllDay: true.
Nothing before now is offered. Times are wall-clock times in the Mac's
zone, which is how calendar_create reads an offset-less time, so a slot
can be booked by passing its date and time straight back.calendar_create, calendar_update (passing calendarName moves the
event), calendar_delete — update/ delete take span: "futureEvents"
to change that occurrence and every later one.reminders_read — lists, list, search, get (paged, soonest due first)reminders_create, reminders_update, reminders_complete,
reminders_reopen, reminders_deletecontacts_read — search (default) and get.contacts_create, contacts_update, contacts_delete. Emails, phones and
addresses are edited one entry at a time ( add / remove / replace),
so an edit never rewrites the whole list. Writes need full Contacts
access. A card with anything in its Notes field cannot be written through
the Contacts framework at all — not just its note, every field —
without the com.apple.developer.contacts.notes entitlement. An update or
delete on such a card is replayed automatically through Contacts.app, which
does hold the entitlement; it needs Automation access to Contacts.app, and
the reply says so when it happens. The replay covers name, organization, job
title and note, and declines an edit touching emails/phones/addresses or
a birthday ( contacts_create with a note is still refused outright) — those
cannot be expressed faithfully in AppleScript, so a declined edit writes
nothing rather than part of itself. contacts_delete takes contactId only,
never a name.maps — place search (default) and directions. Read-only.mail_read — accounts, mailboxes, unread, search, get. Search
reads Mail's Envelope Index directly (needs Full Disk Access), so it is
fast on large accounts and returns results newest first.mail_send — send an email. Confirmation-gated (see below).messages_read — chats and read (history from chat.db, group chats).messages_send — send an iMessage / SMS, optionally with an attachment.
Confirmation-gated (see below).
Attachments inside hidden directories or dotfiles, ~/Library and system
locations are refused.notes_read — list, search (optionally with bodies inline), get (a
full note, pageable), folders.notes_create, notes_update, notes_deletephotos_read — albums, list, search, searchText (free text),
asset (detail), imageData (a JPEG rendition)photos_export (originals to a directory), photos_import,
photos_create_album, photos_add_to_album, photos_remove_from_album,
photos_update ( setFavorite / setTitle / setDescription /
setKeywords)music_read — playlists (default; every playlist and folder with its
kind, track count and folder; filter with query or folderId, "root"
for the top level), playlist (one playlist's tracks in order, with
positions, ids and a revision), search (library tracks matching
query — find the ids to add). Ids are Music persistent IDs (16 hex
characters, shown as [id …]).music_create_playlist (optionally in a folder and already holding
trackIds), music_create_folder, music_add_tracks (appends; skips
tracks already there unless skipDuplicates: false),
music_update_playlist (rename, description, move into a folder).music_remove_tracks ( trackIds removes every copy; positions removes
exactly those rows), music_reorder_playlist ( mode: move, sort,
reverse, dedupe, replace), music_delete_playlist (a playlist, or an
empty folder). All three are confirmation-gated.revision from music_read as expectedRevision to have an edit
refused if the playlist changed in between. Every write reports whether a
re-read confirmed it; with iCloud Music Library on, Music can revert edits
to synced playlists, so "not verified yet" means check again, not retry.When both servers are connected, prefer this one for Calendar, Contacts and
Mail on the Mac (it sees every account, not only iCloud) and for Reminders,
Notes, Messages, Photos and Music.app playlist edits. Use apple-icloud-mcp for
the Apple Music catalog, Apple Maps and WeatherKit. Change one thing through
one server at a time: both can edit the same iCloud event, contact or synced
playlist. To put a catalog song in a local playlist, add it with
apple_music_add_to_library first, wait for Music.app to sync it, then
music_read search and music_add_tracks. If the search is empty, wait
rather than adding the song again. The reorder tools name their arguments
differently: mode/ sortBy here, operation/ by there.
mail_send, messages_send, calendar_delete, contacts_delete,
music_remove_tracks, music_reorder_playlist and music_delete_playlist.
These cannot be undone, so none acts on its first call. A client with MCP
elicitation prompts the user. Otherwise the first call returns
status: "confirmation-required" with a preview and a confirmToken: show
the preview to the user, and only after they approve call again with the same
arguments plus confirmToken. The token is single-use and bound to exactly
those arguments. MCP_CONFIRM_MODE ( ask-user default / auto / refuse)
sets the fallback.
The old calendar, reminders, contacts, mail, messages, notes and
photos tools are gone. Calling one returns an error naming its
replacements. The same operation values carry over to the *_read tools;
each write operation became its own tool ( messages send →
messages_send, contacts delete → contacts_delete, reminders complete → reminders_complete, photos setTitle → photos_update
with operation: "setTitle", and so on).
Optional user config (set in Claude Desktop → Settings → MCP):
APPLE_MCP_DEFAULT_CALENDAR — default calendar name for event creationAPPLE_MCP_DEFAULT_REMINDER_LIST — default list name for reminder creationBinary is arm64-only because some transitive MCP SDK deps (swift-collections, swift-service-lifecycle) don't declare x86_64.
EventKit / Contacts / MapKit-backed tools are orders of magnitude faster than the equivalent AppleScript path.
messages_read reads chat.db directly for history; messages_send uses AppleScript.
mail_read search takes multiple terms in one call — prefer that over one call
per term:
invoice overdue — both (implicit AND)invoice OR receipt — either ( OR must be uppercase)"past due" — quoted phrasefrom: / to: / subject: — field scoping, combinable with ANDA mail_read timeout comes back as an explicit error, never as an empty result
list. If you see "the search completed and matched no messages", the search
really did finish and find nothing. If you see "Timed out", nothing was
retrieved and the query needs narrowing — do not report it as "no mail
found".