{
  "markdown": "# macos-mcp\n\nMCP servers for macOS native apps — gives [Claude Code](https://claude.ai/code), [Claude Desktop](https://claude.ai/download), and any MCP client native access to Mail, Numbers, Contacts, Calendar, Reminders, and FaceTime / phone calls.\n\nNo API keys, no OAuth, no cloud services. Talks directly to macOS apps via AppleScript and URL schemes (`tel://`, `facetime://`). Runs locally on your Mac.\n\n> CUPS printing (`printmcp`) used to live here; it moved to [office-mcp](https://github.com/aernouddekker/office-mcp) since it's a shell wrapper, not an AppleScript bridge.\n\n## Servers\n\n### Mail (`mailappmcp`) — 21 tools\n\nWorks with every email account configured in Mail.app — iCloud, Gmail, Outlook, Fastmail, you name it.\n\n| Tool | Description |\n|------|-------------|\n| `list-mailboxes` | List all mailboxes across all accounts with unread counts |\n| `list-accounts` | List configured mail accounts with email addresses and type |\n| `list-signatures` | List available email signatures |\n| `search-messages` | Search messages by subject or sender (empty query lists all) |\n| `read-message` | Read the full content of a specific email |\n| `get-message-source` | Get raw RFC822 source of a message |\n| `list-attachments` | List attachments on a message with name, MIME type, size |\n| `save-attachment` | Save email attachments to disk |\n| `compose-message` | Create a draft in Mail.app (does **not** send) — supports plain or HTML body via `htmlBody`, plus attachments |\n| `send-message` | Send an email immediately (supports `from`, attachments, plain or HTML body via `htmlBody`) |\n| `reply-to-message` | Reply or reply-all to a message — threads correctly and supports a branded `htmlBody` or attachments |\n| `forward-message` | Forward a message to new recipients |\n| `redirect-message` | Redirect a message (preserves original sender) |\n| `move-messages` | Move messages between mailboxes |\n| `delete-messages` | Delete messages by Message-ID |\n| `mark-as-read` | Mark messages as read |\n| `mark-as-junk` | Mark/unmark messages as junk |\n| `flag-message` | Flag/unflag messages with color support |\n| `set-message-color` | Set background color of messages in the message list |\n| `check-for-new-mail` | Trigger a mail fetch for one or all accounts |\n| `extract-email-address` | Parse \"John Doe \\<jdoe@example.com\\>\" into name and address |\n\n### Numbers (`numbersmcp`) — 29 tools\n\nWorks with any open Numbers spreadsheet.\n\n| Tool | Description |\n|------|-------------|\n| `list-spreadsheets` | List all open Numbers documents |\n| `create-document` | Create a new Numbers document |\n| `list-sheets` | List sheets and tables in a document |\n| `get-active-sheet` | Get the currently active sheet |\n| `read-range` | Read cell values from a range (e.g. \"A1:C10\") |\n| `read-table` | Read an entire table as structured data |\n| `write-cell` | Write a value to a specific cell |\n| `write-range` | Write multiple values to a range |\n| `clear-range` | Clear contents and formatting of a cell range |\n| `get-formula` | Get the formula from a cell |\n| `set-formula` | Set a formula on a cell |\n| `add-row` | Append a row to a table |\n| `delete-row` | Delete a row from a table |\n| `add-column` | Add a column to a table |\n| `delete-column` | Delete a column from a table |\n| `resize-row-column` | Set row height or column width |\n| `add-sheet` | Add a new sheet to a document |\n| `delete-sheet` | Delete a sheet from a document |\n| `rename-sheet` | Rename a sheet |\n| `add-table` | Add a new table to a sheet |\n| `delete-table` | Delete a table from a sheet |\n| `rename-table` | Rename a table |\n| `sort-table` | Sort a table by a column |\n| `transpose-table` | Swap rows and columns of a table |\n| `merge-cells` | Merge a range of cells |\n| `unmerge-cells` | Unmerge previously merged cells |\n| `set-cell-format` | Set cell format (number, currency, date, percentage, etc.) |\n| `set-cell-style` | Set font, color, background, bold, italic, alignment |\n| `export-document` | Export to PDF, Excel, or CSV |\n\n### Contacts (`@aernoud/contactsmcp`) — 15 tools\n\nWorks with the system address book — all accounts synced to Contacts.app.\n\n| Tool | Description |\n|------|-------------|\n| `search-contacts` | Search contacts by name, email, or phone |\n| `search-by-modification-date` | Find contacts modified after a given date |\n| `read-contact` | Get full contact details |\n| `get-my-card` | Get the user's own contact card |\n| `get-vcard` | Export a contact as vCard 3.0 text |\n| `create-contact` | Create a new contact |\n| `update-contact` | Update contact fields |\n| `delete-contact` | Delete a contact |\n| `list-groups` | List all contact groups |\n| `create-group` | Create a new contact group |\n| `rename-group` | Rename a contact group |\n| `delete-group` | Delete a contact group |\n| `add-to-group` | Add a contact to a group |\n| `remove-from-group` | Remove a contact from a group |\n| `list-group-members` | List all contacts in a group |\n\n### Calendar (`calendarmcp`) — 25 tools\n\nWorks with every calendar configured in Calendar.app — iCloud, Google, Exchange, local, you name it.\n\n| Tool | Description |\n|------|-------------|\n| `list-calendars` | List all calendars with name, writable flag, description |\n| `get-calendar` | Get properties of a single calendar by name (incl. event count) |\n| `create-calendar` | Create a new calendar |\n| `update-calendar` | Rename a calendar or set its description |\n| `delete-calendar` | **Not supported by Calendar.app** — returns a descriptive error; calendars must be removed from the Calendar.app UI |\n| `switch-view` | Switch Calendar.app to day/week/month view, optionally jumping to a date |\n| `reload-calendars` | Force Calendar.app to refresh from accounts |\n| `list-events` | List events in a calendar between two ISO dates |\n| `search-events` | Search events by summary substring within a date window (defaults: −30d to +365d). Scope to a single calendar for speed |\n| `get-event` | Get full event details by uid |\n| `create-event` | Create an event with summary, start, end, optional location/description/url |\n| `update-event` | Patch any event field by uid |\n| `delete-event` | Delete an event by uid |\n| `move-event` | Move an event to another calendar (delete + recreate; new uid) |\n| `duplicate-event` | Duplicate an event into the same or another calendar |\n| `today-events` | List today's events in one calendar or across all |\n| `upcoming-events` | List events in the next N days |\n| `list-attendees` | List attendees on an event |\n| `add-attendee` | Add an attendee with email and optional display name |\n| `remove-attendee` | Remove an attendee by email |\n| `list-alarms` | List display, mail, and sound alarms on an event |\n| `add-display-alarm` | Add a display alarm N minutes before event start |\n| `add-sound-alarm` | Add a sound alarm N minutes before event start |\n| `add-mail-alarm` | Add a mail alarm N minutes before event start |\n| `remove-alarm` | Remove an alarm by 1-based index from list-alarms output |\n\n### Reminders (`@aernoud/remindersmcp`) — 22 tools\n\nWorks with every reminder list configured in Reminders.app — iCloud, Exchange, local, you name it.\n\n| Tool | Description |\n|------|-------------|\n| `list-accounts` | List all accounts in Reminders.app with name and id |\n| `list-lists` | List every reminder list, optionally scoped to one account |\n| `get-list` | Get properties of a single list (id, account, color, emblem, open + completed counts) |\n| `create-list` | Create a new list, optionally in a specific account |\n| `update-list` | Rename a list |\n| `delete-list` | Delete a list (Reminders.app supports this directly via AppleScript, unlike Calendar.app) |\n| `show-list` | Bring a list to the front in Reminders.app |\n| `list-reminders` | List reminders in a named list (excludes completed by default) |\n| `search-reminders` | Search reminders by name substring; scope to one list for speed |\n| `get-reminder` | Get full reminder details by id |\n| `today-reminders` | List reminders due today, scoped to one list or all |\n| `upcoming-reminders` | List reminders due in the next N days |\n| `overdue-reminders` | List reminders past their due date and not yet completed |\n| `create-reminder` | Create a reminder with body, due date or all-day due date, remind-me date, priority, flagged |\n| `update-reminder` | Patch any reminder field by id |\n| `delete-reminder` | Delete a reminder by id |\n| `complete-reminder` | Mark a reminder as completed (Reminders auto-stamps completion date) |\n| `uncomplete-reminder` | Mark a previously completed reminder as not completed |\n| `move-reminder` | Move a reminder to a different list (uses the native `move` verb — id is preserved) |\n| `flag-reminder` | Set or clear the flagged state |\n| `set-priority` | Set priority to none / high / medium / low (mapped to Reminders' 0/1/5/9 enum) |\n| `show-reminder` | Bring Reminders.app to the front and focus a specific reminder |\n\n### FaceTime (`facetimemcp`) — 3 tools\n\nInitiates calls by handing URL schemes to `open`. Phone calls require an iPhone paired via Continuity (so macOS can route them through your phone).\n\n| Tool | Description |\n|------|-------------|\n| `call-phone` | Place a cellular call via paired iPhone (`tel://`) |\n| `call-facetime-audio` | Start a FaceTime audio call to a phone number or Apple ID email |\n| `call-facetime-video` | Start a FaceTime video call to a phone number or Apple ID email |\n\nPhone numbers are normalized + validated as E.164 (`+15551234567`); spaces, dashes, and parens are tolerated. macOS may show a confirmation prompt before dialing — there is no fully silent dial path, by design.\n\n## Requirements\n\n- macOS (uses AppleScript — won't work on Linux/Windows)\n- Node.js 18+\n\n## Install\n\n### From npm\n\n```bash\nnpm install -g mailappmcp            # Mail server\nnpm install -g numbersmcp            # Numbers server\nnpm install -g @aernoud/contactsmcp  # Contacts server\nnpm install -g @aernoud/calendarmcp  # Calendar server\nnpm install -g @aernoud/remindersmcp # Reminders server\nnpm install -g @aernoud/facetimemcp  # FaceTime / phone calls\n```\n\n### From source\n\n```bash\ngit clone https://github.com/aernouddekker/macos-mcp.git\ncd macos-mcp\nnpm install\nnpm run build\n```\n\n## Configure\n\n### Claude Code\n\nAdd to `~/.claude/settings.json` or your project's `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"mailappmcp\":   { \"command\": \"npx\", \"args\": [\"-y\", \"mailappmcp\"] },\n    \"numbersmcp\":   { \"command\": \"npx\", \"args\": [\"-y\", \"numbersmcp\"] },\n    \"contactsmcp\":  { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/contactsmcp\"] },\n    \"calendarmcp\":  { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/calendarmcp\"] },\n    \"remindersmcp\": { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/remindersmcp\"] },\n    \"facetimemcp\":  { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/facetimemcp\"] }\n  }\n}\n```\n\n### Claude Desktop / Cowork\n\nAdd to `~/Library/Application Support/Claude/claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"mailappmcp\":   { \"command\": \"npx\", \"args\": [\"-y\", \"mailappmcp\"] },\n    \"numbersmcp\":   { \"command\": \"npx\", \"args\": [\"-y\", \"numbersmcp\"] },\n    \"contactsmcp\":  { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/contactsmcp\"] },\n    \"calendarmcp\":  { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/calendarmcp\"] },\n    \"remindersmcp\": { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/remindersmcp\"] },\n    \"facetimemcp\":  { \"command\": \"npx\", \"args\": [\"-y\", \"@aernoud/facetimemcp\"] }\n  }\n}\n```\n\n## How it works\n\nEach server runs locally over stdio. The Mail, Numbers, and Contacts servers build AppleScript strings, execute them via `osascript`, and parse the structured output back into JSON. The Print server shells out to CUPS (`lp`, `lpstat`, `lpoptions`, `cancel`); the FaceTime server hands `tel://` / `facetime://` URLs to `open`. A shared package (`@mailappmcp/shared`) provides the AppleScript runner, the generic command runner (`runCommand`), string escaping, and delimiter-based parsing.\n\n### App lifecycle — leave as found\n\nMail, Calendar, Contacts, and Reminders need their respective app running to respond to AppleScript. Rather than requiring you to keep those apps open, each server auto-launches them on first use and cleans up afterwards:\n\n- **On the first tool call that touches an app**, the server checks (via `pgrep`) whether the app is already running. If not, it launches it in the background (`open -g -a`) without stealing focus, and waits up to 5 seconds for the app to accept AppleEvents before running the tool.\n- **That state is remembered in-process** for the lifetime of the MCP server. Subsequent tool calls against the same app skip the probe and reuse the running app — no repeated launch penalty.\n- **On server shutdown** (SIGTERM / SIGINT / SIGHUP / normal exit — e.g. when Claude Desktop disconnects the server or the chat ends), the server quits only the apps **it launched**. Apps you had open before the server started are left alone.\n- Edge case: if the server is force-killed (`SIGKILL`), the exit handler can't run, so any auto-launched apps stay running. Same as if you'd never had the server.\n\nThis means you can use the tools without worrying about a pile of half-launched apps lingering after the session, and without the servers killing apps you were actively using.\n\n### Safety\n\n- `compose-message` opens a visible draft — you review before sending\n- `send-message` is a separate, explicit action\n- `reply-to-message` and `forward-message` default to draft mode (`sendImmediately: false`)\n- `delete-messages` moves to Trash (standard Mail.app behavior)\n\n### HTML email\n\n`compose-message` and `send-message` accept an optional `htmlBody` parameter. When supplied, the message is created as a rich-text/HTML message and the recipient sees rendered formatting (headings, bold, lists, clickable links) instead of raw tags. `body` is still required and is used as the plain-text fallback for clients that read plain content. Omit `htmlBody` for the existing plain-text behavior — fully backward compatible.\n\n```jsonc\n// send-message with HTML body\n{\n  \"to\": [\"alice@example.com\"],\n  \"subject\": \"Weekly update\",\n  \"body\": \"Highlights:\\n- Shipped feature X\\n- Fixed bug Y\",\n  \"htmlBody\": \"<h2>Highlights</h2><ul><li>Shipped <b>feature X</b></li><li>Fixed bug Y — see <a href=\\\"https://example.com/issue/42\\\">#42</a></li></ul>\"\n}\n```\n\nUnder the hood, the HTML path for `compose-message` / `send-message` uses JXA (`osascript -l JavaScript`) and Mail.app's runtime `htmlContent` setter on outgoing messages. The plain-text path still uses ordinary AppleScript.\n\n#### Branded replies that thread correctly\n\n`reply-to-message` also accepts `htmlBody`, producing a reply that is **both** branded **and** correctly threaded. Mail's scripting model can't set arbitrary RFC headers, and an outgoing reply's rich-text editor isn't writable via the AppleScript `content` property — so neither tool alone could do both before. The reply tool now:\n\n1. invokes Mail's native `reply` verb, which constructs the reply and populates the `In-Reply-To` / `References` headers (these are message properties, independent of the body), then\n2. puts the branded HTML on the pasteboard as `public.html` and pastes it (Cmd-V) above the auto-quoted thread.\n\nThe result renders the branding inline, keeps the quoted thread, and carries the threading headers strict clients (Outlook, Gmail web) require to nest the reply.\n\n```jsonc\n// reply-to-message with a branded HTML body\n{\n  \"account\": \"iCloud\",\n  \"mailbox\": \"INBOX\",\n  \"messageId\": \"<abc123@example.com>\",\n  \"body\": \"Thanks — see below.\",       // ignored when htmlBody is set\n  \"htmlBody\": \"<p>Thanks for reaching out — here's the update:</p><ul><li><b>Status:</b> shipped</li></ul>\"\n}\n```\n\nConstraints:\n\n- `htmlBody` and `attachments` cannot be combined on a reply (Mail re-parses the HTML during MIME multipart composition and corrupts the rendered body) — the tool returns an error if both are passed. Send the branded reply first, then attach files in a separate reply.\n- When `htmlBody` is supplied, the plain-text `body` is ignored; Mail derives the plain-text MIME alternative from the pasted rich text.\n- Pasting requires Mail to hold focus briefly; the script aborts safely (and restores your clipboard) if focus is lost.\n\n#### Verifying an HTML / branded reply\n\nAfter producing a branded reply, confirm both halves actually work:\n\n1. **Render check** — open the draft Mail created. The branding should appear as formatted rich text (headings, bold, links), not raw `<tags>`, sitting above the quoted original.\n2. **Threading / header check** — send the reply to a recipient on a strict client (**Outlook** or **Gmail web**) and confirm it nests inside the original conversation rather than appearing as a loose message. Equivalently, run `get-message-source` on the sent reply and verify it carries `In-Reply-To:` and `References:` headers pointing at the original Message-ID.\n3. **Clipboard check** — confirm your clipboard holds its prior contents afterwards (the tool saves and restores it around the paste).\n4. **Attachment guard** — calling `reply-to-message` with both `htmlBody` and `attachments` should return a clear error, not a corrupted draft.\n\n## Known limitations\n\n### General\n\n- `osascript` has a 30-second timeout per call\n- Apps are auto-launched on first use and quit on server shutdown if they weren't running beforehand — see [App lifecycle](#app-lifecycle--leave-as-found)\n\n### Mail\n- For Mail tools taking `messageId` or `messageIds`, prefer the numeric string `id` from `search-messages`. RFC `messageId` values remain supported, but comparing them loads message headers across the mailbox and can leave Mail busy even after the 30-second timeout. Local IDs are specific to this Mac's Mail library; search again after moving a message or rebuilding the library.\n- `content contains` searches in AppleScript can be slow on large mailboxes — Mail server searches subject and sender by default\n\n### Numbers\n- Numbers tools require an open document\n\n### Calendar\n\nThese are limitations of Calendar.app's AppleScript interface itself, not the MCP server:\n\n- **Calendars cannot be deleted via AppleScript.** `delete calendar` raises `-10000` (AppleEvent handler failed) on every modern macOS version. The `delete-calendar` tool checks that the calendar exists and then returns a descriptive error — you must remove calendars manually from the Calendar.app sidebar (right-click → Delete).\n- **Calendars have no usable id/uid.** Calendar.app's `calendar` class does not expose a stable id via AppleScript (`uid of` raises `-10000`). All calendar tools therefore identify calendars by **name** — make sure your calendar names are unique.\n- **`whose` filters are O(n) over events.** AppleScript scans every event in a calendar to evaluate `whose start date ≥ X` predicates. On a busy multi-calendar store, even an empty 7-day query across all calendars can take 60+ seconds and exceed the 30 s `osascript` timeout. **Always pass a `calendarName` to `list-events`, `search-events`, `today-events`, and `upcoming-events` when possible** to scope the scan. `search-events` additionally enforces a date window (default: −30 days to +365 days).\n- **`move-event` reassigns the uid.** Calendar.app cannot reparent an event between calendars. The tool deletes the source event and creates a new one in the target calendar; the new event has a fresh uid (returned as `newUid` alongside `oldUid`).\n- **Recurrence is exposed as raw RRULE strings** (e.g. `FREQ=WEEKLY;INTERVAL=1`) — read and write only, no parsing or expansion.\n\n## License\n\nMIT\n",
  "bytes": 19553,
  "sha": "af75b7d0cc80801897214dcf50de2ec4c43b16bb58107bbc8ca39f806ebcb7fa",
  "repo_slug": "aernouddekker/macos-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_aernouddekker_contactsmcp_ed2dc35e/readme"
}