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.
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
fromis 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-STATUSis reported: who it reached, and who it did not. An address whose mail server refuses the message comes back5.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-queryhonors<C:expand>, so occurrences arrive individually with their ownRECURRENCE-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-filteralongside atime-range, but iCloud silently ignores the text match when both are present, and rejects the reverse order with a412. Soqueryis matched client-side, which also lets it cover descriptions, locations and participants rather than titles alone. RECURRENCE-IDcomes back in UTC whileDTSTARTstays 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-readablerecurrenceIdis converted for display.- Scheduling is the server's job, not ours.
OPTIONSadvertisescalendar-auto-schedule, so an ORGANIZER plus ATTENDEEs on a PUT is all it takes to send invitations, and changing your ownPARTSTATis all it takes to reply. - CardDAV under-reports, where CalDAV over-reported. The address book's
supported-report-setlists onlyaddressbook-multigetandsync-collection— yetaddressbook-queryworks 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_contactmutates 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-contentprivilege, address books with plainwrite. 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
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。