icloud-calendar-mcp
Allows an agent to read, create, update, and delete events — including recurring events — on iCloud calendars, with support for listing calendars, retrieving event details, and modifying or removing individual occurrences or entire series.
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-mcpWhat events do I have on my Home calendar tomorrow?"
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
An MCP server that lets an agent read, publish, edit and delete events — including repeating ones — on iCloud calendars.
Apple ships no public REST API for iCloud Calendar, so this speaks CalDAV
against https://caldav.icloud.com using the caldav
and icalendar libraries.
Tools
Tool | Input | Output |
| — |
|
|
|
|
|
|
|
|
| The event; for a series also |
|
| The updated event and its |
|
| What was |
list_calendars exists because the other tools need an exact calendar name and
iCloud names are whatever the user typed in the Calendar app. Name matching is
case-insensitive and falls back to a unique substring match; an ambiguous or
unknown name comes back as an error listing the real calendars.
Related MCP server: icloud-mcp
Setup
1. Generate an app-specific password
Go to appleid.apple.com → Sign-In and Security →
App-Specific Passwords. You will get something shaped abcd-efgh-ijkl-mnop.
A normal Apple ID password cannot authenticate against iCloud CalDAV while two-factor authentication is switched on, and it is switched on for every modern Apple account. This is the single most common reason the tool fails to connect.
2. Install and configure
You need uv; it fetches Python 3.12+ for you if necessary.
git clone https://github.com/frizzy/icloud-calendar-mcp.git
cd icloud-calendar-mcp
cp .env.example .env # then fill it in
uv syncVariable | Required | Default | Purpose |
| yes | — | Full iCloud email address |
| yes | — | App-specific password |
| no |
| IANA name used for input times with no offset, and for rendering times back |
| no |
| Override for a non-iCloud CalDAV server |
3. Verify the connection
set -a && source .env && set +a
uv run icloud-calendar-mcp --checkThis prints the calendars it can see and exits, so credential problems surface before you wire anything up:
OK: connected to https://caldav.icloud.com as you@icloud.com
Default timezone: Europe/London
3 calendar(s):
- Home
- Work
- Family4. Register with the agent
By default the server speaks MCP over stdio, run on the same machine as the
client (to host it on another machine, see Running on a Raspberry Pi).
Replace /path/to/icloud-calendar-mcp below with the directory you cloned
into. For Claude Code:
claude mcp add icloud-calendar \
--env ICLOUD_USERNAME=you@icloud.com \
--env ICLOUD_APP_PASSWORD=abcd-efgh-ijkl-mnop \
--env CALDAV_DEFAULT_TIMEZONE=Europe/London \
-- uv --directory /path/to/icloud-calendar-mcp run icloud-calendar-mcpOr, as raw config for any host that takes the standard mcpServers shape:
{
"mcpServers": {
"icloud-calendar": {
"command": "uv",
"args": ["--directory", "/path/to/icloud-calendar-mcp", "run", "icloud-calendar-mcp"],
"env": {
"ICLOUD_USERNAME": "you@icloud.com",
"ICLOUD_APP_PASSWORD": "abcd-efgh-ijkl-mnop",
"CALDAV_DEFAULT_TIMEZONE": "Europe/London"
}
}
}
}Hermes Agent
Hermes Agent reads MCP servers from
mcp_servers in ~/.hermes/config.yaml. It gives a stdio server only the
environment variables listed under its env, so the credentials have to be
listed there. Keep the values themselves in ~/.hermes/.env:
# ~/.hermes/.env
ICLOUD_USERNAME=you@icloud.com
ICLOUD_APP_PASSWORD=abcd-efgh-ijkl-mnopThen add one of these to ~/.hermes/config.yaml.
From a local checkout (if Hermes can't find uv, use the full path that
command -v uv prints):
mcp_servers:
icloud_calendar:
command: "uv"
args: ["--directory", "/path/to/icloud-calendar-mcp", "run", "icloud-calendar-mcp"]
env:
ICLOUD_USERNAME: "${ICLOUD_USERNAME}"
ICLOUD_APP_PASSWORD: "${ICLOUD_APP_PASSWORD}"
CALDAV_DEFAULT_TIMEZONE: "Europe/London"With Docker, and nothing to install:
mcp_servers:
icloud_calendar:
command: "docker"
args: ["run", "-i", "--rm",
"-e", "ICLOUD_USERNAME", "-e", "ICLOUD_APP_PASSWORD", "-e", "CALDAV_DEFAULT_TIMEZONE",
"ghcr.io/frizzy/icloud-calendar-mcp:latest"]
env:
ICLOUD_USERNAME: "${ICLOUD_USERNAME}"
ICLOUD_APP_PASSWORD: "${ICLOUD_APP_PASSWORD}"
CALDAV_DEFAULT_TIMEZONE: "Europe/London"Over HTTP, to an always-on server (the Pi service
or Docker Compose), with
ICLOUD_CALENDAR_MCP_TOKEN=<token> added to ~/.hermes/.env:
mcp_servers:
icloud_calendar:
url: "http://127.0.0.1:8765/mcp"
headers:
Authorization: "Bearer ${ICLOUD_CALENDAR_MCP_TOKEN}"Check the connection with hermes mcp test icloud_calendar; it should list
six tools. Then run /reload-mcp in an open chat, or start a new one. The
tools show up as mcp__icloud_calendar__list_events and so on.
To give the agent read access only, add a filter to the server entry:
tools:
include: [list_calendars, list_events, get_event]Running with Docker
A multi-arch image (amd64 and arm64, so a Raspberry Pi works too) is published
as ghcr.io/frizzy/icloud-calendar-mcp. To build it yourself instead, run
docker build -t icloud-calendar-mcp . in a checkout and use that name in the
commands below.
Check your credentials first, using a .env file filled in from
.env.example:
docker run --rm --env-file .env ghcr.io/frizzy/icloud-calendar-mcp --checkAs a stdio server
The agent starts a fresh container for each session and talks to it over
stdin/stdout, so -i is required. A bare -e NAME forwards that variable from
the agent's own environment, which keeps the password out of the command line.
For Claude Code:
claude mcp add icloud-calendar \
--env ICLOUD_USERNAME=you@icloud.com \
--env ICLOUD_APP_PASSWORD=abcd-efgh-ijkl-mnop \
--env CALDAV_DEFAULT_TIMEZONE=Europe/London \
-- docker run -i --rm -e ICLOUD_USERNAME -e ICLOUD_APP_PASSWORD \
-e CALDAV_DEFAULT_TIMEZONE ghcr.io/frizzy/icloud-calendar-mcpAlways on, over HTTP
docker-compose.yml runs the streamable-HTTP server with
a restart policy, a health check and a read-only filesystem:
cp .env.example .env # fill in your credentials
echo "MCP_AUTH_TOKEN=$(openssl rand -hex 32)" >> .env
docker compose up -dIt listens on http://127.0.0.1:8765/mcp on this machine only. Connect with the
header Authorization: Bearer <MCP_AUTH_TOKEN>, just as for the
Pi service. To open it to your network, change
the port mapping to "8765:8765"; the security notes for the Pi apply. Use
docker compose logs -f for logs, and
docker compose pull && docker compose up -d to update.
Running on a Raspberry Pi
The same server can run as an always-on service on a Pi (or any systemd Linux
box), speaking MCP's streamable-HTTP transport and guarded by a bearer token.
By default it listens on 127.0.0.1 only, for an agent harness running on the
Pi itself; --lan opens it to your home network or Tailscale instead.
Requirements on the Pi: 64-bit Raspberry Pi OS (Bookworm or later),
SSH access from your machine, rsync, and uv:
ssh pi@raspberrypi.local 'curl -LsSf https://astral.sh/uv/install.sh | sh'Deploy from this directory on your machine:
deploy/deploy.sh pi@raspberrypi.local # agent on the Pi itself
deploy/deploy.sh --lan pi@raspberrypi.local # clients elsewhere on the networkThat copies the project to ~/icloud-calendar-mcp on the Pi (never .git or
.env) and runs deploy/install.sh there, which:
installs the dependencies into a local
.venvwithuv sync --frozen;on the first run, creates
/etc/icloud-calendar-mcp.env(root-only, modefrom your local
.env— sent once and then deleted on the Pi — or by prompting if you have none, and generates a randomMCP_AUTH_TOKEN;
checks the iCloud credentials with
--check;installs and starts a hardened
icloud-calendar-mcpsystemd service on port 8765, and prints the URL and token to give your agent.
Run the same command again to deploy an update; the configuration is kept.
--lan only matters on the first install; to switch afterwards, change
MCP_HOST in the env file (127.0.0.1 or 0.0.0.0) and restart.
Connect the agent. Any MCP client that supports streamable HTTP needs two
things: the URL http://127.0.0.1:8765/mcp (or http://raspberrypi.local:8765/mcp
with --lan) and the header Authorization: Bearer <token>. For Claude Code:
claude mcp add --transport http icloud-calendar http://127.0.0.1:8765/mcp \
--header "Authorization: Bearer <token>"If your harness can only launch local commands, skip the service entirely and
have it run ~/icloud-calendar-mcp/.venv/bin/icloud-calendar-mcp over stdio,
passing ICLOUD_USERNAME, ICLOUD_APP_PASSWORD and CALDAV_DEFAULT_TIMEZONE
in its environment.
For clients that only launch local commands (such as Claude Desktop's
mcpServers config), bridge with
mcp-remote:
{
"mcpServers": {
"icloud-calendar": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://raspberrypi.local:8765/mcp", "--allow-http",
"--header", "Authorization:Bearer <token>"]
}
}
}Day to day, on the Pi:
Task | Command |
Logs |
|
Status |
|
Change settings or rotate the token |
|
Health check (no token needed) |
|
Uninstall |
|
Security notes. On the default localhost bind nothing off the Pi can
connect, and the token keeps other local processes out. With --lan, traffic
is plain HTTP, so keep it on a network you trust or on Tailscale (which
encrypts it); don't port-forward it to the internet. Anyone holding the token
can read and change your calendars. To listen only on
the tailnet, set MCP_HOST to the Pi's Tailscale IP. MCP_ALLOWED_HOSTS
optionally restricts accepted Host headers as a DNS-rebinding defence. The
server refuses to bind beyond loopback without a token of at least 32
characters.
You can also run HTTP mode by hand anywhere:
MCP_AUTH_TOKEN=$(openssl rand -hex 32) uv run icloud-calendar-mcp --transport http --host 0.0.0.0How times are handled
iCloud is strict about time values, so the rules are deliberately narrow:
Input is ISO 8601.
2026-07-01T14:00:00+01:00is taken at its offset;2026-07-01T14:00:00has no offset and is read inCALDAV_DEFAULT_TIMEZONErather than being silently treated as UTC.On the wire, everything is converted to UTC and written as
...Z. That avoidsTZID=parameters, which would otherwise oblige us to ship a matchingVTIMEZONEcomponent or risk iCloud misplacing the event.Output is rendered back in
CALDAV_DEFAULT_TIMEZONEwith an explicit offset, so the caller never has to guess what a bare timestamp meant.All-day events: pass date-only values (
2026-08-03) for bothstart_timeandend_time.end_timeis the last day the event covers — the exclusiveDTENDthat iCalendar requires is added and stripped for you.
list_events defaults to a now → now + 30 days window and expands recurring
events, so each occurrence in range comes back separately (sharing a UID,
distinguished by recurrence_id). Ranges beyond 400 days are refused rather
than asking the server to expand an unbounded number of recurrences.
Editing and deleting
update_event only touches the fields you pass. An empty string clears
description or location. Moving only start_time keeps the duration; moving
only end_time keeps the start. Switching between all-day and timed needs both.
Edits are made to the stored iCalendar data in place, so alarms, attendees and
Apple's own X-APPLE-* properties survive, and SEQUENCE is bumped.
iCloud refuses CalDAV's search-by-UID (HTTP 412), so events are found by
fetching <uid>.ics directly — the name both iCloud and this server use — and,
failing that, by scanning the calendar.
Recurring events
recurrence takes an iCalendar
RRULE,
with or without the RRULE: prefix:
Want |
|
Every Monday and Wednesday |
|
Ten working days |
|
Fortnightly |
|
First of every month until the end of 2027 |
|
start_time/end_timedescribe the first occurrence, which must itself match the rule — a Tuesday start withBYDAY=MOis refused rather than left to each client's interpretation.A date-only
UNTILon a timed series means "through the end of that day" and is converted to the UTC form RFC 5545 requires.Repeating timed events are the exception to the UTC rule above: they are written as
TZID=local times with aVTIMEZONE, so a 09:00 meeting stays at 09:00 when the clocks change. Events already pinned to a timezone (anything made in Apple Calendar) keep theirs when edited.
To act on one occurrence, pass its recurrence_id from list_events as
occurrence (a date alone also works when only one occurrence falls on it):
update_event(..., occurrence=...)changes just that occurrence.delete_event(..., occurrence=...)removes just that occurrence.delete_event(..., occurrence=..., and_following=true)ends the series before it.
Without occurrence, update_event changes the whole series and
delete_event removes it. When a series is moved, its individually changed and
deleted occurrences move with it; any that no longer land on the new rule are
dropped and counted in removed_exceptions. recurrence="" turns a series
back into a single event.
Errors
Tool calls return {"status": "error", "error": "..."} rather than raising, so
the agent can read the problem and correct itself. Messages name the specific
cause — a missing env var, an unparseable timestamp, an unknown calendar (with
the available names listed), an unknown event_id, an occurrence that is not
part of the series, an RRULE that does not fit the start, a read-only calendar,
or rejected credentials.
Warnings
A successful call can still carry a warnings list when something worked but
not quite as asked, or had a side effect the user should hear about:
the event has attendees — this tool never sends invitations, updates or cancellations, and edits to someone else's meeting can be overwritten by the organizer;
a fixed offset (
-04:00) given for a repeating event was anchored to a named timezone, so occurrences follow that zone's daylight saving;editing a series discarded exceptions that no longer fit, or left occurrences with their own time, title, description or location unchanged (named by
recurrence_id, so they can be updated individually);changing the location removed Apple's map pin;
the server would not expand repeating events, so
list_eventsshows each series once;the event is an invitation to a single occurrence, or uses
EXRULE.
Not supported: "this and all following" edits (end the series with
delete_event(..., and_following=true) and create a new one), managing
attendees, and reminders/tasks.
Development
uv run pytestThe suite covers calendar-name resolution, timezone conversion in both
directions, all-day round-tripping, range validation, the recurrence-expansion
fallback for servers that reject expand, event lookup, partial updates,
recurring series (DST, per-occurrence edits and deletes, truncation, moving a
series with its exceptions), and the MCP tool schemas. It uses a
fake CalDAV calendar, so nothing touches the network.
Layout
src/icloud_calendar_mcp/
server.py MCP tool definitions and CLI entry point
http_app.py Streamable-HTTP transport with bearer-token auth
calendar_client.py CalDAV connection, calendar and event lookup, read/write/delete
recurrence.py RRULE validation and occurrence matching
config.py Environment configuration
timeutil.py ISO 8601 parsing and normalisation
Dockerfile Container image (stdio by default)
docker-compose.yml Always-on HTTP server in Docker
deploy/
deploy.sh Copy to a Pi over SSH and install/update there
install.sh Install or update the systemd service (runs on the Pi)
icloud-calendar-mcp.service systemd unit templateICloudCalendarClient is importable on its own if you ever want the CalDAV
logic without the MCP layer.
License
Available Tools
6 toolscreate_eventCreate calendar eventA
Publish a new event to an iCloud calendar. Returns the created event's ID (its iCalendar UID). Times are ISO 8601; a value with no UTC offset is read in the configured default timezone. Give date-only values for both start_time and end_time to create an all-day event, where end_time is the last day it covers. To make it repeat, pass recurrence as an iCalendar RRULE, e.g. 'FREQ=WEEKLY;BYDAY=MO,WE', 'FREQ=DAILY;COUNT=10' or 'FREQ=MONTHLY;BYMONTHDAY=1;UNTIL=20271231'; start_time/end_time then describe the first occurrence, which must itself match the rule. Repeating timed events keep their local time across daylight-saving changes.
| Name | Required | Description | Default |
|---|---|---|---|
| end_time | Yes | ||
| location | No | ||
| recurrence | No | ||
| start_time | Yes | ||
| description | No | ||
| event_title | Yes | ||
| calendar_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations present, the description carries the full burden and delivers substantial behavioral detail: the return value (iCalendar UID), timezone interpretation for ISO 8601 values, the non-obvious end_date-inclusive semantics for all-day events, the constraint that the first occurrence must match the RRULE, and DST behavior. It stops short of disclosing error/failure behavior and idempotency, which keeps it from a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single ~130-word paragraph ordered from the core action to return value, time semantics, all-day handling, and recurrence rules. Every sentence carries high-value information, the RRULE examples are concrete, and the subtle end_date-inclusive and DST notes earn their place. No filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 7 parameters, 0% schema coverage, and no annotations, the description covers the hardest parts—timezone, all-day, recurrence, and return value (with an output schema also present). The remaining gaps are calendar_name resolution (must the agent derive it from list_calendars?) and expected error behavior on invalid input. These are material for a create operation but secondary to the semantics already disclosed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It richly documents the genuinely ambiguous parameters: start_time/end_time (ISO 8601, timezone handling, date-only convention) and recurrence (exact RRULE format with three concrete examples plus constraint). Self-evident fields like location and description need no elaboration. However, calendar_name—a required parameter—is never explained as display name vs. ID or connected to list_calendars.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb+resource: 'Publish a new event to an iCloud calendar.' The creation intent is unmistakable and clearly distinguishes this tool from siblings like get_event, update_event, delete_event, and list_events. An agent can differentiate it without inspecting schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
While it never explicitly names alternatives or says 'use this instead of update_event,' it gives clear operational context: date-only values signal all-day events, and recurrence requires an RRULE whose first occurrence must match the rule. The conditional guidance ('To make it repeat...', 'Give date-only values...') effectively tells the agent how to shape the call for each scenario. It lacks explicit exclusions, so not a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_eventDelete calendar eventA
Delete an event. Without occurrence this removes the event, or the whole series if it repeats. For a repeating event, set occurrence to an occurrence's recurrence_id from list_events to delete only that one, and add and_following=true to also delete every occurrence after it (the series then ends before it). Cancellations are not sent to attendees. Check warnings in the result.
| Name | Required | Description | Default |
|---|---|---|---|
| event_id | Yes | ||
| occurrence | No | ||
| and_following | No | ||
| calendar_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It clearly states that cancellations are not sent to attendees, explains the effect of omitting occurrence (deleting the whole series), and instructs to check warnings in the result. This goes beyond typical expectations for a delete tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise yet comprehensive, front-loading the core action and then providing conditional details in a logical order. Every sentence adds value, with no redundancy or fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity of handling recurring events and the existence of an output schema, the description covers all necessary information: deletion scope, recurrence handling, cancellation behavior, and result inspection. Nothing critical is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explicitly explains the meaning and usage of occurrence and and_following, which are the non-obvious parameters. Event_id and calendar_name are self-explanatory from their names and the description's context. The description effectively adds meaning to all parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('Delete') and resource ('event'), and clearly distinguishes deletion of a single event from deletion of a recurring series. It differentiates from siblings like update_event and list_events by focusing on deletion semantics and mentioning list_events for obtaining recurrence IDs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance on when to use the occurrence and and_following parameters, and explains the behavior for recurring events. It does not explicitly list when not to use this tool versus alternatives, but the context is clear from the tool name and the description, so it is not misleading.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_eventGet calendar eventA
Fetch one event by its event_id. For a repeating event this is the series itself: its first occurrence, its recurrence rule, the excluded_occurrences that were deleted, and the modified_occurrences that were changed individually.
| Name | Required | Description | Default |
|---|---|---|---|
| event_id | Yes | ||
| calendar_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral disclosure burden. 'Fetch' signals a read-only operation, and the description goes further by explaining exactly what a repeating event returns: first occurrence, recurrence rule, excluded occurrences, and modified occurrences. This is valuable context beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two tightly written sentences. The action is front-loaded, and the second sentence adds high-value recurrence behavior without any filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a simple read tool and an output schema exists, so return values are covered. The recurrence behavior is a strong addition. However, the under-explained required calendar_name parameter leaves a meaningful gap in the input contract, making the description incomplete for fully correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must explain the parameters. It addresses event_id but completely omits calendar_name, which is required and has no documented meaning. An agent cannot tell why calendar_name is needed or how it relates to event_id from this description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb and resource ('Fetch one event by its event_id') and clearly distinguishes this from list_events by emphasizing it returns a single event. The added detail about repeating events gives additional precision and makes the tool's scope unmistakable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly implies this tool is for retrieving a single known event by ID, which is a clear usage context. It does not explicitly contrast with list_events or state when not to use it, but the one-by-ID framing is enough for an agent to route correctly most of the time.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_calendarsList calendarsA
List the iCloud calendars available to the account, with their names and CalDAV URLs. Use this to discover the exact calendar_name that create_event and list_events expect.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
There are no annotations, so the description carries the full burden. It clarifies the scope ('available to the account') and the output content, but since it is a list operation with no side effects, the read-only nature is strongly implied by the name. It does not explicitly state auth requirements or whether the output is a flat array, which an output schema may cover.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with no filler. The core purpose is front-loaded, and the usage guidance is concise and actionable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter discovery tool, the description is complete. It states what is returned, the scope, and how the result feeds into sibling tools. The output schema is present, so detailed return structure does not need to be repeated.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the description does not need to explain parameter behavior. It adds value by telling the agent what the output will be useful for, specifically the `calendar_name` consumed by event tools.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action ('List the iCloud calendars available to the account') and resource, plus the returned data ('names and CalDAV URLs'). It clearly differentiates this from the sibling event-focused tools by emphasizing calendar discovery rather than event operations.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly tells the agent when to use this tool: to discover the exact `calendar_name` expected by `create_event` and `list_events`. This gives a concrete, actionable purpose and names the dependent sibling tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_eventsList upcoming eventsA
Fetch events from an iCloud calendar within a time range. Defaults to the next 30 days when no range is given. Recurring events are expanded, so each occurrence in the range is returned separately. Ranges longer than 400 days are rejected.
| Name | Required | Description | Default |
|---|---|---|---|
| end_date | No | ||
| start_date | No | ||
| calendar_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral disclosure burden. It reveals the default 30-day range, expansion of recurring events, and the 400-day limit. These are meaningful traits beyond the schema, though it omits details like date format or pagination.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, zero filler, with the primary action stated first. Each sentence adds a distinct piece of information: purpose, default behavior, and a constraint.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema exists and annotations are absent, the description covers the essential operational aspects: what the tool does, default range, recurrence expansion, and rejection of long ranges. The main gap is parameter format guidance, but overall the context is largely sufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must explain parameters. It mentions 'time range' and defaults but never maps start_date/end_date to the range bounds or explains calendar_name's role. The constraint on long ranges is behavioral, not parameter-specific.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('Fetch events'), the resource ('iCloud calendar'), and the scope ('within a time range'). It is immediately distinguishable from siblings like create_event, get_event, and delete_event.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives helpful context (time range, default period, rejection of long ranges) but never explicitly tells the agent when to choose this over get_event or list_calendars. The intended use case is implied but not contrasted with alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
update_eventUpdate calendar eventA
Change an existing event. Only the fields you pass are changed; pass an empty string to clear description or location. Changing only start_time moves the event and keeps its duration. For a repeating event, leave occurrence out to change the whole series, or set it to an occurrence's recurrence_id from list_events to change just that one. recurrence (whole series only) replaces the RRULE; an empty string stops the event repeating, and an RRULE makes a one-off event repeat. Moving a series moves its individually changed and deleted occurrences with it; any that no longer fit the new rule are discarded and counted in removed_exceptions. Changing "this and all following" occurrences is not supported: end the series with delete_event(..., and_following=true) and create a new one. Invitations are not sent to attendees. Check warnings in the result.
| Name | Required | Description | Default |
|---|---|---|---|
| end_time | No | ||
| event_id | Yes | ||
| location | No | ||
| occurrence | No | ||
| recurrence | No | ||
| start_time | No | ||
| description | No | ||
| event_title | No | ||
| calendar_name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and delivers extensively: partial-update semantics, empty-string clearing, duration preservation when only start_time changes, per-occurrence vs whole-series editing, RRULE replacement/clearing, series-move effects on exceptions with removed_exceptions, unsupported and_following, and no invitations. This is exemplary behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Densely packed and logically ordered, starting with the basic semantics and then handling recurrence edge cases, limitations, and reminders. Every sentence adds behavioral nuance needed for correct invocation, with no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Coverage is strong for the hard parts: recurrence scoping, series moves, exception counting, and the unsupported and_following case, and it points to warnings in the result. Minor gaps exist around end_time semantics, datetime format, and not-found behavior, but these are small given the complexity and the presence of an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explains the nuanced semantics of start_time (moves event, keeps duration), occurrence (recurrence_id scope), recurrence (RRULE vs empty string), and clearing description/location. Only end_time and event_title lack explicit mention, but their meanings are obvious from names.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States 'Change an existing event' with a specific verb and resource. The contrast with siblings (create/delete/get/list) is clear from 'existing' and the subsequent modification semantics, so an agent can distinguish it without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names delete_event(..., and_following=true) as the alternative for unsupported 'this and all following' changes and points to list_events for retrieving recurrence_id. It does not explicitly cover when to use create_event vs update_event, but the 'existing event' qualifier makes that implicit.
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.
6 tool updates
v0.1.0- First observed
create_event - First observed
delete_event - First observed
get_event - First observed
list_calendars - First observed
list_events - First observed
update_event
TDQS
Scored across 6 tools
Each tool maps to a distinct resource and action: list_calendars handles calendar discovery, while create/get/update/delete_event and list_events cleanly separate single-event, bulk-listing, and write operations. Recurring-event behavior is described carefully so get_event and list_events are not confused.
All tool names follow a consistent snake_case verb_noun convention: list_calendars, create_event, get_event, update_event, delete_event, list_events. The only variation is singular vs. plural nouns, which is appropriate for single-resource vs. collection operations.
Six tools is well-scoped for a calendar integration: four event lifecycle operations plus calendar discovery and range-based event listing. Each tool has a clear purpose and none feel redundant.
Event CRUD is fully covered, including recurring-event exceptions and range queries, and calendar discovery exists to support event operations. Minor gaps remain, such as no calendar creation/update/delete and no attendee/invitation management, but the core scheduling workflow is complete.
Maintenance
Related MCP Connectors
- muse.spaceOAuthspace.muse
Let your agent into iCloud Mail, calendar and contacts. It gets actions, never your password.
Calendar API for AI agents: events, availability, Google/Microsoft setup, scheduling, and iCal.
Agent-Native Google Calendar - manage events, sync, and public booking
Manage Google, Outlook & Apple calendars and create private sync rules from chat.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables Claude (or any MCP client) to read and write events on your iCloud calendars via CalDAV.517 npmMIT
- AlicenseNot gradedqualityCmaintenanceEnables calendar management with iCloud through CalDAV, allowing users to list, create, update, and delete events via a standardized API.14 npmMIT
- AlicenseNot gradedqualityCmaintenanceProvides AI calendar operators full iCloud Calendar access through CalDAV, with two-factor authentication and app-specific passwords.MIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to interact with iCloud Calendar through CalDAV, allowing users to list, search, create, update, and delete calendar events using natural language.14 npmMIT