apple-calendar-jxa-mcp
Provides tools to interact with Apple Calendar on macOS, including listing calendars, creating/updating/deleting events, and finding free time slots.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@apple-calendar-jxa-mcpshow my calendar events for today"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.shinstall.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: iCloud 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:
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 withtccutil.Hardened runtime. Official Node.js builds (including everything nvm installs) are signed with the hardened runtime and without the
com.apple.security.personal-information.calendarsentitlement. 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 --selftestA 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/ClaudeThat'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.
read —
startDate/endDate(defaults: today → +14 days),filterCalendar(name or id),search(matches title/notes/location/calendar), oridfor 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).create —
title+startDaterequired;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(frequencydaily/weekly/monthly/yearly,interval,daysOfWeek1=Sun…7=Sat,endDateoroccurrenceCount).update —
idrequired; any create-field to change, plusclearAlarms,clearRecurrence,span(this-event|future-events),occurrenceStartto target a specific occurrence of a recurring event.delete —
idrequired;span/occurrenceStartas 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.attendeesis 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
osascriptprocess (~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 Calendargives 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
commandpath in your config.)
Roadmap
Apple Reminders support (same JXA approach; needs the separate Reminders permission)
npm publish for
npxinstallationOptional 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.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/jakobjhartmann/apple-calendar-jxa-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server