che-ical-mcp

by psychquant

Not rated
GitHub

About

Native macOS Calendar & Reminders MCP server via Apple EventKit — events, reminders, recurrence, location triggers, conflict checks, and batch ops across iCloud/Google/Exchange.

Details

Author
psychquant
Categories
Productivity, Other

Setup

Install che-ical-mcp in your MCP client (Claude Desktop, Cursor, Windsurf, and others).

Repository: https://github.com/psychquant/che-ical-mcp

Follow the installation instructions in the repository README, then restart your MCP client.

Give Claude native control of macOS Calendar and Reminders.A Swift MCP server built directly on EventKit — 29 tools for events, reminders, tags, batch operations, conflict detection, and undo/redo. Not just calendar events: it drives Reminders and tasks too.

Claude Code— register this repo as a marketplace, then install the plugin. The plugin bundles the/today,/week,/quick-event,/remindslash commands and a PreToolUse hook that verifies day-of-week on every event write:

claude plugin marketplace add PsychQuant/che-ical-mcp claude plugin install che-ical-mcp@che-ical-mcp

Claude Desktop— download the latest.mcpbfromReleasesand double-click to install.

Standalone MCP— the 29-tool server on its own, no plugin extras:

mkdir -p ~/bin curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP && chmod +x ~/bin/CheICalMCP claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP

On first use, macOS prompts forCalendarandRemindersaccess — clickAllow. Building from source, upgrading in place, or running under SSH / launchd / VS Code? SeeInstallationfor the full guide.

The quick paths live at thetop of this README. This is the full reference — manual config, source builds, permission edge cases, in-place upgrades, and CLI mode.

- macOS 14.0+ (Sonoma or later — required since v1.11.0 for the full TCC permission API)
- Xcode Command Line Tools (only if building from source)

One-click (recommended):download the latestche-ical-mcp-<version>.mcpbfromReleases, double-click, and restart Claude Desktop.

Manual config:download the binary, then pointclaude_desktop_config.jsonat it.

curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o /usr/local/bin/che-ical-mcp chmod +x /usr/local/bin/che-ical-mcp

Edit~/Library/Application Support/Claude/claude_desktop_config.json, then restart Claude Desktop:

{ "mcpServers": { "che-ical-mcp": { "command": "/usr/local/bin/che-ical-mcp" } } }
claude plugin marketplace add PsychQuant/che-ical-mcp claude plugin install che-ical-mcp@che-ical-mcp

- Inside Claude Code, the slash equivalents/plugin marketplace add PsychQuant/che-ical-mcpand/plugin install che-ical-mcp@che-ical-mcpwork the same way.
- Add the marketplace through itsGit repo(owner/repo), not a rawmarketplace.jsonURL — the pluginsourceis a same-repo relative path (./plugin) that only resolves when added via Git.
- Also bundled in thepsychquant-claude-pluginsaggregator (claude plugin install che-ical-mcp@psychquant-claude-plugins); both serve the same versioned binary.
- The wrapper auto-downloads the binary to~/bin/CheICalMCPon first use if it isn't already there.

mkdir -p ~/bin # If upgrading, remove the old binary first. On macOS 26 the kernel can kill a # fresh binary that inherits a stale code-signature cache from the old inode — # one a running MCP process may still be holding open. rm -f ~/bin/CheICalMCP curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP chmod +x ~/bin/CheICalMCP # --scope user: available in all projects · --transport stdio: local stdin/stdout claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP

💡 Tip:Keep the binary in a local directory like~/bin/. Cloud-synced folders (Dropbox, iCloud, OneDrive) can trigger MCP connection timeouts when sync touches the file.

git clone https://github.com/PsychQuant/che-ical-mcp.git cd che-ical-mcp make release && make install claude mcp add --scope user --transport stdio che-ical-mcp -- ~/bin/CheICalMCP

⚠️ Swift 6 / Xcode 18 users:Don't runswift builddirectly — the upstream MCP SDK has a concurrency error (swift-sdk#214). The Makefile auto-detects this and falls back to Swift 5 language mode.

On first use, macOS will prompt forCalendarandRemindersaccess. ClickAllowfor both.

⚠️ macOS Sequoia (15.x) Note:The permission dialog is attributed to theparent applicationthat launched the MCP server, not the binary itself. This means:

If the permission dialog doesn't appear(common with VS Code), you need to addNSCalendarsFullAccessUsageDescriptionto VS Code's Info.plist:

# Add calendar usage description to VS Code /usr/libexec/PlistBuddy -c "Add :NSCalendarsFullAccessUsageDescription string 'VS Code needs calendar access for MCP extensions.'" \ "/Applications/Visual Studio Code.app/Contents/Info.plist" /usr/libexec/PlistBuddy -c "Add :NSRemindersFullAccessUsageDescription string 'VS Code needs reminders access for MCP extensions.'" \ "/Applications/Visual Studio Code.app/Contents/Info.plist" # Re-sign VS Code (required after Info.plist modification) codesign -s - -f --deep "/Applications/Visual Studio Code.app" # Restart VS Code, then the permission dialog will appear

Note:This modification will be overwritten when VS Code updates. You'll need to re-apply it after each VS Code update.

The plugin wrapper auto-downloads onfreshinstalls but does not replace an existing binary. To upgrade in place:

This queries GitHub Releases for the latest tag, downloads the new binary, and atomically replaces the current one. If it is running as an MCP server, restart your MCP host (Claude Desktop / Claude Code) afterward to pick up the new version. Manual fallback:rm -f ~/bin/CheICalMCP && curl -L https://github.com/PsychQuant/che-ical-mcp/releases/latest/download/CheICalMCP -o ~/bin/CheICalMCP && chmod +x ~/bin/CheICalMCP.

All 29 tools can be invoked directly from the command line, no MCP server required:

# Flag-based: --key value pairs CheICalMCP --cli list_events --start_date 2026-03-29 --end_date 2026-03-30 # JSON via stdin echo '{"tool":"list_calendars","arguments":{}}' | CheICalMCP --cli # From Claude Code via shell claude -p "Run: ~/bin/CheICalMCP --cli list_events_quick --range today"

Handy for launchd jobs, shell scripts, CI pipelines, and agents that prefer a subprocess over the MCP protocol. TCC permissions still apply — runCheICalMCP --setupfirst if needed.

All date parameters now accept 4 formats:

Set the display timezone for individual events — essential for multi-timezone travel itineraries.

"Create a flight departure at 09:14 Berlin time" → create_event(title: "Flight LH123", start_time: "2026-04-08T09:14:00", timezone: "Europe/Berlin", ...) "Update the hotel check-in to Dubai time" → update_event(event_id: "...", timezone: "Asia/Dubai") "Remove the custom timezone from an event" → update_event(event_id: "...", clear_timezone: true)

- timezoneparameter accepts IANA identifiers (e.g.,Europe/Berlin,America/New_York,Asia/Taipei)
- Whentimezoneis provided, naive datetimes (without offset) are interpreted in that timezone
- Event output includes the event's own timezone intimezonefield and formatsstart_date_local/end_date_localaccordingly
- Available oncreate_event,update_event, andcreate_events_batch
- Undo/redo preserves per-event timezone

Event responses include participant information when available. These fields areread-onlydue to EventKit limitations — they cannot be set or modified through the MCP.

Available in:list_events,search_events,list_events_quick,check_conflicts

attendees(array, optional) — Present when the event has participants. Each attendee object contains:

organizer(object, optional) — Present when the event has an organizer. Contains:

Note:Both fields are omitted when the event has no participants or organizer (e.g., local calendar events created without invitees).

Calendar names are now matchedcase-insensitively. If not found, the error message lists all available calendars.

- list_events:filter(all/past/future/all_day),sort(asc/desc),limit
- list_reminders:filter(all/incomplete/completed/overdue),sort(due_date/creation_date/priority/title),limit
- delete_events_batch: date range mode (before_date/after_date) +dry_runpreview

Breaking Change:list_eventsandlist_remindersnow return{events/reminders: [...], metadata: {...}}instead of a plain array.

"List all my calendars" "What's on my schedule next week?" "Create a meeting tomorrow at 2 PM titled 'Team Sync'" "Add a dentist appointment on Friday at 10 AM with location '123 Main St'" "Delete the meeting called 'Cancelled Meeting'"
"List my incomplete reminders" "Show all reminders in my Shopping list" "Add a reminder: Buy milk" "Create a reminder to call mom tomorrow at 5 PM" "Mark 'Buy milk' as completed" "Delete the reminder about groceries"
"Remove the due date from 'Buy groceries'" → update_reminder(reminder_id: "...", clear_due_date: true)
"Search for events containing 'meeting'" "Search for events with both 'project' AND 'review'" "What do I have today?" "Show me this week's schedule" "Are there any conflicts if I schedule a meeting from 2-3 PM?" "Create 3 weekly team meetings for the next 3 weeks" "Copy the dentist appointment to my Work calendar" "Move all events from 'Old Calendar' to 'New Calendar'" "Delete all the cancelled events" "Find duplicate events between 'IDOL' and 'Idol' calendars"
"Show my next 5 upcoming events" → list_events(start_date: "2026-02-06", end_date: "2026-12-31", filter: "future", sort: "asc", limit: 5) "Show my overdue reminders" → list_reminders(filter: "overdue") "Preview which events would be deleted from 'Old Calendar' before 2025" → delete_events_batch(calendar_name: "Old Calendar", before_date: "2025-01-01", dry_run: true) "Create an event at 2 PM" (no need for full ISO8601!) → create_event(start_time: "14:00", end_time: "15:00", ...)

Works with any calendar synced to macOS Calendar app:

- iCloud Calendar
- Google Calendar
- Microsoft Outlook/Exchange
- CalDAV calendars
- Local calendars

Same-Name Calendar Disambiguation (v0.6.0+)

If you have calendars with the same name from different sources (e.g., "Work" in both iCloud and Google), use thecalendar_sourceparameter:

"Create an event in my iCloud Work calendar" → create_event(calendar_name: "Work", calendar_source: "iCloud", ...) "Show events from my Google Work calendar" → list_events(calendar_name: "Work", calendar_source: "Google", ...)

If ambiguity is detected, the error message will list all available sources.

