CalDAV MCP Server

io.github.lukegskw由 GitHub 账号 lukegskw 发布v0.2.0更新于 Oct 30, 2026

A TypeScript MCP server for iCloud Calendar with native multi-VALARM support.

已验证STDIO仅桌面效率与工作流沟通与协作

概览

AI 生成的概览

通过 CalDAV 连接一个 iCloud 日历账户的 MCP 服务器,让助手列出日历并读取、创建、修改和删除日程。

可以做什么
  • 列出所配置 iCloud 账户中的日历并取得不透明的日历 ID
  • 按时间范围列出日程,展开重复日程的各个实例并支持分页
  • 按资源 ID 或日历 ID 加 UID 读取单个日程
  • 创建定时、全天和重复日程,可带多个显示提醒和可选的 Apple 行程时间
  • 修改或删除单个日程或整个重复系列,可用 ETag 防止覆盖并发修改
适用场景
适合希望助手在聊天客户端中查询自己的 iCloud 日历,或新增、修改、删除约会与提醒的场景,包括重复日程和多提醒设置。
运行要求
需要本地运行环境:npm 包需 Node.js 22 或更高版本,也可使用已发布的 Docker 镜像;Claude Desktop 可用一键安装包。需要一个已启用日历和双重认证的 iCloud 账户,以及 Apple 专用密码,通过 CALDAV_USERNAME 和 CALDAV_PASSWORD 提供。需要访问 iCloud 的网络连接。每个进程只支持一个账户。
安装前请注意
需要在 CALDAV_PASSWORD 中提供 Apple 专用密码,请妥善保管客户端配置文件,不要把凭据作为工具参数传入。创建、修改和删除会改动远端日历,删除不可恢复。可选的 Streamable HTTP 端点当前没有身份验证,应仅限可信网络使用。可请求原始 iCalendar 内容,其中可能包含敏感日历数据。

安装

在 SourceWeft 中

  1. 打开 控制台中的 CalDAV MCP Server,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

CalDAV MCP Server

[TypeScript] [CI] [npm] [npm downloads] [Container] [MCP Registry] [License]

CalDAV MCP Server is a Model Context Protocol server for managing iCloud Calendar events, including native support for multiple VALARM reminders on one event.

iCloud Calendar is the only provider officially supported and manually validated in the first release. The server works with any MCP client that supports stdio or Streamable HTTP.

This independent project is not affiliated with, authorized, sponsored, or approved by Apple Inc. Apple and iCloud are trademarks of their respective owner.

Quick start

Create an Apple app-specific password, then pick the option for your client.

Claude Desktop (one click, no Node.js install): download caldav-mcp.mcpb, open it with Claude Desktop, and enter your Apple Account email and app-specific password. See Claude Desktop extension.

Any other stdio client: install Node.js 22 or newer and add this server to a JSON-configured MCP client such as Gemini CLI or Claude Code:

json
{  "mcpServers": {    "icloud-calendar": {      "command": "npx",      "args": ["--yes", "@lukegskw/caldav-mcp@latest"],      "env": {        "CALDAV_USERNAME": "[email protected]",        "CALDAV_PASSWORD": "xxxx-xxxx-xxxx-xxxx"      }    }  }}

Restart the client and confirm that it lists six calendar tools. See client-specific setup and Docker deployment below. Keep the configuration file private because it contains the app-specific password.

Navigation

About

The server connects one configured account to iCloud through CalDAV. It discovers the account's calendars and exposes normalized read and write operations through MCP.

Updates preserve the complete iCalendar resource, including unknown properties, Apple extensions, VTIMEZONE, recurrence exceptions, and alarms omitted from a patch. Writes use opaque resource identifiers and ETags instead of assuming that a CalDAV filename matches an event UID.

Calendar resources are processed in memory. The server has no telemetry and no application database, and raw iCalendar is returned only when explicitly requested.

