iCloud Calendar MCP Server
Provides access to iCloud Calendar over CalDAV, enabling listing calendars, reading events in date ranges, and creating, updating, moving, and deleting events. Write tools can be disabled with read-only mode.
Click on "Deploy 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., "@iCloud Calendar MCP Server What's on my calendar this week?"
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.
iCloud Calendar MCP Server
A Model Context Protocol server that gives MCP clients access to iCloud Calendar over CalDAV: list calendars, read, create, update, move and delete events. It works on Linux, macOS and Windows, since it talks to iCloud directly instead of going through the macOS Calendar app.
This is a maintained fork of zwrong/iCalendar, itself derived from the macOS
mcp-ical server. See Changes in this fork.
Tools
Tool | Description |
| Names of the available calendars |
| Events in a date range, optionally for one calendar, sorted by start time |
| New event: title, times, all-day, location, notes, URL, alarms, recurrence |
| Change any of those fields, or move the event to another calendar |
| Delete an event by identifier |
| Connection check: calendars and event counts over the last 7 days |
The calendars://list resource also lists the calendars. With MCP_ICAL_READ_ONLY=true, the three write tools are
not registered at all.
Related MCP server: icloud-calendar-mcp-server
Requirements
Python 3.12 or later and uv
An Apple ID with iCloud Calendar enabled
An app-specific password for that Apple ID
App-specific password
Sign in to appleid.apple.com, open Sign-In and Security, then App-Specific Passwords.
Generate a password, for example named
mcp-icloud-caldav.
The username is your Apple ID address, which is not necessarily an @icloud.com address.
An app-specific password gives access to iCloud CalDAV, CardDAV and IMAP: keep it out of version control and revoke
it if it leaks.
Installation
The package is published on PyPI as mcp-icloud-caldav. With uv, MCP clients can
start it directly, without a separate install step:
uvx mcp-icloud-caldavuvx downloads the package into a cached, isolated environment on first use. To install it permanently instead:
uv tool install mcp-icloud-caldav (or pipx install mcp-icloud-caldav).
From source:
git clone https://github.com/Glob0x17/mcp-icloud-caldav.git mcp-icloud-caldav
cd mcp-icloud-caldav
uv sync --no-dev
uv run mcp-icloud-caldavConfiguration
Credentials are read in this order:
Environment variables (recommended)
Variable
Required
Default
ICLOUD_USERNAMEyes
ICLOUD_PASSWORDyes
CALDAV_SERVER_URLno
https://caldav.icloud.com/MCP_ICAL_CONFIG: path to a JSON file shaped likeconfig.json.exampleconfig_private.json, thenconfig.json, at the repository root (kept for compatibility)
Other settings:
Variable | Default | Effect |
|
|
|
|
|
|
The server speaks MCP over stdio and is started by the client. To check it by hand:
ICLOUD_USERNAME=you@example.com ICLOUD_PASSWORD=xxxx-xxxx-xxxx-xxxx uvx mcp-icloud-caldavIt waits on stdin and prints nothing on stdout.
Client setup
Use the absolute path of uvx (which uvx) if the client cannot find it: clients often start servers without your
shell PATH, which shows up as spawn uvx ENOENT. Usual locations:
Installed with | Path |
The uv installer (Linux, macOS) |
|
Homebrew on Apple silicon |
|
Homebrew on Intel Macs |
|
The command and its argument are separate fields: the command is uvx (or its path) and mcp-icloud-caldav is an
argument. A config where the command is the single string "uvx mcp-icloud-caldav" fails with ENOENT, because
the client looks for a program with that exact name, space included.
Keeping the password out of the client config
Most clients store env values in clear text in their config file. A small wrapper that reads an env file avoids
that:
mkdir -p ~/.config/mcp-icloud-caldav && chmod 700 ~/.config/mcp-icloud-caldav
cat > ~/.config/mcp-icloud-caldav/env <<'EOF'
ICLOUD_USERNAME=you@example.com
ICLOUD_PASSWORD=xxxx-xxxx-xxxx-xxxx
MCP_ICAL_READ_ONLY=true
EOF
chmod 600 ~/.config/mcp-icloud-caldav/env
cat > ~/.local/bin/mcp-icloud-caldav-run <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
set -a; source "$HOME/.config/mcp-icloud-caldav/env"; set +a
exec /absolute/path/to/uvx mcp-icloud-caldav
EOF
chmod +x ~/.local/bin/mcp-icloud-caldav-runThen point the client at ~/.local/bin/mcp-icloud-caldav-run. Starting read-only and switching to false once reading works
is a sensible first step.
Claude Desktop
In claude_desktop_config.json (Settings, Developer, Edit Config). On macOS it is
~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"icloud-calendar": {
"command": "uvx",
"args": ["mcp-icloud-caldav"],
"env": {
"ICLOUD_USERNAME": "you@example.com",
"ICLOUD_PASSWORD": "xxxx-xxxx-xxxx-xxxx"
}
}
}
}or, with the wrapper, "command": "/home/you/.local/bin/mcp-icloud-caldav-run" and no env. Quit Claude Desktop
completely and start it again after editing. On macOS, server logs are in
~/Library/Logs/Claude/mcp-server-icloud-calendar.log.
Claude Code
claude mcp add --scope user icloud-calendar \
-e ICLOUD_USERNAME=you@example.com -e ICLOUD_PASSWORD=xxxx-xxxx-xxxx-xxxx \
-- uvx mcp-icloud-caldavor, with the wrapper, claude mcp add --scope user icloud-calendar -- ~/.local/bin/mcp-icloud-caldav-run.
Do not quote what follows --: -- uvx mcp-icloud-caldav registers uvx as the command and mcp-icloud-caldav as
its argument, while -- "uvx mcp-icloud-caldav" registers one program name and fails with ENOENT. To fix an
existing entry, remove it with claude mcp remove icloud-calendar --scope user and add it again.
/mcp in a session shows the server state and its tools; claude mcp get icloud-calendar shows the registered
command and arguments.
Other clients
Any MCP client that can start a stdio server works: run uvx mcp-icloud-caldav with the environment variables above.
The server is also listed in the MCP Registry as
io.github.Glob0x17/mcp-icloud-caldav.
Behavior worth knowing
Times. Event times are written in UTC. A time with an offset (
2030-01-07T09:30:00+01:00) is converted, and a time without one is taken as the machine's local time. Times are shown back in the machine's timezone. All-day events are written as dates, with the end date exclusive as the iCalendar standard requires.Moving between calendars. iCloud refuses the same event UID in two calendars, so a move cannot be "copy then delete".
update_eventwithcalendar_nameuses a WebDAVMOVE, which is atomic on the server: if it fails, the event stays where it was. A move without other changes keeps the original iCalendar data untouched.Finding events. Listings fill an in-memory index (UID to URL) that lives as long as the server process. Lookups then go straight to the event, falling back to the
<calendar>/<UID>.icsURL iCloud uses for most events, and only then to a scan of all calendars. iCloud does not support CalDAV UID searches.Volume. iCloud returns any number of events per request. The practical limit is the client: many cap a tool result at around 25,000 tokens, roughly 300 events in this server's output, so split long ranges.
Scope. Only calendars hosted on iCloud are visible. Exchange, Google or other accounts added to the Calendar app on a device are not reachable through iCloud CalDAV, and neither are read-only subscribed (ICS) calendars.
Privacy and security
The only network destination is the configured CalDAV server; there is no telemetry.
The password never appears in logs, at any level, nor in the configuration object's
repr.Logs go to stderr only, since stdout carries the MCP protocol. Event contents (titles, notes) are logged only at
DEBUGor below; atINFO, only identifiers. Some clients save server stderr to disk, so keepDEBUGfor troubleshooting.Dependencies are locked in
uv.lockand resolved from the official PyPI index only.
Troubleshooting
Symptom | Likely cause |
| Regular Apple ID password instead of an app-specific one, or a revoked one |
|
|
No events returned | Wrong date range, an event in a non-iCloud or subscribed calendar, or a timezone mix-up |
Server does not respond | Something writes to stdout and breaks the stdio protocol |
| Calendar names are case-sensitive: check |
debug_calendar_connection and MCP_ICAL_LOG_LEVEL=DEBUG help narrow things down.
Development
uv sync
uv run pytest -m "not integration" # unit tests, CalDAV client mocked, no network
uv run ruff check .Integration tests run against a real account, read-only, and are skipped unless credentials are set:
ICLOUD_USERNAME=... ICLOUD_PASSWORD=... uv run pytest -m integrationProject layout:
mcp_icloud_caldav/
├── server.py # MCP server, tool registration, logging setup
├── caldav_client.py # CalDAV access: search, create, update, move, delete, event index
├── ical.py # Thin calendar manager used by the server
├── models.py # Event and request models, iCalendar parsing
└── config.py # Configuration loading
tests/ # Unit tests
tests/integration/ # Read-only tests against a real accountChanges in this fork
0.2.1: documentation only (uvx locations, ENOENT causes, client paths on macOS).
0.2.0, compared with zwrong/iCalendar 0.1.0:
Dependencies. Lockfile regenerated from the official PyPI index (it resolved packages through a third-party mirror over plain HTTP). Upgrade to
mcp2.x (FastMCPbecameMCPServer) andcaldav3.x; the original code no longer starts against current releases.Configuration. Environment variables,
MCP_ICAL_CONFIG, read-only mode, configurable log level, no event contents in logs aboveDEBUG.Fixes.
Creating an event with a timezone offset failed (
Unable to guess TZID); all-day events and recurrence end dates were written incorrectly.Alarms were never read back.
Updating an event without a location, notes or URL failed; changing its time produced invalid iCalendar data.
Moving an event deleted it before saving the copy, so a failed save lost it.
New events got a UID containing spaces and the machine's hostname.
Behavior. Events sorted by start time, compact output without empty fields, in-memory event index.
Tests. Unit tests for configuration, read-only mode, logging, iCalendar output, moves and lookups.
Packaging. Published on PyPI as
mcp-icloud-caldavand listed in the MCP Registry. The Python package and the command are renamed frommcp_ical/mcp-ical, which clashed with the original macOS project. The unusedmcp[cli]extra was dropped.
Credits and license
MIT, see LICENSE.
The original macOS
mcp-icalserver (Omar D.), whose copyright notice is kept inLICENSEzwrong/iCalendar, the iCloud CalDAV port this fork builds on
python-caldav and the Model Context Protocol Python SDK
Available Tools
6 toolscreate_eventB
Create a new calendar event.
Before using this tool, make sure to:
Ask the user which calendar they want to use if not specified (check calendars://list)
Ask if they want to add a location if none provided
Ask if they want to add any notes/description if none provided
Confirm the date and time with the user
Ask if they want to set reminders for the event
Args:
title: Event title
start_time: Start time in ISO format (YYYY-MM-DDTHH:MM:SS)
end_time: End time in ISO format (YYYY-MM-DDTHH:MM:SS)
notes: Optional event notes/description. Ask user if they want to add notes.
location: Optional event location. Ask user if they want to specify a location.
calendar_name: Optional calendar name. Ask user which calendar to use, referencing calendars://list.
all_day: Whether this is an all-day event
reminder_offsets: List of minutes before the event to trigger reminders e.g. [60, 1440] means two reminders, the first 24 hours before the event and the second one hour before.
recurrence_rule: Optional recurrence rule for the event. This should be an instance of RecurrenceRule with the following fields:
- frequency: Frequency of the recurrence (e.g., DAILY, WEEKLY, MONTHLY, YEARLY).
- interval: Interval between recurrences (e.g., every 2 weeks).
- end_date: Optional end date for the recurrence. If specified, the recurrence will stop on this date.
- occurrence_count: Optional number of occurrences. If specified, the recurrence will stop after this many occurrences.
- days_of_week: Optional list of weekdays for the event. Use integers to represent days:
- Sunday: 1
- Monday: 2
- Tuesday: 3
- Wednesday: 4
- Thursday: 5
- Friday: 6
- Saturday: 7
Note: Both end_date and occurrence_count should not be set simultaneously; choose one or the other, or leave both unset.
Inputs: create_event_request (required). Returns: result
debug_calendar_connectionARead-only
Debug calendar connection and provide detailed diagnostic information.
This tool helps diagnose why events might not be showing up. It will test:
CalDAV connection status
Calendar accessibility
Event search capabilities
Recent events count
Inputs: no named parameters. Returns: result
delete_eventADestructive
Delete a calendar event by its identifier.
Before using this tool, make sure to:
Confirm with the user that they want to delete the event
Verify the event exists by listing events first
Note that this action cannot be undone
Args: event_id: Unique identifier of the event to delete
Inputs: event_id (required). Returns: result
list_calendarsARead-only
List all available calendars.
Inputs: no named parameters. Returns: result
list_eventsARead-only
List calendar events in a date range.
The start_date should always use the time such that it represents the beginning of that day (00:00:00). The end_date should always use the time such that it represents the end of that day (23:59:59). This way, range based searches are always inclusive and can locate all events in that date range.
Args: start_date: Start date in ISO8601 format (YYYY-MM-DDT00:00:00). end_date: Optional end date in ISO8601 format (YYYY-MM-DDT23:59:59). calendar_name: Optional calendar name to filter by
Inputs: end_date (required), start_date (required), calendar_name. Returns: result
update_eventADestructive
Update an existing calendar event.
Before using this tool, make sure to:
Ask the user which fields they want to update
If moving to a different calendar, verify the calendar exists using calendars://list
If updating time, confirm the new time with the user
Ask if they want to add/update location if not specified
Ask if they want to add/update notes if not specified
Ask if they want to set reminders for the event
Args:
event_id: Unique identifier of the event to update
title: Optional new title
start_time: Optional new start time in ISO format
end_time: Optional new end time in ISO format
notes: Optional new notes/description. Ask user if they want to update notes.
location: Optional new location. Ask user if they want to specify/update location.
calendar_name: Optional new calendar. Ask user which calendar to use, referencing calendars://list.
all_day: Optional all-day flag
reminder_offsets: List of minutes before the event to trigger reminders e.g. [60, 1440] means two reminders, the first 24 hours before the event and the second one hour before.
recurrence_rule: Optional recurrence rule for the event. This should be an instance of RecurrenceRule with the following fields:
- frequency: Frequency of the recurrence (e.g., DAILY, WEEKLY, MONTHLY, YEARLY).
- interval: Interval between recurrences (e.g., every 2 weeks).
- end_date: Optional end date for the recurrence. If specified, the recurrence will stop on this date.
- occurrence_count: Optional number of occurrences. If specified, the recurrence will stop after this many occurrences.
- days_of_week: Optional list of weekdays for the event. Use integers to represent days:
- Sunday: 1
- Monday: 2
- Tuesday: 3
- Wednesday: 4
- Thursday: 5
- Friday: 6
- Saturday: 7
Note: Both end_date and occurrence_count should not be set simultaneously; choose one or the other, or leave both unset.
Inputs: event_id (required), update_event_request (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Each tool targets a clearly distinct resource and action: calendar listing, event listing, and event CRUD operations are unambiguous. The diagnostic tool is easily distinguished from list_events despite both relating to events.
All tools follow a consistent snake_case verb_noun pattern (list_calendars, list_events, create_event, update_event, delete_event, debug_calendar_connection). No mixing of conventions or vague verbs.
Six tools is well-scoped for a calendar server, covering the essential lifecycle plus a diagnostic helper. Each tool earns its place without redundancy.
Full event CRUD plus calendar listing and diagnostics covers the core domain well. Minor gaps exist, such as no get_event-by-id or calendar creation/deletion, but agents can work around these via list_events.
Maintenance
Related MCP Connectors
Read and send iCloud Mail, manage iCloud Calendar. Needs an approved account at icloud.mcp.visin.ch.
MCP server for Cronofy — read calendars, events and free/busy, and create, update or delete events.
EU-hosted calendar MCP: Google, Outlook, iCloud, GMX, WEB.DE, CalDAV. Read, book, RSVP, sync.
Hosted Google Calendar MCP server for AI agents. No self-hosting or Google Cloud setup.
Related MCP Servers
- AlicenseAqualityBmaintenanceMCP server for Apple Calendar and CalDAV providers. Enables listing, creating, updating, deleting events, and checking free/busy status with per-calendar write protection.6MIT
- AlicenseAqualityDmaintenanceEnables Claude (or any MCP client) to read and write events on your iCloud calendars via CalDAV.535 npmMIT
- AlicenseAqualityBmaintenanceEnables MCP clients to interact with CalDAV calendars, letting users view schedules, find free time, create and update events, manage tasks and dated notes, and respond to invitations on their own server.22349 npm2MIT
- AlicenseAqualityCmaintenanceEnables agents to read, create, update, and delete iCloud Calendar events, including recurring events, via CalDAV.6MIT