Silent permanent denial after upgrade (#154)

If one service (typically Calendar) returnsaccess deniedwhile the other works,and--print-tcc-pathand System Settings report the permission as granted, you are likely hitting the#154signature: a TCC row created by a pre-v1.7.1 (ad-hoc-signed) build is pinned to that old build's code hashes. The upgraded Developer ID binary can never match it, and on macOS 26.5+ the OS only allows the healing re-prompt when the binary carries the matchingcom.apple.security.personal-information.entitlement.

As ofv1.14.0+ the startup banner surfaces this directly— a[drift] TCC.db <service> entry pins a code requirement this binary no longer satisfiesline (#155) — when the Security-framework check can confirm the csreq mismatch. Before that,everystatus-API diagnostic (including the banner) reported green, which is exactly what made this class silent. If you hit the denial through Claude Desktop's.mcpbinstall, the denial message itself now names the real blocker and the working paths instead of the dead-end--setup(#158).

Fix: upgrade tov1.11.0 or later(the binary now ships both entitlements), restart the host app (fullCmd+Qfor Claude Desktop), and approve the permission dialog that appears on the first Calendar/Reminders access. Approving rewrites the TCC row keyed to the Developer ID requirement, so it survives all future upgrades. If you accidentallydenythe dialog, re-enable the corresponding toggle in System Settings → Privacy & Security → Calendars or Reminders.

⚠️Erratum for the #108-era workaround:tccutil reset Calendar com.checheng.CheICalMCPdoesnotwork for a bare (non-bundled) binary — it fails withOSStatus error -10814because the binary has no LaunchServices registration. And donotrun a baretccutil reset Calendar(without a bundle ID): it wipes Calendar grants forevery*app on the machine and, on a pre-entitlements binary, leaves CheICalMCP permanently unable to re-prompt.

Claude Code updates rotate the host-side grant (#170)

Under a Claude Codenative install, the real executable lives at aversioned path(~/.local/share/claude/versions/<version>;~/.local/bin/claudeis just a symlink), and macOS TCC keys the host-side Calendar/Reminders grant to that path.Every Claude Code auto-update rotates the path and silently invalidates the grant— the classic symptom is "worked yesterday, broken right after an update", with System Settings accumulating stale bare-version-number entries (2.1.202,2.1.203, …).

Fix: trigger any calendar tool call from Claude Code so macOS re-prompts (or re-creates the entry), then toggle thenewestversion-number entry ON in System Settings → Privacy & Security → Calendars / Reminders. Full checklist: thetroubleshoot-tccskill (/che-ical-mcp:check-tcc). Root cause is upstream (tracked in#170— Claude Code would need a stable TCC identity); this repo can only detect and document it.

macOS TCC (Transparency, Consent, and Control) grants privacy permissionsper-application. SSH sessions run undersshd, which is a different security context — so permissions granted to Terminal or Claude Code locally donotcarry over to SSH.

Workaround A — Run locally first (recommended):
- RunCheICalMCPonce on the target Maclocally(not over SSH)
- Grant Calendar and Reminders access when the TCC dialog appears
- SSH sessions should then inherit the grant for theCheICalMCPbinary

Workaround B — Grant Full Disk Access to sshd:
- OpenSystem Settings → Privacy & Security → Full Disk Access
- Click+, press⌘⇧G, type/usr/sbin/sshd, and add it
- Restart the SSH session

⚠️ Workaround B grantssshdbroad file access — only use this on machines you fully control.

When running CheICalMCP fromlaunchd, cron, or other non-interactive automation, macOS TCC cannot show permission dialogs. Use--setupto pre-grant permissions:

# Step 1: Run once from Terminal (triggers TCC permission dialog) CheICalMCP --setup # Step 2: Grant Calendar & Reminders access in the dialog that appears # Step 3: The binary now has permission — launchd jobs can use it

Detection: CheICalMCP automatically detects non-interactive sessions (missingTERMenv var or direct launchd child) and provides targeted error messages with--setupinstructions. This works even for indirect launch chains (launchd → Claude Code → CheICalMCP).

--setupin non-interactive sessions(#143): if you run--setupitself from a non-interactive session (noTERM/ direct launchd child) and permission is still undetermined,--setupnowskips the request and exits non-zeroinstead of hanging — a TCC dialog can't appear there, so it prints manual-grant instructions rather than blocking. Run--setupfrom a real Terminal to trigger the dialog. (An already-granted binary still reports success even when re-run non-interactively.)

Note: If--setupgrants permission but the MCP still fails under launchd, TCC may have associated the permission with the parent process. In that case, manually add CheICalMCP inSystem Settings → Privacy & Security → Calendar/Reminders.

- Current Version: v1.15.0
- Framework:
MCP Swift SDKv0.12.0
- Calendar API: EventKit (native macOS framework)
- Transport: stdio
- Platform: macOS 14.0+ (Sonoma and later — bumped from 13.0 in the post-1.10 cluster per
#119)
- Tools: 29 tools for calendars, events, reminders, tags, undo/redo, cleanup, and advanced operations

Contributions are welcome! Please feel free to submit a Pull Request.

Version numbers live in three places with different semantics:

scripts/build-mcpb.shenforces the first three match; it will fail the build if any drifts.server.jsonis intentionally decoupled because bumping it requires a rebuilt.mcpb, a fresh SHA256, and a re-submission — steps that don't happen every source release.

Signing & Notarization (required for macOS 26+)

Starting v1.7.1, release binaries are signed with a Developer ID Application certificate and notarized via Apple'snotarytool. This isrequiredon macOS 26 — ad-hoc signed binaries cannot trigger Calendar / Reminders TCC permission dialogs there.
- Apple Developer Program enrollment.
- Developer ID Application certificate installed in login keychain.

- Verify with:security find-identity -p codesigning -v(must showDeveloper ID Application: <Your Name> (<TeamID>)).
- Your Team ID is your own — find it at
https://developer.apple.com/account→ Membership Details. (The maintainer's6W377FS7BSshown anywhere in this repo is for reference only.)

No reviews yet — be the first

Sign in to leave a review

Use Google, GitHub, or an email account so ratings stay tied to real people.

Email sign in

No reviews posted yet.