# calendarmcp

**Category:** 🖥️ OS Automation  
**Repository:** https://github.com/aernouddekker/macos-mcp  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/calendarmcp-2

## Description
MCP server for macOS Calendar.app — calendar and event management via AppleScript

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "calendarmcp": {
    "command": "npx",
    "args": ["-y","calendarmcp-2"]
  }
}
```

## Documentation & README

# macos-mcp

MCP 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.

No API keys, no OAuth, no cloud services. Talks directly to macOS apps via AppleScript and URL schemes (`tel://`, `facetime://`). Runs locally on your Mac.

> 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.

## Servers

### Mail (`mailappmcp`) — 21 tools

Works with every email account configured in Mail.app — iCloud, Gmail, Outlook, Fastmail, you name it.

| Tool | Description |
|------|-------------|
| `list-mailboxes` | List all mailboxes across all accounts with unread counts |
| `list-accounts` | List configured mail accounts with email addresses and type |
| `list-signatures` | List available email signatures |
| `search-messages` | Search messages by subject or sender (empty query lists all) |
| `read-message` | Read the full content of a specific email |
| `get-message-source` | Get raw RFC822 source of a message |
| `list-attachments` | List attachments on a message with name, MIME type, size |
| `save-attachment` | Save email attachments to disk |
| `compose-message` | Create a draft in Mail.app (does **not** send) — supports plain or HTML body via `htmlBody`, plus attachments |
| `send-message` | Send an email immediately (supports `from`, attachments, plain or HTML body via `htmlBody`) |
| `reply-to-message` | Reply or reply-all to a message — threads correctly and supports a branded `htmlBody` or attachments |
| `forward-message` | Forward a message to new recipients |
| `redirect-message` | Redirect a message (preserves original sender) |
| `move-messages` | Move messages between mailboxes |
| `delete-messages` | Delete messages by Message-ID |
| `mark-as-read` | Mark messages as read |
| `mark-as-junk` | Mark/unmark messages as junk |
| `flag-message` | Flag/unflag messages with color support |
| `set-message-color` | Set background color of messages in the message list |
| `check-for-new-mail` | Trigger a mail fetch for one or all accounts |
| `extract-email-address` | Parse "John Doe \<jdoe@example.com\>" into name and address |

### Numbers (`numbersmcp`) — 29 tools

Works with any open Numbers spreadsheet.

| Tool | Description |
|------|-------------|
| `list-spreadsheets` | List all open Numbers documents |
| `create-document` | Create a new Numbers document |
| `list-sheets` | List sheets and tables in a document |
| `get-active-sheet` | Get the currently active sheet |
| `read-range` | Read cell values from a range (e.g. "A1:C10") |
| `read-table` | Read an entire table as structured data |
| `write-cell` | Write a value to a specific cell |
| `write-range` | Write multiple values to a range |
| `clear-range` | Clear contents and formatting of a cell range |
| `get-formula` | Get the formula from a cell |
| `set-formula` | Set a formula on a cell |
| `add-row` | Append a row to a table |
| `delete-row` | Delete a row from a table |
| `add-column` | Add a column to a table |
| `delete-column` | Delete a column from a table |
| `resize-row-column` | Set row height or column width |
| `add-sheet` | Add a new sheet to a document |
| `delete-sheet` | Delete a sheet from a document |
| `rename-sheet` | Rename a sheet |
| `add-table` | Add a new table to a sheet |
| `delete-table` | Delete a table from a sheet |
| `rename-table` | Rename a table |
| `sort-table` | Sort a table by a column |
| `transpose-table` | Swap rows and columns of a table |
| `merge-cells` | Merge a range of cells |
| `unmerge-cells` | Unmerge previously merged cells |
| `set-cell-format` | Set cell format (number, currency, date, percentage, etc.) |
| `set-cell-style` | Set font, color, background, bold, italic, alignment |
| `export-document` | Export to PDF, Excel, or CSV |

### Contacts (`@aernoud/contactsmcp`) — 15 tools

Works with the system address book — all accounts synced to Contacts.app.

| Tool | Description |
|------|-------------|
| `search-contacts` | Search contacts by name, email, or phone |
| `search-by-modification-date` | Find contacts modified after a given date |
| `read-contact` | Get full contact details |
| `get-my-card` | Get the user's own contact card |
| `get-vcard` | Export a contact as vCard 3.0 text |
| `create-contact` | Create a new contact |
| `update-contact` | Update contact fields |
| `delete-contact` | Delete a contact |
| `list-groups` | List all contact groups |
| `create-group` | Create a new contact group |
| `rename-group` | Rename a contact group |
| `delete-group` | Delete a contact group |
| `add-to-group` | Add a contact to a group |
| `remove-from-group` | Remove a contact from a group |
| `list-group-members` | List all contacts in a group |

