calendar-mcp

calendar-mcp

An MCP server for Apple iCloud Calendar over CalDAV, supporting list, search, create, update, delete, RSVP, and free-time finding without local macOS dependencies.

Category
Visit Server

README

dav-mcp

An MCP server for Apple iCloud Calendar and Contacts, over CalDAV and CardDAV. One Apple ID, one app-specific password, one endpoint.

It exposes the same calendar surface the Fastmail MCP server does — list, search, create, update, delete, RSVP — so a model that has driven one can drive the other without relearning field names, plus find_free_time for scheduling. It runs over stdio locally, or over authenticated HTTP for a remote client such as a Claude connector.

No local app, no EventKit, no AppleScript: it talks to caldav.icloud.com directly, so it does not need to run on the machine your calendar is synced to, and it needs none of the macOS privacy grants that an EventKit-based server does.

Tools

Tool What it does
list_calendars Calendar ids, names and colors. Event calendars only; reminder lists are out of scope.
search_events List a period or search it. Recurring events come back expanded, one result per occurrence, each independently addressable.
create_event Create a timed, all-day, or recurring event.
update_event Change any field. Given an occurrence id, edits that occurrence alone; given the series id, edits the series.
delete_event Delete an event. Given an occurrence id, cancels that occurrence and leaves the series intact.
rsvp_event Respond to an invitation: accepted, tentative or declined.
find_free_time Find openings long enough for something, honoring working hours and what actually counts as busy.
search_contacts Name, email, phone or organization → contacts with every way of reaching them.
create_contact Add a contact.
update_contact Change a contact. Emails and phones use add/remove deltas.
delete_contact Remove a contact.
list_address_books Address book ids. Most accounts have exactly one.

GET /health is served unauthenticated alongside them, for monitoring.

Invitations send real mail

create_event(participants=…), update_event(addParticipants=…/removeParticipants=…) and rsvp_event all cause iCloud to send iMIP email, immediately and irrevocably. iCloud advertises calendar-auto-schedule, so it does the sending itself the moment a PUT lands — this server never touches SMTP and has no way to recall anything.

Two consequences worth knowing before wiring this to a model:

  • Any edit to an event that already has guests mails all of them, including a one-word title fix. The tools say so in their replies rather than presenting such an edit as silent.
  • The organizer address must be one of the account's own calendar-user-addresses. iCloud accepts a PUT naming a foreign organizer and then silently sends nothing, which is indistinguishable from success — so from is validated against the account's real identities before writing.
  • Sending is not the same as delivering. After a write that touches participants, the event is re-read and iCloud's per-attendee SCHEDULE-STATUS is reported: who it reached, and who it did not. An address whose mail server refuses the message comes back 5.1, and the tool says so rather than claiming the invitation was sent.

What counts as busy

find_free_time exists because slot arithmetic across a week is exactly what models get wrong, and because "am I free" is not the same question as "what is on my calendar". Three kinds of event are deliberately not treated as busy:

  • Events marked free — TRANSP:TRANSPARENT, the standard "on my calendar but not occupying me" signal — and cancelled events.
  • Invitations the user declined. Apple leaves them on the calendar, and counting a meeting you refused would block the week with things you are not attending. Not having replied yet still counts as busy.
  • All-day events. Whether one occupies the day is genuinely ambiguous — a birthday does not, a multi-day trip does — so rather than guess, they are excluded from the arithmetic and listed separately in the reply.

Openings are reported at their full length rather than trimmed to the requested duration: knowing a two-hour gap exists is more useful than being told an hour fits somewhere in it.

Setup

Requires Python 3.12+ and uv.

uv sync
cp .env.example .env      # then fill in the two credentials

APPLE_APP_PASSWORD is an app-specific password generated at appleid.apple.com, not your Apple ID password. A two-factor account rejects the account password outright.

uv run dav-mcp                              # stdio
DAV_MCP_TRANSPORT=http uv run dav-mcp  # http on 127.0.0.1:18790

To use it from Claude Code over stdio:

claude mcp add calendar -- uv --directory /path/to/dav-mcp run dav-mcp

Configuration

Everything is environment variables; nothing is read from .env by the server itself, which only documents them.

Variable Default Meaning
APPLE_ID — Apple ID email. Required.
APPLE_APP_PASSWORD — App-specific password. Required.
DAV_MCP_DEFAULT_CALENDAR first writable Calendar create_event writes to when the caller names none, by id or display name.
DAV_MCP_TIMEZONE host zone IANA zone assumed when a caller omits timeZone.
DAV_MCP_CALDAV_ROOT https://caldav.icloud.com CalDAV entry point.
DAV_MCP_TRANSPORT stdio stdio or http.
DAV_MCP_HOST 127.0.0.1 HTTP bind address.
DAV_MCP_PORT 18790 HTTP bind port.
DAV_MCP_STATELESS true false restores per-client sessions.
DAV_MCP_AUTH none none or password. HTTP only.
DAV_MCP_PASSWORD — Shared password. Required when AUTH=password.
DAV_MCP_BASE_URL — Public URL; becomes the OAuth issuer. Required when AUTH=password.
DAV_MCP_STATE_DIR ~/.dav-mcp Where OAuth state is persisted.

Set DAV_MCP_DEFAULT_CALENDAR. iCloud does not publish schedule-default-calendar-URL — it comes back empty — so with nothing configured the only available tie-break is the order the server happens to list collections in, which changes the moment you add a calendar to the account.

Authentication

A remote MCP client has one input field: a URL. There is nowhere to put an API key. So DAV_MCP_AUTH=password starts a self-contained OAuth 2.1 authorization server whose only credential is one shared password — the client discovers it, registers itself, and gets redirected to a password form.

Dynamic client registration, PKCE, discovery metadata and the 401 challenge come from FastMCP and the MCP SDK. This project adds the login screen and the credential check. Registered clients and tokens persist across restarts; authorization codes and in-flight logins are deliberately memory-only.

Bind to localhost and put a tunnel or reverse proxy in front of it. See docs/deployment-macos.md.

Notes on iCloud

Findings that shaped the implementation, each verified against a live account rather than taken from the spec:

  • Recurrence is expanded server-side. calendar-query honors <C:expand>, so occurrences arrive individually with their own RECURRENCE-ID. There is no local expansion and no cache — ranged queries are fast enough not to need one.
  • Text search and time ranges are mutually exclusive. RFC 4791 permits a prop-filter alongside a time-range, but iCloud silently ignores the text match when both are present, and rejects the reverse order with a 412. So query is matched client-side, which also lets it cover descriptions, locations and participants rather than titles alone.
  • RECURRENCE-ID comes back in UTC while DTSTART stays in the event's own zone. Event ids therefore carry the literal server form of the key and only ever round-trip it; the human-readable recurrenceId is converted for display.
  • Scheduling is the server's job, not ours. OPTIONS advertises calendar-auto-schedule, so an ORGANIZER plus ATTENDEEs on a PUT is all it takes to send invitations, and changing your own PARTSTAT is all it takes to reply.
  • CardDAV under-reports, where CalDAV over-reported. The address book's supported-report-set lists only addressbook-multiget and sync-collection — yet addressbook-query works and genuinely filters (1 match out of 913 cards). Believing the advertisement would have meant downloading the whole book to filter locally. The rule for iCloud is to test the behavior, in either direction.
  • A contact card carries far more than any tool surface models — photos, social profiles, related names, Apple's own bookkeeping. So update_contact mutates the parsed card in place and never rebuilds it from a dict, which would silently delete all of that.
  • Writability is reported differently per protocol: calendars come back with the write-content privilege, address books with plain write. Checking only one marks the other read-only and refuses every write.

Development

uv sync --extra test
uv run pytest

The suite is entirely offline — no test touches iCloud. Fixtures reproduce the exact payload shapes iCloud returns, with identities replaced.

For manual verification against a real account, create a scratch calendar and point calendarId at it. Do not run write experiments against a calendar you care about.

License

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured