loading…
Search for a command to run...
loading…
Enables Claude and other MCP clients to directly interact with macOS Calendar.app using AppleScript for local calendar management. Provides tools for listing, s
Enables Claude and other MCP clients to directly interact with macOS Calendar.app using AppleScript for local calendar management. Provides tools for listing, searching, creating, updating, and deleting calendar events without cloud APIs or CalDAV setup.
LIST! CREATE! UPDATE! DELETE!
Apple Calendar MCP is a Model Context Protocol server that hands Claude (and any other MCP client) the keys to the macOS Calendar.app that's already on your Mac. It speaks AppleScript under the hood — no cloud API, no CalDAV account setup, no telemetry — and exposes six focused tools for listing, searching, creating, updating, and deleting events.
If you want Claude to actually do things on your calendar instead of pasting iCal blobs at you, this is it.
GitHub · Issues · Changelog · Security · Contributing · Vision
Runtime: Node 22.14+, macOS (Calendar.app required).
claude mcp add apple-calendar -- npx -y apple-calendar-mcp
Or use the npx one-liner directly — no install step — and let your MCP client spawn it on demand (see Quick start below).
Add this to your Claude Code MCP config (~/.claude.json or a project-local .mcp.json):
{
"mcpServers": {
"apple-calendar": {
"command": "npx",
"args": ["-y", "apple-calendar-mcp"]
}
}
}
Then ask Claude things like:
Default: the server runs over stdio only. Your calendar never leaves your Mac.
The first time a tool runs, macOS will prompt you to grant Calendar access to the controlling process (the terminal or IDE that spawned npx). Approve it.
If you see "not authorized" or error -1743 / -1744 later:
You may also need Privacy & Security → Calendars enabled for the same app. If you launched via npx, the controlling process is whichever app spawned it — not npx itself.
| Tool | Description | Key args |
|---|---|---|
list_calendars |
All calendars with name + writable |
— |
list_events |
Events in a date range | start_date, end_date, calendar_name?, limit? |
search_events |
Substring match across title, location, notes | query, start_date?, end_date?, limit? |
create_event |
Create a new event on any writable calendar | title, start_date, end_date, calendar_name?, location?, notes?, url?, all_day? |
update_event |
Update any subset of fields | event_id, plus any optional field from create_event |
delete_event |
Delete by id | event_id |
All dates are ISO 8601 (2026-04-21T14:30:00Z or 2026-04-21T10:30:00-07:00). Event id values are Calendar.app uid strings — stable across calls, safe to stash and reuse.
Apple Calendar MCP treats every string arriving from an MCP tool call as untrusted. That matters because AppleScript has no prepared-statement equivalent.
escapeAppleScriptString, which escapes \ and " before wrapping in quotes. This is the only user-input path into osascript, and the function is unit-tested against injection payloads like "; do shell script "rm -rf /"; --.console.log in tool code — that corrupts the transport.id is the Calendar.app uid, not a list-index or hash. It survives restarts, moves between calendars, and edits.osascript, gets a reply, and that's the end of it.See SECURITY.md for the full threat model.
claude mcp add line and you're done.osascript without mangling.git clone https://github.com/yongzhe-wang/apple-calendar-mcp.git
cd apple-calendar-mcp
pnpm install
pnpm test
pnpm build
pnpm check
pnpm dev — rebuild on changepnpm test — run the unit suite (pure helpers only, no Calendar.app required)pnpm lint / pnpm lint:fix — oxlintpnpm format / pnpm format:check — oxfmtpnpm typecheck — tsc --noEmitpnpm knip — unused code/depspnpm check — typecheck + lint + format:check + knip (same gate as CI)Project layout:
src/
index.ts # MCP server bootstrap (stdio transport)
applescript.ts # osascript bridge + string/date escaping helpers
errors.ts # user-facing error formatting
types.ts # zod schemas for tool inputs
tools/ # one file per MCP tool
test/ # unit tests for pure helpers (no Calendar.app required)
Apple Calendar MCP runs osascript -e <script> for each tool call. It builds the AppleScript source in TypeScript, escapes every untrusted field through escapeAppleScriptString, and reads back a single string result.
To survive arbitrary user text inside that result (commas, newlines, quotes, emoji), the scripts emit fields joined by ASCII control bytes — record separator 0x1E between rows and unit separator 0x1F between fields. These bytes virtually never appear in real calendar data, so parsing becomes a dumb split — no CSV/JSON quoting gymnastics, no ambiguity.
Why not a Swift/EventKit helper binary? EventKit is cleaner, but shipping a signed native binary through npm is a packaging nightmare, and AppleScript + osascript is already on every Mac. The trade-off is verbose scripts; the win is zero-dependency distribution.
There are a handful of existing MCP servers that try to reach macOS Calendar.app. Most of them fall over in at least one of the same ways. This table is the landscape as of April 2026:
| Repo | Approach | Status | Where it breaks |
|---|---|---|---|
| supermemoryai/apple-mcp | AppleScript (TS) | archived Aug 2025 | locale-dependent date "${start.toLocaleString()}"; incomplete string escape (quotes before backslashes); read path returns a hardcoded dummy event |
| Omar-V2/mcp-ical | EventKit (Python) | active, 24 open issues | timezone bugs on list/create/delete (issues #17, #20, #25, #18); requires launching Claude from terminal for the permission prompt |
| joshrutkowski/applescript-mcp | AppleScript (TS) | stale since Apr 2025 | zero escaping on event title — direct interpolation into AppleScript source |
| steipete/macos-automator-mcp | Generic AppleScript runner (TS) | active | not calendar-specific; escaping responsibility pushed entirely to the LLM caller |
| PsychQuant/che-ical-mcp | Native Swift EventKit | active | feature-rich but requires downloading a signed binary and a PlistBuddy + codesign ritual per IDE |
date "Monday, April 21, 2026 at 10:00:00 AM" parses differently in non-US locales. Several servers ship this bug.codesign invocations, running Claude from a terminal, or a special launch path to trigger the TCC prompt.stdout-vs-stderr discipline is undocumented. MCP speaks JSON-RPC on stdout; a stray console.log in server code silently corrupts the transport. No competitor README flags this.SECURITY.md that names AppleScript injection, permission scope, or the stdout invariant.escapeAppleScriptString escapes \ before ", tested with adversarial payloads** including "; do shell script "rm -rf /"; -- in every string field.isoToAppleScriptDate is built from epoch seconds against a fixed 1970 anchor so it parses identically on every macOS locale.id is Calendar.app's own uid property — stable across app restarts and (with the copy-then-delete update path) survives calendar moves cleanly.console.log is banned in server code paths. All diagnostics go to stderr. stdout is reserved for the MCP transport.zod at the boundary.osascript runs with a 16 MiB output cap to bound memory on runaway scripts.We're not feature-complete. Today this server does not cover:
If you need any of the above today, che-ical-mcp or mcp-ical are your better options. If you want a small, correctness-first calendar bridge that won't eat your events, this is it.
Does this need iCloud? No. It talks to whatever calendars are configured in Calendar.app — iCloud, Google (via Calendar.app), local "On My Mac", CalDAV, whatever. If Calendar.app can see it, this server can.
Does it need Full Disk Access? No. Only Automation access to Calendar (and, on some macOS versions, Privacy & Security → Calendars).
Linux / Windows support? No. This server is Mac-only by design — it uses osascript and Calendar.app. The package.json declares "os": ["darwin"] so npm install on other platforms is a fast fail.
How are event ids stable? The id is the Calendar.app uid, a UUID-ish string that persists across edits, calendar moves, and Calendar.app restarts. It's the same value Calendar.app uses in CalDAV sync.
Recurring events? The current tools read each occurrence as Calendar.app presents it. You can update or delete a specific occurrence by its uid, but this server does not (yet) expose recurrence-rule editing. See VISION.md for the roadmap.
Does anything get sent over the network? No. Zero network listeners, zero outbound calls. The server reads stdin, writes stdout, and shells out to osascript — that's it.
Bug reports and PRs welcome. See CONTRIBUTING.md for setup, review, and scope guidelines.
MIT © Yongzhe Wang 2026
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"apple-calendar-mcp": {
"command": "npx",
"args": []
}
}
}