Runs on your machine from its package; the steps are below.
At most: moves money, runs code or commands, deletes data, or sends messages for you.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx mcp-icloud-caldav
MCP client config
{
"mcpServers": {
"icloud-calendar-caldav": {
"command": "uvx",
"args": [
"mcp-icloud-caldav"
]
}
}
}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.
| Tool | Description |
|---|---|
list_calendars | Names of the available calendars |
list_events | Events in a date range, optionally for one calendar, sorted by start time |
create_event | New event: title, times, all-day, location, notes, URL, alarms, recurrence |
update_event | Change any of those fields, or move the event to another calendar |
delete_event | Delete an event by identifier |
debug_calendar_connection | 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.
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.
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-caldav
uvx 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-caldav
Credentials are read in this order:
Environment variables (recommended)
| Variable | Required | Default |
|---|---|---|
ICLOUD_USERNAME | yes | |
ICLOUD_PASSWORD | yes | |
CALDAV_SERVER_URL | no | https://caldav.icloud.com/ |
MCP_ICAL_CONFIG: path to a JSON file shaped like config.json.example
config_private.json, then config.json, at the repository root (kept for compatibility)
Other settings:
| Variable | Default | Effect |
|---|---|---|
MCP_ICAL_READ_ONLY | false | true leaves out create_event, update_event and delete_event |
MCP_ICAL_LOG_LEVEL | INFO | TRACE, DEBUG, INFO, WARNING or ERROR. Logs go to stderr |
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-caldav
It waits on stdin and prints nothing on stdout.
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) | ~/.local/bin/uvx |
| Homebrew on Apple silicon | /opt/homebrew/bin/uvx |
| Homebrew on Intel Macs | /usr/local/bin/uvx |
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.
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-run
Then 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.
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 mcp add --scope user icloud-calendar \
-e ICLOUD_USERNAME=you@example.com -e ICLOUD_PASSWORD=xxxx-xxxx-xxxx-xxxx \
-- uvx mcp-icloud-caldav
or, 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.
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.
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.update_event with calendar_name uses a WebDAV MOVE, 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.<calendar>/<UID>.ics URL iCloud uses for most
events, and only then to a scan of all calendars. iCloud does not support CalDAV UID searches.repr.DEBUG or below; at INFO, only identifiers. Some clients save server stderr to disk, so keep DEBUG for
troubleshooting.uv.lock and resolved from the official PyPI index only.| Symptom | Likely cause |
|---|---|
401 Unauthorized | Regular Apple ID password instead of an app-specific one, or a revoked one |
spawn uvx ENOENT or ENOENT | uvx is not on the client's PATH (use its absolute path), or the command and its argument were registered as one string (see Client setup) |
| 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 'X' not found | Calendar names are case-sensitive: check list_calendars |
debug_calendar_connection and MCP_ICAL_LOG_LEVEL=DEBUG help narrow things down.
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 integration
Project 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 account
0.2.1: documentation only (uvx locations, ENOENT causes, client paths on macOS).
0.2.0, compared with zwrong/iCalendar 0.1.0:
mcp 2.x (FastMCP became MCPServer) and caldav 3.x; the original code no
longer starts against current releases.MCP_ICAL_CONFIG, read-only mode, configurable log level, no event
contents in logs above DEBUG.Unable to guess TZID); all-day events and recurrence end
dates were written incorrectly.mcp-icloud-caldav and listed in the MCP Registry. The Python package and the
command are renamed from mcp_ical / mcp-ical, which clashed with the original macOS project. The unused
mcp[cli] extra was dropped.MIT, see LICENSE.
mcp-ical server (Omar D.), whose copyright notice is kept in LICENSE