Skip to main content
Glama
frizzy
by frizzy

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

list_calendars

—

calendars: name + CalDAV URL for each calendar on the account

create_event

calendar_name, event_title, start_time, end_time, description?, location?, recurrence?

status, event_id, plus the normalised times actually written

list_events

calendar_name, start_date?, end_date?

count and events[] — each with title, start_time, end_time, event_id, location, description, all_day, and recurrence_id for occurrences of a series

get_event

calendar_name, event_id

The event; for a series also recurrence, excluded_occurrences and modified_occurrences

update_event

calendar_name, event_id, any of event_title, start_time, end_time, description, location, recurrence; occurrence?

The updated event and its scope (event, series or occurrence)

delete_event

calendar_name, event_id, occurrence?, and_following?

What was deleted: event, series, occurrence or occurrence_and_following

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 sync

Variable

Required

Default

Purpose

ICLOUD_USERNAME

yes

—

Full iCloud email address

ICLOUD_APP_PASSWORD

yes

—

App-specific password

CALDAV_DEFAULT_TIMEZONE

no

UTC

IANA name used for input times with no offset, and for rendering times back

CALDAV_URL

no

https://caldav.icloud.com

Override for a non-iCloud CalDAV server

3. Verify the connection

set -a && source .env && set +a
uv run icloud-calendar-mcp --check

This 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
  - Family

4. 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-mcp

Or, 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-mnop

Then 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 --check

As 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-mcp

Always 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 -d

It 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 network

That copies the project to ~/icloud-calendar-mcp on the Pi (never .git or .env) and runs deploy/install.sh there, which:

  1. installs the dependencies into a local .venv with uv sync --frozen;

  2. on the first run, creates /etc/icloud-calendar-mcp.env (root-only, mode

    1. from your local .env — sent once and then deleted on the Pi — or by prompting if you have none, and generates a random MCP_AUTH_TOKEN;

  3. checks the iCloud credentials with --check;

  4. installs and starts a hardened icloud-calendar-mcp systemd 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

sudo journalctl -u icloud-calendar-mcp -f

Status

systemctl status icloud-calendar-mcp

Change settings or rotate the token

sudo nano /etc/icloud-calendar-mcp.env && sudo systemctl restart icloud-calendar-mcp

Health check (no token needed)

curl http://127.0.0.1:8765/healthz

Uninstall

sudo systemctl disable --now icloud-calendar-mcp && sudo rm /etc/systemd/system/icloud-calendar-mcp.service /etc/icloud-calendar-mcp.env && rm -rf ~/icloud-calendar-mcp

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.0

How 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:00 is taken at its offset; 2026-07-01T14:00:00 has no offset and is read in CALDAV_DEFAULT_TIMEZONE rather than being silently treated as UTC.

  • On the wire, everything is converted to UTC and written as ...Z. That avoids TZID= parameters, which would otherwise oblige us to ship a matching VTIMEZONE component or risk iCloud misplacing the event.

  • Output is rendered back in CALDAV_DEFAULT_TIMEZONE with 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 both start_time and end_time. end_time is the last day the event covers — the exclusive DTEND that 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

recurrence

Every Monday and Wednesday

FREQ=WEEKLY;BYDAY=MO,WE

Ten working days

FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR;COUNT=10

Fortnightly

FREQ=WEEKLY;INTERVAL=2

First of every month until the end of 2027

FREQ=MONTHLY;BYMONTHDAY=1;UNTIL=20271231

  • start_time/end_time describe the first occurrence, which must itself match the rule — a Tuesday start with BYDAY=MO is refused rather than left to each client's interpretation.

  • A date-only UNTIL on 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 a VTIMEZONE, 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_events shows 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 pytest

The 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 template

ICloudCalendarClient is importable on its own if you ever want the CalDAV logic without the MCP layer.

License

MIT

Available Tools

6 tools
create_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.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_timeYes
locationNo
recurrenceNo
start_timeYes
descriptionNo
event_titleYes
calendar_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
event_idYes
occurrenceNo
and_followingNo
calendar_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
event_idYes
calendar_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_dateNo
start_dateNo
calendar_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.9/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_timeNo
event_idYes
locationNo
occurrenceNo
recurrenceNo
start_timeNo
descriptionNo
event_titleNo
calendar_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines4/5

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.

  1. 6 tool updatesv0.1.0
    • First observedcreate_event
    • First observeddelete_event
    • First observedget_event
    • First observedlist_calendars
    • First observedlist_events
    • First observedupdate_event

TDQS

A4.4/5.0

Scored across 6 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers