Skip to main content
Glama
jakobjhartmann

apple-calendar-jxa-mcp

apple-calendar-jxa-mcp

An MCP (Model Context Protocol) server for Apple Calendar on macOS — built to solve one specific, maddening problem: most Apple-Calendar MCP servers are silently denied calendar access under hosts like Claude Desktop. No permission prompt ever appears, no entry shows up in System Settings, and every call fails with a generic "permission denied".

This server fixes that by never touching EventKit from Node at all. Node only speaks MCP; every calendar operation is delegated to /usr/bin/osascript (JXA) — an Apple platform binary that is fully allowed to use EventKit and to trigger the macOS permission prompt.

Zero dependencies. One file. Node ≥ 18. macOS only. MIT.

Quick start

cd ~/Documents   # any stable location — just not Downloads
git clone https://github.com/jakobjhartmann/apple-calendar-jxa-mcp.git
cd apple-calendar-jxa-mcp
bash install.sh

install.sh registers the server in Claude Desktop's config (keeping your other MCP servers and backing up the file). Restart Claude, ask "What's on my calendar today?", and click Allow Full Access when macOS shows the permission dialog. That's it — no Terminal launches, no app modification, launch Claude normally from the Dock.

Using an AI assistant? If you're setting this up through Claude (Cowork) or another agent, just point it at this repository — see AGENTS.md, which tells the assistant exactly what to do, how to verify it, and how to troubleshoot.

The rest of this README explains why the usual approaches fail, what install.sh does under the hood, the manual alternative, and the full tool reference.

Related MCP server: Apple Calendar MCP

Why your calendar MCP server never shows a permission prompt

Two independent macOS mechanisms have to say yes before any process reads your calendar:

  1. TCC (privacy consent) attributes every calendar request to the responsible process — the app at the root of the process tree. For an MCP server spawned by Claude Desktop, that is Claude Desktop itself. If that app does not declare calendar usage in its Info.plist, macOS rejects the request instantly: no dialog, and no entry in System Settings → Privacy & Security → Calendars (entries only appear after a valid request). This is why the pane stays empty no matter how often you reset with tccutil.

  2. Hardened runtime. Official Node.js builds (including everything nvm installs) are signed with the hardened runtime and without the com.apple.security.personal-information.calendars entitlement. Any EventKit call made inside the Node process is therefore blocked at the signature level — even if TCC would allow it. You can verify this yourself:

    codesign -dv --entitlements - "$(which node)" 2>&1 | grep flags
    # flags=0x10000(runtime)  ← hardened, and no calendar entitlement anywhere

Most Apple-Calendar MCP servers run EventKit through Node bindings or a bundled helper and hit one or both walls. This project routes around both:

Claude Desktop ──spawns──▶ node index.js        (MCP protocol only, no EventKit)
                              │
                              └──execFile──▶ /usr/bin/osascript -l JavaScript
                                                (JXA + EventKit: Apple platform
                                                 binary, prompts & access allowed)

The remaining requirement is TCC attribution (mechanism 1) — and here modern Claude Desktop versions do something helpful: they spawn MCP servers as self-responsible processes. The permission request this server makes through osascript is therefore attributed to the node process itself, which macOS happily shows a dialog for. You approve once ("node — Full Access" appears in System Settings), and it keeps working no matter how Claude is launched — and survives every Claude update, because the grant belongs to your Node binary, not to the app.

What install.sh does (and manual config)

install.sh adds this to ~/Library/Application Support/Claude/claude_desktop_config.json (existing servers preserved, file backed up first):

{
  "mcpServers": {
    "apple-calendar": {
      "command": "/ABSOLUTE/PATH/TO/node",
      "args": ["/ABSOLUTE/PATH/TO/apple-calendar-jxa-mcp/index.js"]
    }
  }
}

It uses an absolute Node path because the PATH Claude Desktop sees usually does not include nvm. That's all it does — it never touches the Claude app itself. To wire the config by hand instead, add the block above yourself (it also ships as examples/claude_desktop_config.example.json).

Permission setup

Default: no setup at all. On first calendar use, this server actively requests EventKit access and waits up to 45 seconds. Because Claude Desktop spawns MCP servers as self-responsible processes, macOS shows the dialog for the Node process: "node" would like full access to "Calendar" → click Allow Full Access. Done — verify under System Settings → Privacy & Security → Calendars: node — Full Access. The grant sticks across app launches and app updates. (If you later switch Node versions, e.g. via nvm, the dialog simply appears once more.)

⚠️ Do not patch or re-sign the Claude app to "add" calendar permission. An earlier version of this project did exactly that — it works, but ad-hoc re-signing changes the app's code identity, which breaks its keychain-bound state: forced re-logins and, worse, a broken device pairing for Claude's cloud/Cowork features. Learned the hard way so you don't have to.

Fallback — launch from Terminal (older hosts)

If no dialog ever appears (your host does not spawn MCP servers as self-responsible processes), make Terminal the responsible process and grant it calendar access once.

Step 1 — trigger the permission prompt and verify in one go. Run the built-in self-test from the repo directory in Terminal:

node index.js --selftest

A dialog appears: "Terminal" would like full access to "Calendar" → click Allow Full Access. The self-test then prints your calendar list — that's your proof the whole chain works. Verify under System Settings → Privacy & Security → Calendars: Terminal — Full Access.

Step 2 — launch your MCP host from Terminal. For Claude Desktop:

/Applications/Claude.app/Contents/MacOS/Claude

That's it. Launched this way, macOS attributes calendar requests to Terminal, which now has access.

Optional — make launching comfortable. Add an alias to ~/.zshrc:

alias claude-cal='nohup /Applications/Claude.app/Contents/MacOS/Claude >/dev/null 2>&1 & disown'

Then start Claude with claude-cal; the Terminal window can be closed afterwards.

Why Terminal? On hosts that don't disclaim responsibility for child processes, calendar requests are attributed to the host app itself — and unless its bundle declares calendar usage, macOS silently denies without a prompt. Launching from Terminal makes Terminal the responsible process, which can hold the permission.

Tools

calendar_calendars

Lists all calendars: id, name, account, writable. Call this first — several calendars can share a name across accounts (three calendars called "Calendar" is normal); disambiguate with id.

calendar_events

CRUD for events via action: read | create | update | delete.

  • readstartDate/endDate (defaults: today → +14 days), filterCalendar (name or id), search (matches title/notes/location/calendar), or id for a single event. Returns title, times, calendar, location, notes, URL, status, availability, recurrence flag and alarms — plus, for invitations, the organizer and all attendees with name, email address and response status (accepted/declined/pending), including which attendee is you (isMe).

  • createtitle + startDate required; endDate (default +1 h), targetCalendar (default: system default calendar), isAllDay, location, note, url, availability (busy/free/tentative/unavailable), alarms ([{"relativeOffset": -1800}] = 30 min before), recurrenceRules (frequency daily/weekly/monthly/yearly, interval, daysOfWeek 1=Sun…7=Sat, endDate or occurrenceCount).

  • updateid required; any create-field to change, plus clearAlarms, clearRecurrence, span (this-event | future-events), occurrenceStart to target a specific occurrence of a recurring event.

  • deleteid required; span/occurrenceStart as above.

Dates use local time, format YYYY-MM-DD HH:mm:ss (or just YYYY-MM-DD).

calendar_free_slots