Features

  • Discovers calendars available to the configured iCloud account.
  • Lists events in semi-open time ranges and expands recurring occurrences.
  • Creates timed, all-day, and recurring events.
  • Supports zero, one, or multiple display alarms per event.
  • Reads and writes Apple Calendar travel time (X-APPLE-TRAVEL-DURATION).
  • Emits the Apple alarm extensions expected by iCloud Calendar.
  • Reads events by opaque resource ID or by calendar ID and UID.
  • Applies partial updates while preserving omitted and unknown iCalendar data.
  • Uses ETags for optimistic concurrency on updates and deletions.
  • Rejects isolated recurrence mutations instead of changing the complete series.
  • Redacts credentials, raw calendar content, and CalDAV paths from logs and errors.
  • Runs as a non-root container with a read-only root filesystem configuration.
  • Supports stdio and Streamable HTTP MCP transports.

MCP tools

list_calendars

Lists the calendars discovered for the configured account. Each result includes an opaque calendar_id, display name, description, timezone, and best-effort write status.

list_events

Lists events in a semi-open interval and expands recurring occurrences. The maximum range is 366 days, the default page size is 100, and the maximum page size is 500. Results use a deterministic chronological order. Pagination cursors are opaque and do not represent a snapshot when events are modified during traversal.

Example input:

json
{  "calendar_id": "opaque-calendar-id",  "start": "2026-09-01T00:00:00Z",  "end": "2026-10-01T00:00:00Z",  "timezone": "Europe/Berlin",  "limit": 100}

get_event

Reads an event by resource_id, or by a calendar_id and UID pair. Raw iCalendar is excluded by default and can be requested with include_raw_ical: true for controlled diagnostics.

create_event

Creates an event and reads back the representation stored by the server.

Timed event with two alarms:

json
{  "calendar_id": "opaque-calendar-id",  "summary": "Buy Shinkansen tickets",  "start": {    "date_time": "2026-09-06T03:00:00+02:00",    "timezone": "Europe/Berlin"  },  "end": {    "date_time": "2026-09-06T03:30:00+02:00",    "timezone": "Europe/Berlin"  },  "description": "Smart-EX",  "location": null,  "alarms": [    { "minutes_before": 1440, "action": "DISPLAY" },    { "minutes_before": 0, "action": "DISPLAY" }  ],  "rrule": null}

All-day event with an exclusive end date:

json
{  "calendar_id": "opaque-calendar-id",  "summary": "Trip",  "start": { "date": "2026-09-06" },  "end": { "date": "2026-09-08" },  "alarms": []}

Recurring events accept an RFC 5545 rule without the RRULE: prefix:

json
{  "calendar_id": "opaque-calendar-id",  "summary": "Weekly planning",  "start": {    "date_time": "2026-09-07T09:00:00+02:00",    "timezone": "Europe/Berlin"  },  "end": {    "date_time": "2026-09-07T09:30:00+02:00",    "timezone": "Europe/Berlin"  },  "rrule": "FREQ=WEEKLY;BYDAY=MO;COUNT=10"}

Timed event with 30 minutes of Apple Calendar travel time:

json
{  "calendar_id": "opaque-calendar-id",  "summary": "Dentist",  "start": {    "date_time": "2026-10-05T10:00:00+02:00",    "timezone": "Europe/Amsterdam"  },  "end": {    "date_time": "2026-10-05T10:30:00+02:00",    "timezone": "Europe/Amsterdam"  },  "travel_minutes": 30}

travel_minutes sets a fixed travel duration before the event. In this example, travel starts at 09:30 and the appointment stays at 10:00–10:30. The server accepts whole minutes from 1 to 1,440 for timed events; omit the field or use null when creating an event without travel time. It does not calculate routes or change the event's start, end, or alarms.

The duration is stored in Apple's X-APPLE-TRAVEL-DURATION extension. Display in other calendar clients depends on their support for that extension. Returned events include travel_minutes, rounded to the nearest whole minute, or null when the duration is missing, invalid, or rounds to zero.

update_event

Patches an event or the master event of a recurring series. Omitted fields are preserved, null removes a nullable field, and alarms: [] removes all alarms. An optional expected_etag prevents overwriting a newer server version.

For travel time, omit patch.travel_minutes to keep the current value, set it to a number to replace the duration, or set it to null to remove the duration and related Apple routing metadata. For example, "patch": { "travel_minutes": null } clears travel time. When converting an event with travel time to all-day, include travel_minutes: null alongside the new start and end dates. Existing recurrence exceptions retain their own travel settings.

delete_event

Deletes an event or complete recurring series, optionally requiring an observed ETag. Deleting a single expanded occurrence is not supported in the current release.

Tech stack

Installation

Prerequisites

  • An iCloud account with Calendar enabled.
  • Two-factor authentication enabled for the Apple Account.
  • An app-specific password.
  • Docker and Docker Compose for container deployment, Node.js 22+ for npx, or Claude Desktop for the one-click extension.
  • pnpm is required only when building from source. Corepack and CI use the version pinned in package.json.

Node.js requirements

UseNode.js
Claude Desktop extension (.mcpb)None to install; Claude Desktop uses its built-in Node.js
npx or a global npm install22 LTS or newer; CI tests 22 and 24
Docker imageNone to install; the image ships Node.js 24
Building from source and contributing22.13 or newer, required by ESLint 10 and Vitest 5; 24 recommended

engines.node in package.json is >=22. Node.js 20 reached end of life in April 2026 and is not supported; npm prints an EBADENGINE warning on older versions. Check the version your MCP client will use with node --version. Desktop clients may use a different node than your terminal, so if a client cannot start the server, point it at the absolute path of a Node.js 22+ npx (command -v npx on macOS/Linux, where npx on Windows).

npm / npx

No global install or repository clone is required. MCP clients can launch the latest published package directly:

sh
CALDAV_USERNAME='[email protected]' \CALDAV_PASSWORD='xxxx-xxxx-xxxx-xxxx' \npx --yes @lukegskw/caldav-mcp@latest

The command waits for MCP messages on stdin and normally prints nothing to stdout. In practice, add it to the client configuration as shown in MCP client setup. For reproducible environments, replace latest with an exact published version such as X.Y.Z.

Docker Compose

The recommended installation uses the published multi-architecture image:

text
ghcr.io/lukegskw/caldav-mcp:latest

Download the Compose example:

sh
curl -O https://raw.githubusercontent.com/lukegskw/caldav-mcp/main/compose.example.yaml

Provide the Apple Account email and app-specific password, then start the service:

sh
export CALDAV_USERNAME='[email protected]'export CALDAV_PASSWORD='xxxx-xxxx-xxxx-xxxx'docker compose -f compose.example.yaml up -d

To publish a different host port, set:

sh
export CALDAV_MCP_PUBLISHED_PORT=18100docker compose -f compose.example.yaml up -d

The latest tag follows the newest stable release. Stable releases also publish an exact tag such as 0.1.6 and a minor-series tag such as 0.1. The Compose example pins latest by digest so deployments are reproducible. To upgrade, download the updated Compose example or replace the full image reference with the desired published version and digest.

The Streamable HTTP endpoint will be available at:

text
http://<host>:8100/mcp

The host port can change without changing port 8100 inside the container. No persistent volume is required; calendar data remains in iCloud.

Docker run

The same hardened container configuration can be started directly:

sh
docker run -d \  --name caldav-mcp \  --restart unless-stopped \  --read-only \  --user 10001:10001 \  --cap-drop ALL \  --security-opt no-new-privileges:true \  --tmpfs /tmp:size=16m,mode=1777 \  -e CALDAV_PROVIDER=icloud \  -e CALDAV_USERNAME \  -e CALDAV_PASSWORD \  -e CALDAV_MCP_TRANSPORT=streamable-http \  -e CALDAV_MCP_HOST=0.0.0.0 \  -p 8100:8100 \  ghcr.io/lukegskw/caldav-mcp:latest

Build the container from source

Building locally is optional. Prefer the published image unless you need to modify or audit the container build.

sh
git clone https://github.com/lukegskw/caldav-mcp.gitcd caldav-mcpdocker buildx build --load -t caldav-mcp:local .

Local Node.js installation

Building from source requires Node.js 22.13 or newer; Node.js 24 is recommended. Builds and typechecks use TypeScript 7. The typescript dependency aliases @typescript/typescript6 for ESLint, which still requires the TypeScript 6 API; @typescript/native supplies TypeScript 7’s tsc executable. See the TypeScript migration guidance.

sh
git clone https://github.com/lukegskw/caldav-mcp.gitcd caldav-mcppnpm install --frozen-lockfilecp .env.example .envpnpm buildpnpm start -- --transport stdio

In stdio mode, stdout is reserved exclusively for MCP messages. To run Streamable HTTP locally:

sh
CALDAV_MCP_TRANSPORT=streamable-http pnpm start

Configuration

All settings use the CALDAV_ or CALDAV_MCP_ prefix.

VariableRequiredDefaultDescription
CALDAV_PROVIDERNoicloudProvider policy. iCloud is the supported profile.
CALDAV_URLNohttps://caldav.icloud.comCalDAV discovery URL.
CALDAV_USERNAMEYesNoneApple Account email.
CALDAV_PASSWORDYesNoneApp-specific password, not the account password.
CALDAV_MCP_TRANSPORTNostdiostdio or streamable-http.
CALDAV_MCP_HOSTNo0.0.0.0HTTP bind address.
CALDAV_MCP_PORTNo8100HTTP listening port.
CALDAV_MCP_LOG_LEVELNoINFOApplication log level.
CALDAV_MCP_REQUEST_TIMEOUT_MSNo30000CalDAV request timeout.

The core retains an experimental generic provider policy and configurable URL to keep Apple extensions isolated from the shared iCalendar implementation. No compatibility with other providers is currently claimed.

Secrets must be supplied through the deployment platform or environment. Never commit .env, pass credentials as MCP tool arguments, or include them in diagnostic reports.

MCP client setup

Claude Desktop extension (recommended)

Claude Desktop for macOS and Windows installs the server as a one-click MCP Bundle and runs it with its built-in Node.js:

  1. Download caldav-mcp.mcpb from the latest release.
  2. Open it with Claude Desktop (double-click it, or drag it onto Settings -> Extensions) and choose Install.
  3. Enter the Apple Account email and app-specific password. The password is stored as a sensitive setting.

To upgrade, install the .mcpb from a newer release. See Claude Desktop extension for bundle contents and how it is built and released.

Gemini CLI extension

Install directly from GitHub and enable automatic extension updates:

sh
gemini extensions install https://github.com/lukegskw/caldav-mcp --auto-update

Gemini prompts for the username and stores the app-specific password as a sensitive setting. The public extension gallery discovers tagged releases from this repository.

Manual configuration

For any MCP client that accepts Streamable HTTP server definitions, configure the URL:

yaml
mcp_servers:  caldav:    url: http://<host>:8100/mcp

If the client shares the Compose network, use the service name and internal port:

yaml
mcp_servers:  caldav:    url: http://caldav-mcp:8100/mcp

For clients that launch local stdio servers, prefer the npm command from Quick start. If a desktop client cannot find npx, use the absolute path reported by command -v npx on macOS/Linux or where npx on Windows.

Claude Desktop

The extension is the simplest option. To configure the server manually instead, add the Quick start JSON under mcpServers in claude_desktop_config.json, then completely restart Claude Desktop. Open the file through Settings -> Developer -> Edit Config instead of assuming its location.

Claude Code

Claude Code can add the same stdio server at user scope:

sh
claude mcp add --transport stdio --scope user \  --env [email protected] \  --env CALDAV_PASSWORD=xxxx-xxxx-xxxx-xxxx \  icloud-calendar -- npx --yes @lukegskw/caldav-mcp@latest

This command places the values in Claude's MCP configuration. Avoid running it where shell history is shared or retained insecurely.

Codex

Codex can add the server to its shared CLI and IDE configuration:

sh
codex mcp add icloud-calendar \  --env [email protected] \  --env CALDAV_PASSWORD=xxxx-xxxx-xxxx-xxxx \  -- npx --yes @lukegskw/caldav-mcp@latest

Run codex mcp list to verify it. For finer control, use the official Codex MCP configuration in ~/.codex/config.toml or a project-scoped .codex/config.toml.

Gemini CLI

Following the Gemini CLI MCP configuration, add the server under mcpServers in ~/.gemini/settings.json (user scope) or the project's .gemini/settings.json:

json
{  "mcpServers": {    "icloud-calendar": {      "command": "npx",      "args": ["--yes", "@lukegskw/caldav-mcp@latest"],      "env": {        "CALDAV_USERNAME": "[email protected]",        "CALDAV_PASSWORD": "xxxx-xxxx-xxxx-xxxx"      }    }  }}
Claude Desktop (Docker, stdio alternative)

Claude Desktop launches local stdio servers as subprocesses. Running the published container this way keeps the app-specific password on the client machine and opens no network port, which matches the transport guidance in Limitations.

Add the server to claude_desktop_config.json:

json
{  "mcpServers": {    "icloud-calendar": {      "command": "/absolute/path/to/docker",      "args": [        "run",        "-i",        "--rm",        "--env-file",        "/absolute/path/to/caldav-mcp.env",        "-e",        "CALDAV_PROVIDER=icloud",        "-e",        "CALDAV_MCP_TRANSPORT=stdio",        "ghcr.io/lukegskw/caldav-mcp@sha256:<digest>"      ]    }  }}

-i is required. Without an attached stdin the client cannot speak MCP to the container. --rm removes the container once the client stops it.

Supply credentials through --env-file rather than -e. Arguments passed to docker run are visible in the host process list; the contents of an env file are not. The file holds the variables described in Configuration:

[email protected]CALDAV_PASSWORD=xxxx-xxxx-xxxx-xxxx

Pin the image by digest instead of latest, so that restarting the client cannot silently start a different version:

docker pull ghcr.io/lukegskw/caldav-mcp:latestdocker images --digests ghcr.io/lukegskw/caldav-mcp

Restart Claude Desktop completely after editing the configuration file.

Windows

When Claude Desktop is installed from the Microsoft Store, Windows redirects %APPDATA%\Claude into the package container and the file lives at:

%LOCALAPPDATA%\Packages\Claude_<package-id>\LocalCache\Roaming\Claude\claude_desktop_config.json

In that case dir %APPDATA%\Claude reports nothing. Server logs are written next to the configuration file, in logs\mcp-server-<server-name>.log.

Use the absolute path to docker.exe, because PATH inside the package container is not reliable. where docker prints it, typically C:\Program Files\Docker\Docker\resources\bin\docker.exe. Backslashes must be escaped in JSON.

Configuration formats differ between MCP clients. Consult the client's documentation for its exact schema and reload or restart it after changing the server definition.

Verification

Check the container state and logs:

sh
docker compose -f compose.example.yaml psdocker compose -f compose.example.yaml logs caldav-mcp

The container should report healthy. The TCP healthcheck validates the server process, not iCloud credentials.

Run the repository verification suite:

sh
pnpm install --frozen-lockfilepnpm format:checkpnpm lintpnpm typecheckpnpm test:unitpnpm test:integrationpnpm buildpnpm test:packagepnpm test:distributionpnpm build:mcpbpnpm test:mcpb

Finally, connect with an MCP client and confirm that all six tools are listed.

Limitations

  • One iCloud account is configured per server process or container.
  • The Streamable HTTP endpoint has no authentication in the current release. Restrict it to a trusted LAN, VPN, or private container network; do not expose it directly to the internet.
  • Individual recurrence occurrences are read-only. Updating or deleting the complete series is supported.
  • Only ACTION:DISPLAY alarms are created.
  • Individual iCalendar resources are limited to 5 MiB.
  • Event list ranges are limited to 366 days and pages to 500 results.
  • Events may contain at most 20 alarms.
  • Attendee scheduling is outside the current scope.
  • Providers other than iCloud are not officially supported.

Review SECURITY.md before reporting a security issue or attaching diagnostics.

Contributing

Contributions are welcome. Before opening a pull request:

sh
pnpm install --frozen-lockfilepnpm format:checkpnpm lintpnpm typecheckpnpm testpnpm builddocker buildx build --load -t caldav-mcp:test .

Changes to CalDAV writes or iCalendar serialization must preserve ETag checks, opaque resource boundaries, unknown properties, recurrence exceptions, and alarms omitted from patches. TypeScript changes must continue to satisfy the rules in .codex/rules/typescript.md.

Releasing

Releases are version-driven and automated from main so that a partial registry outage can be retried without publishing a second npm version.

  1. Prepare the new version with pnpm release:prepare X.Y.Z. This synchronizes the npm, MCP Registry, Gemini, and MCP Bundle (manifest.json) metadata.
  2. Run the verification suite, including pnpm test:distribution and pnpm test:mcpb.
  3. Commit and push the release changes to main.
  4. The release workflow validates the commit and creates the matching vX.Y.Z tag automatically before publishing.

The release workflow validates the versions, tests the packed npm artifact and the Claude Desktop bundle, and publishes the exact, minor-series, and latest container tags together with the npm package, MCP Registry entry, and GitHub release with caldav-mcp.mcpb attached. Prereleases receive only their exact container tag. Gemini can discover the tagged extension without another per-release edit. A rerun skips matching artifacts that already exist and resumes the missing steps. See Claude Desktop extension for building, testing, and shipping the .mcpb bundle.

License

MIT. See LICENSE.

来源:README.md,提交 76dbf1f

工具

6

list_calendars

List calendars

List calendars available in the configured iCloud account. Use this first to obtain the opaque calendar_id required by list_events and create_event; writable is a best-effort capability indicator.

无需输入参数。

list_events

List calendar events

List events that overlap the start-inclusive, end-exclusive interval and expand recurring series into occurrences, across at most 366 days. Use list_calendars first to obtain calendar_id and use get_event instead for one known event. Continue with next_cursor and unchanged query filters when more results are available.

输入参数end必填limitstart必填cursortimezonecalendar_id必填

get_event

Get calendar event

Read one event by resource_id, preferably from a previous result, or by the calendar_id and uid pair. Use list_events for range searches. Request raw iCalendar only for controlled diagnostics because it may contain sensitive calendar data.

输入参数uidcalendar_idresource_idinclude_raw_ical

create_event

Create calendar event

Create a new event in a writable iCloud calendar and return the stored representation; existing events are not changed. Use list_calendars first to obtain calendar_id, and use update_event when the event already exists. Timed values require matching offsets and timezones, all-day end dates are exclusive, recurrence rules omit the RRULE: prefix, and travel_minutes sets Apple Calendar travel time for timed events.

输入参数end必填rrulestart必填alarmssummary必填locationcalendar_id必填descriptiontravel_minutes

update_event

Update calendar event

Modify an existing event or entire recurring series; individual expanded occurrences are not supported. Target it by resource_id or by calendar_id with uid. Omitted patch fields are preserved, null clears nullable fields, an empty alarms array removes alarms, and expected_etag can prevent a stale write.

输入参数uidpatch必填calendar_idresource_idexpected_etag

delete_event

Delete calendar event

Permanently delete an event or entire recurring series; individual expanded occurrences are not supported. Target it by resource_id or by calendar_id with uid, and supply expected_etag when available to prevent deleting a concurrently changed event.

输入参数uidcalendar_idresource_idexpected_etag

版本历史

3
  1. v0.2.0最新Oct 21, 2026
  2. v0.1.9Sep 23, 2026
  3. v0.1.8Sep 16, 2026

评分

已满足适用检查项的 90%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分
  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具必需15 分
  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分