### Calendar (`calendarmcp`) — 25 tools

Works with every calendar configured in Calendar.app — iCloud, Google, Exchange, local, you name it.

| Tool | Description |
|------|-------------|
| `list-calendars` | List all calendars with name, writable flag, description |
| `get-calendar` | Get properties of a single calendar by name (incl. event count) |
| `create-calendar` | Create a new calendar |
| `update-calendar` | Rename a calendar or set its description |
| `delete-calendar` | **Not supported by Calendar.app** — returns a descriptive error; calendars must be removed from the Calendar.app UI |
| `switch-view` | Switch Calendar.app to day/week/month view, optionally jumping to a date |
| `reload-calendars` | Force Calendar.app to refresh from accounts |
| `list-events` | List events in a calendar between two ISO dates |
| `search-events` | Search events by summary substring within a date window (defaults: −30d to +365d). Scope to a single calendar for speed |
| `get-event` | Get full event details by uid |
| `create-event` | Create an event with summary, start, end, optional location/description/url |
| `update-event` | Patch any event field by uid |
| `delete-event` | Delete an event by uid |
| `move-event` | Move an event to another calendar (delete + recreate; new uid) |
| `duplicate-event` | Duplicate an event into the same or another calendar |
| `today-events` | List today's events in one calendar or across all |
| `upcoming-events` | List events in the next N days |
| `list-attendees` | List attendees on an event |
| `add-attendee` | Add an attendee with email and optional display name |
| `remove-attendee` | Remove an attendee by email |
| `list-alarms` | List display, mail, and sound alarms on an event |
| `add-display-alarm` | Add a display alarm N minutes before event start |
| `add-sound-alarm` | Add a sound alarm N minutes before event start |
| `add-mail-alarm` | Add a mail alarm N minutes before event start |
| `remove-alarm` | Remove an alarm by 1-based index from list-alarms output |

### Reminders (`@aernoud/remindersmcp`) — 22 tools

Works with every reminder list configured in Reminders.app — iCloud, Exchange, local, you name it.