Finds free time windows across calendars — for planning a week or finding time for a call against a shared calendar. Parameters: startDate (required), endDate (default +7 days), durationMinutes (5–1440, default 30), dayStart/dayEnd (default 09:00–21:00), calendars (busy sources; default all), includeAllDay (default false — holidays and birthdays don't block), treatFreeAsBusy (default false). Returns per-day slots with date, from, to and length.

Example prompts

  • "What's on my calendar today?"

  • "Plan my week: list everything Monday–Friday grouped by day."

  • "Create 'Dentist' on Thursday 14:00–15:00 in my Work calendar, reminder 30 minutes before."

  • "Move my 10 o'clock meeting tomorrow to 11."

  • "When could I make a 45-minute call this week, considering my calendar and the shared one?"

  • "Who hasn't responded to Friday's meeting invite yet, and what are their email addresses?"

  • "Block tomorrow 12–13 as 'Focus time', marked as free so it doesn't block scheduling."

Scheduled tasks (daily briefings etc.)

One non-obvious constraint if you automate with this server: Claude's cloud-based scheduled tasks cannot use it. They fire as headless cloud sessions, and the bridge that exposes local MCP servers only attaches to interactive sessions — so scheduled cloud runs have no calendar access even with your Mac awake and Claude Desktop open, and they fail silently (the run continues without calendar data). For a recurring calendar briefing, create a local scheduled task in the Claude Desktop app instead; local runs have this server attached. Per-run requirements: Mac awake, Claude Desktop running. (Observed July 2026; host behavior may change.)

Limitations

  • Sending invitations (adding attendees) is not possible. Apple exposes no public API for adding attendees to an event — EKEvent.attendees is read-only, and Calendar.app's scripting dictionary does not support it either. Practical routes: create the event here, then add invitees in the Calendar app; or use a shared calendar — events created in a shared iCloud calendar automatically notify the other participants, which covers many "invite" use cases without any invitation at all.

  • Responding to invitations (accept/decline) is not possible. Same reason: no public API for setting your participation status. Attendee names, email addresses and their responses are fully readable; changing yours requires the Calendar app. Both limitations apply to every third-party calendar tool on macOS, not just this one.

  • Each tool call spawns a short-lived osascript process (~100–300 ms overhead). Fine for interactive use; not built for bulk sync.

  • Very large date ranges are capped by EventKit's internal query limits (~4 years per read).

  • macOS only, by nature.

Troubleshooting

  • The permission dialog appeared but the tool call timed out — normal on the very first use: the dialog can outlive the host's tool timeout. Click Allow, then simply retry the call once.

  • No dialog ever appears and calls fail with "Kein Kalender-Vollzugriff: …" — your host attributes the request to a process that cannot prompt. Use the Terminal fallback above. Also check Screen Time / MDM restrictions; tccutil reset Calendar gives you a clean slate to retry.

  • You clicked "Don't Allow" or granted "Add Only" — run tccutil reset Calendar, restart the host, trigger a calendar action and choose Full Access this time.

  • Broke after switching Node versions (nvm) — the grant is bound to the Node binary; the dialog reappears once, just allow it again. (Also update the command path in your config.)

Roadmap

  • Apple Reminders support (same JXA approach; needs the separate Reminders permission)

  • npm publish for npx installation

  • Optional read-only mode

License

MIT — see LICENSE.


Born from a long debugging session that started with an empty Calendars pane in System Settings and ended at flags=0x10000(runtime). If this README saved you that evening: you're welcome.

Available Tools

3 tools
calendar_calendarsA

Reads all calendar collections (name, account, id, writable). Use this first to discover exact calendar names before filtering or creating events. Note: several calendars can share the same name across accounts — disambiguate with the id.

ParametersJSON Schema
NameRequiredDescriptionDefault
actionNoOptional; the only operation is read.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided, so description carries full burden. Notes that read-only operation and disambiguates issues with duplicate calendar names. Does not mention rate limits or auth, but adequate for simple read tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences that deliver purpose, usage guidance, and a caveat without waste. Each sentence adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, description explains returned fields. Provides workflow suggestion with sibling tools. Sufficient for a simple read operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% for one parameter (action). Description adds no additional meaning beyond the schema's enum and optionality. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clearly states verb 'Reads' and resource 'calendar collections', lists returned fields (name, account, id, writable), and distinguishes from sibling tools by indicating it's the first step for discovery.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly instructs to use this first to discover exact calendar names before filtering or creating events, providing workflow context. Does not explicitly state when not to use, but the guidance is strong.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

calendar_eventsA

Manages Apple Calendar events: read, create, update, delete. Recommended format: 'YYYY-MM-DD HH:mm:ss' (local time, e.g. '2026-07-14 09:00:00'). 'YYYY-MM-DD' also works. Reading defaults to today + 14 days. Attendee participation (accept/decline invitations) cannot be changed — this is an Apple platform limitation; attendee statuses are readable.

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoEvent identifier (required for update/delete; optional for read to fetch a single event).
urlNoURL to attach to the event.
noteNoEvent notes.
spanNoScope for changes to recurring events. Default: this-event.
titleNoEvent title (required for create).
actionYesThe operation to perform.
alarmsNoAlarms for the event. relativeOffset in seconds, negative = before start (e.g. -1800 = 30 min before).
searchNoRead: filter events by text in title, notes, location or calendar name.
endDateNoEnd date/time. For read: defaults to startDate + 14 days. For create: defaults to startDate + 1 hour. For all-day events, endDate is the LAST day of the event (inclusive).
isAllDayNoAll-day event flag.
locationNoEvent location.
startDateNoStart date/time. Recommended format: 'YYYY-MM-DD HH:mm:ss' (local time, e.g. '2026-07-14 09:00:00'). 'YYYY-MM-DD' also works.
clearAlarmsNoUpdate: remove all existing alarms (before optionally adding new ones).
availabilityNoHow the event blocks time (free/busy). Events marked 'free' are ignored by calendar_free_slots by default.
filterCalendarNoRead: only include events from this calendar (name or id).
targetCalendarNoCreate/update: calendar (name or id) to place the event in. Must be writable. Default: the system default calendar.
clearRecurrenceNoUpdate: remove all recurrence rules.
occurrenceStartNoFor recurring events: start time of the specific occurrence to target on update/delete. Recommended format: 'YYYY-MM-DD HH:mm:ss' (local time, e.g. '2026-07-14 09:00:00'). 'YYYY-MM-DD' also works.
recurrenceRulesNoRecurrence rules for repeating events.

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It discloses the supported actions, date format recommendations, read defaults, and the attendee limitation. However, it lacks information on error handling, permission requirements, or behavior for write operations beyond stating defaults. This is adequate but leaves gaps for a 19-parameter tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is four sentences, each adding value: purpose, format recommendation, reading default, and a critical limitation. It is front-loaded with the main action and avoids redundancy. No fluff or unnecessary details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite the tool's complexity (19 parameters, multiple actions, recurring events, alarms), the description is brief and does not cover key aspects like how to handle recurring events (span, occurrenceStart), error scenarios, or permission needs. The attendee limitation is the only non-obvious detail. For a tool of this complexity, the description is incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the description's baseline is 3. It adds value by recommending date format, explaining read defaults, and clarifying endDate behavior for all-day events. However, for most parameters (e.g., span, recurrenceRules, alarms), the description provides no additional semantics beyond the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description begins with 'Manages Apple Calendar events: read, create, update, delete,' explicitly stating the verb (manages) and resource (events) with all supported actions. It distinguishes from sibling tools calendar_calendars and calendar_free_slots, which manage calendars and find free slots respectively, by focusing on event operations.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides practical usage guidance, including recommended date format ('YYYY-MM-DD HH:mm:ss') and default reading window ('today + 14 days'). It also notes a key limitation ('Attendee participation cannot be changed') that prevents misuse. However, it does not explicitly compare with sibling tools or state when not to use this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

calendar_free_slotsA

Finds free time slots across calendars in a date range — e.g. to plan a call or a week. Busy times are taken from all (or selected) calendars; all-day events and events marked "free" are ignored by default. Recommended format: 'YYYY-MM-DD HH:mm:ss' (local time, e.g. '2026-07-14 09:00:00'). 'YYYY-MM-DD' also works.

ParametersJSON Schema
NameRequiredDescriptionDefault
dayEndNoDaily window end, 'HH:mm'. Default '21:00'.
endDateNoRange end. Default: startDate + 7 days.
dayStartNoDaily window start, 'HH:mm'. Default '09:00'.
calendarsNoOnly consider these calendars (names or ids) as busy sources. Default: all calendars.
startDateYesRange start. Recommended format: 'YYYY-MM-DD HH:mm:ss' (local time, e.g. '2026-07-14 09:00:00'). 'YYYY-MM-DD' also works.
includeAllDayNoTreat all-day events as busy. Default false (holidays/birthdays do not block slots).
durationMinutesNoMinimum slot length in minutes (5-1440). Default 30.
treatFreeAsBusyNoAlso treat events marked as 'free' availability as busy. Default false.

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must fully disclose behavioral traits. It covers busy sources (selected calendars), ignored event types, and defaults. However, it does not describe the output format (e.g., list of time slots), edge cases (if no free slots), or rate limits. The description is somewhat transparent but lacks completeness on return structure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with purpose and example. Every sentence adds value: the first gives the core function and use case, the second explains default behavior and date format. No fluff or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given 8 parameters (1 required), no output schema, and no annotations, the description is fairly complete: it explains date range, daily windows, calendar selection, and handling of all-day/free events. However, it omits the output format (what does a free slot look like?) and any pagination or limit details. This is a moderate gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description adds context on default behavior (e.g., all-day events ignored) but the parameter descriptions in the schema are already detailed. No significant additional meaning beyond schema is provided.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool finds free time slots across calendars in a date range, with an example use case ('plan a call or a week'). The verb 'finds' and resource 'free time slots' are specific. It implicitly differentiates from siblings (calendar_calendars, calendar_events) by focusing on availability, not listing calendars or events.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains default behaviors (ignoring all-day/free events) and recommends date format, but does not explicitly state when to use this tool versus calendar_events or calendar_calendars. No alternatives or exclusions are mentioned. Usage context is implied but not comparative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv1.3.1
    • First observedcalendar_calendars
    • First observedcalendar_events
    • First observedcalendar_free_slots

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a clearly distinct purpose: listing calendars, managing events, and finding free slots. No overlapping functionality.

Naming Consistency5/5

All tool names follow a consistent 'calendar_' prefix followed by a plural noun ('calendars', 'events', 'free_slots'), maintaining uniform snake_case.

Tool Count5/5

Three tools cover the essential calendar operations (discovery, event CRUD, and free time lookup) without being too few or too many for the domain.

Completeness4/5

Covers core workflows (list calendars, manage events, find free slots). Minor gap: no tool to create or delete calendars, though that may be intentional for safety.

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Transforms macOS calendar management into a conversational experience using natural language, allowing users to create, manage, and update calendar events seamlessly through an MCP-compatible client.
    331
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    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.
    13 npm
    4
    MIT