| Tool | Description |
|------|-------------|
| `list-accounts` | List all accounts in Reminders.app with name and id |
| `list-lists` | List every reminder list, optionally scoped to one account |
| `get-list` | Get properties of a single list (id, account, color, emblem, open + completed counts) |
| `create-list` | Create a new list, optionally in a specific account |
| `update-list` | Rename a list |
| `delete-list` | Delete a list (Reminders.app supports this directly via AppleScript, unlike Calendar.app) |
| `show-list` | Bring a list to the front in Reminders.app |
| `list-reminders` | List reminders in a named list (excludes completed by default) |
| `search-reminders` | Search reminders by name substring; scope to one list for speed |
| `get-reminder` | Get full reminder details by id |
| `today-reminders` | List reminders due today, scoped to one list or all |
| `upcoming-reminders` | List reminders due in the next N days |
| `overdue-reminders` | List reminders past their due date and not yet completed |
| `create-reminder` | Create a reminder with body, due date or all-day due date, remind-me date, priority, flagged |
| `update-reminder` | Patch any reminder field by id |
| `delete-reminder` | Delete a reminder by id |
| `complete-reminder` | Mark a reminder as completed (Reminders auto-stamps completion date) |
| `uncomplete-reminder` | Mark a previously completed reminder as not completed |
| `move-reminder` | Move a reminder to a different list (uses the native `move` verb — id is preserved) |
| `flag-reminder` | Set or clear the flagged state |
| `set-priority` | Set priority to none / high / medium / low (mapped to Reminders' 0/1/5/9 enum) |
| `show-reminder` | Bring Reminders.app to the front and focus a specific reminder |

### FaceTime (`facetimemcp`) — 3 tools

Initiates calls by handing URL schemes to `open`. Phone calls require an iPhone paired via Continuity (so macOS can route them through your phone).

| Tool | Description |
|------|-------------|
| `call-phone` | Place a cellular call via paired iPhone (`tel://`) |
| `call-facetime-audio` | Start a FaceTime audio call to a phone number or Apple ID email |
| `call-facetime-video` | Start a FaceTime video call to a phone number or Apple ID email |

Phone 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.

## Requirements

- macOS (uses AppleScript — won't work on Linux/Windows)
- Node.js 18+

## Install

### From npm

```bash
npm install -g mailappmcp            # Mail server
npm install -g numbersmcp            # Numbers server
npm install -g @aernoud/contactsmcp  # Contacts server
npm install -g @aernoud/calendarmcp  # Calendar server
npm install -g @aernoud/remindersmcp # Reminders server
npm install -g @aernoud/facetimemcp  # FaceTime / phone calls
```

### From source

```bash
git clone https://github.com/aernouddekker/macos-mcp.git
cd macos-mcp
npm install
npm run build
```

## Configure

### Claude Code

Add to `~/.claude/settings.json` or your project's `.mcp.json`:

```json
{
  "mcpServers": {
    "mailappmcp":   { "command": "npx", "args": ["-y", "mailappmcp"] },
    "numbersmcp":   { "command": "npx", "args": ["-y", "numbersmcp"] },
    "contactsmcp":  { "command": "npx", "args": ["-y", "@aernoud/contactsmcp"] },
    "calendarmcp":  { "command": "npx", "args": ["-y", "@aernoud/calendarmcp"] },
    "remindersmcp": { "command": "npx", "args": ["-y", "@aernoud/remindersmcp"] },
    "facetimemcp":  { "command": "npx", "args": ["-y", "@aernoud/facetimemcp"] }
  }
}
```

### Claude Desktop / Cowork

Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "mailappmcp":   { "command": "npx", "args": ["-y", "mailappmcp"] },
    "numbersmcp":   { "command": "npx", "args": ["-y", "numbersmcp"] },
    "contactsmcp":  { "command": "npx", "args": ["-y", "@aernoud/contactsmcp"] },
    "calendarmcp":  { "command": "npx", "args": ["-y", "@aernoud/calendarmcp"] },
    "remindersmcp": { "command": "npx", "args": ["-y", "@aernoud/remindersmcp"] },
    "facetimemcp":  { "command": "npx", "args": ["-y", "@aernoud/facetimemcp"] }
  }
}
```

## How it works

Each 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.

### App lifecycle — leave as found

Mail, 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:

- **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.
- **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.
- **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.
- 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.

This 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.

### Safety

- `compose-message` opens a visible draft — you review before sending
- `send-message` is a separate, explicit action
- `reply-to-message` and `forward-message` default to draft mode (`sendImmediately: false`)
- `delete-messages` moves to Trash (standard Mail.app behavior)

### HTML email

`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.

```jsonc
// send-message with HTML body
{
  "to": ["alice@example.com"],
  "subject": "Weekly update",
  "body": "Highlights:\n- Shipped feature X\n- Fixed bug Y",
  "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>"
}
```

Under 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.

#### Branded replies that thread correctly

`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:

1. 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
2. puts the branded HTML on the pasteboard as `public.html` and pastes it (Cmd-V) above the auto-quoted thread.

The result renders the branding inline, keeps the quoted thread, and carries the threading headers strict clients (Outlook, Gmail web) require to nest the reply.

```jsonc
// reply-to-message with a branded HTML body
{
  "account": "iCloud",
  "mailbox": "INBOX",
  "messageId": "<abc123@example.com>",
  "body": "Thanks — see below.",       // ignored when htmlBody is set
  "htmlBody": "<p>Thanks for reaching out — here's the update:</p><ul><li><b>Status:</b> shipped</li></ul>"
}
```

Constraints:

- `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.
- When `htmlBody` is supplied, the plain-text `body` is ignored; Mail derives the plain-text MIME alternative from the pasted rich text.
- Pasting requires Mail to hold focus briefly; the script aborts safely (and restores your clipboard) if focus is lost.

#### Verifying an HTML / branded reply

After producing a branded reply, confirm both halves actually work:

1. **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.
2. **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.
3. **Clipboard check** — confirm your clipboard holds its prior contents afterwards (the tool saves and restores it around the paste).
4. **Attachment guard** — calling `reply-to-message` with both `htmlBody` and `attachments` should return a clear error, not a corrupted draft.

## Known limitations

### General

- `osascript` has a 30-second timeout per call
- 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)

### Mail
- `content contains` searches in AppleScript can be slow on large mailboxes — Mail server searches subject and sender by default

### Numbers
- Numbers tools require an open document

### Calendar

These are limitations of Calendar.app's AppleScript interface itself, not the MCP server:

- **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).
- **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.
- **`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).
- **`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`).
- **Recurrence is exposed as raw RRULE strings** (e.g. `FREQ=WEEKLY;INTERVAL=1`) — read and write only, no parsing or expansion.

## License

MIT

