关于 icloud-calendar-mcp
icloud-calendar-mcp 是一个基于 CalDAV 访问 iCloud 日历的 MCP 服务。它面向需要让 AI 助手查询和管理 Apple 日历的用户,可在 JVM 上运行,不依赖 macOS。
主要功能
- 01列出账户下的全部日历
- 02按日期范围查询事件
- 03创建、修改和删除事件
- 04支持重复事件的单次或整个系列编辑
- 05使用应用专用密码进行认证
适用场景
- 01让 AI 助手查询本周日程
- 02通过自然语言创建或移动会议
- 03检查某一天的日程是否存在时间冲突
MCP 相关
该项目是一个 MCP 服务,向 MCP 客户端暴露日历列表、事件查询与编辑等工具,并提供日历浏览资源及会议安排、改期、冲突检查等提示词模板。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
npx -y @icloud-calendar-mcp/serverICLOUD_USERNAMEYour Apple ID email (e.g., [email protected])ICLOUD_PASSWORD密钥App-specific password for iCloud (not your main Apple ID password)本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
iCloud 日历 MCP 服务
一个 MCP(模型上下文协议)服务,通过 CalDAV 为 AI 助手提供 iCloud 日历访问能力,并采用符合 OWASP MCP Top 10 的安全控制措施。
注意
切勿使用你的主要 Apple ID 密码。 此服务需要使用专用 App 密码,该密码可单独撤销,不会影响你的 Apple ID。
为什么选择此服务
- 便携的 CalDAV。 只要能运行 JVM,就能运行此服务。它不依赖 macOS、AppleScript 或 Calendar.app。
- 持久的事件句柄。
get_events和create_event会返回一个不透明的handle,用于在不同会话和进程重启后引用事件,因此编辑或删除事件时无需重新列出事件。 - 正确处理重复事件。 系列事件会在指定范围内按每次发生展开为单个结果;你可以编辑或删除单次事件、本次及后续事件,或整个系列。
- 有界响应。 日期范围和事件数量达到上限时,会返回清晰、结构化的错误,而不是静默截断响应。
- 安全性符合 OWASP MCP Top 10,并由专门的测试套件提供保障。
- 已发布到 MCP Registry、npm 和 PyPI。
功能
MCP 工具
| 工具 | 描述 | 只读 | 具有破坏性 |
|---|---|---|---|
list_calendars | 列出 iCloud 账户中的所有日历 | 是 | 否 |
get_events | 获取日历指定日期范围内的事件 | 是 | 否 |
create_event | 创建新的日历事件 | 否 | 否 |
update_event | 更新事件(整个系列或单次事件) | 否 | 否 |
delete_event | 删除事件(整个系列或单次事件) | 否 | 是 |
MCP 资源
| 资源 | 描述 |
|---|---|
calendar://calendars | 浏览可用日历 |
MCP 提示词
由用户发起的模板,可引导 Claude 完成多步骤任务:
| 提示词 | 描述 |
|---|---|
schedule_meeting | 根据标题、参会者和时长拟定事件,然后创建该事件 |
reschedule | 查找现有事件,然后编辑以调整时间 |
find_conflicts | 列出某天的事件并报告时间重叠的时段 |
安全功能
- 凭据保护 - 仅使用环境变量,不写入代码或配置文件
- 输入验证 - 验证所有参数并提供 SSRF 防护
- 速率限制 - 按照 MCP 规范,每分钟限制 60 次读取、20 次写入
- 安全错误处理 - 错误消息不会泄露敏感数据
- 符合 OWASP MCP Top 10 - 282 项安全测试,覆盖所有主要风险
- ReDoS 防护 - 测试所有正则表达式模式,以防范灾难性回溯
- Unicode 安全 - 防范同形异义字符和编码攻击
快速开始
前置条件
- Java 21+(适用于所有安装方式)
- iCloud 账户和专用 App 密码
安装
选择你偏好的安装方式:
重要
所有选项运行的都是同一个 Java 构建。**必须在
PATH中配置 Java 21 或更高版本,使用npx和uvx时也不例外。**这些包装器会下载并启动 JAR;它们不会替代 JVM。
选项 1:npm(推荐)
npx @icloud-calendar-mcp/server
选项 2:Python(uvx)
uvx icloud-calendar-mcp
选项 3:直接使用 JAR
# Download the latest release (version-agnostic name, always resolves)
curl -LO https://github.com/icloud-calendar-mcp/icloud-calendar-mcp/releases/latest/download/icloud-calendar-mcp-all.jar
# Run
java -jar icloud-calendar-mcp-all.jar
选项 4:从源代码构建
git clone https://github.com/icloud-calendar-mcp/icloud-calendar-mcp.git
cd icloud-calendar-mcp
./gradlew fatJar
java -jar build/libs/icloud-calendar-mcp-*-all.jar
配置
将你的 iCloud 凭据设为环境变量:
export ICLOUD_USERNAME="[email protected]"
export ICLOUD_PASSWORD="your-app-specific-password"
安全提示:请使用应用专用密码,不要使用你的 Apple ID 主密码。
Claude Desktop 集成
将以下内容添加到 Claude Desktop 配置中:
| 平台 | 配置路径 |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Linux | ~/.config/claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
使用 npm(推荐)
{
"mcpServers": {
"icloud-calendar": {
"command": "npx",
"args": ["@icloud-calendar-mcp/server"],
"env": {
"ICLOUD_USERNAME": "[email protected]",
"ICLOUD_PASSWORD": "your-app-specific-password"
}
}
}
}
使用 uvx(Python)
{
"mcpServers": {
"icloud-calendar": {
"command": "uvx",
"args": ["icloud-calendar-mcp"],
"env": {
"ICLOUD_USERNAME": "[email protected]",
"ICLOUD_PASSWORD": "your-app-specific-password"
}
}
}
}
直接使用 JAR
{
"mcpServers": {
"icloud-calendar": {
"command": "java",
"args": ["-jar", "/path/to/icloud-calendar-mcp-all.jar"],
"env": {
"ICLOUD_USERNAME": "[email protected]",
"ICLOUD_PASSWORD": "your-app-specific-password"
}
}
}
}
使用示例
配置完成后,你可以向 Claude 提问:
- “我这周的日历上有什么安排?”
- “明天下午 2 点和 John 创建一个会议”
- “显示我的所有日历”
- “删除周五的牙医预约”
- “把我下午 3 点的会议改到下午 4 点”
工具参数
list_calendars
无需参数。
get_events
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
calendar_id | string | 是 | 日历标识符(来自 list_calendars) |
start_date | string | 是 | 开始日期(YYYY-MM-DD) |
end_date | string | 是 | 结束日期(YYYY-MM-DD) |
需要了解的行为:
- UTC 日期边界。
start_date和end_date会选取完整的 UTC 日历日:从start_date当天的 UTC 00:00 到end_date当天结束。定时事件会返回 UTCstartTime/endTime时刻;全天事件会返回纯YYYY-MM-DD。若要确定用户在其他时区的某一天,请在两侧各额外查询一天,并保留开始时间转换到该时区后落在目标日期的事件;全天事件是浮动日期,无需转换。 - 限制。 日期范围最多为 366 天,且
end_date不得早于start_date。响应最多包含 1000 个事件;如果单个重复系列展开后产生过多事件,则会被拒绝。若达到上限,请每次查询一周或一个月。 - 重复系列。 范围内的每次出现都会作为独立结果返回,并带有自己的
handle,以及用于标识该实例的recurrenceId。 - 写入后读取。 iCloud 不保证数据立即可见,因此刚创建的事件可能会在短时间内未出现在下一次
get_events查询中。这是 CDN 索引延迟,并非事件已删除,请勿重新创建。
每个结果都包含 uid、handle、summary、isAllDay,以及 startTime/endTime(定时事件)或 startDate/endDate(全天事件);此外还会包含已设置的 description、location、rrule、recurrenceId、status、url、categories、priority、organizer、attendeeCount 中的任意字段。
create_event
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
calendar_id | string | 是 | 目标日历 |
title | string | 是 | 事件标题 |
start_time | string | 条件必填 | 定时事件的 ISO 8601 日期时间。无时区值(2026-01-15T09:00:00)会被视为 UTC,除非设置了 timezone;带 Z 或偏移量的值表示绝对时间点,会覆盖 timezone |
end_time | string | 条件必填 | 定时事件的 ISO 8601 日期时间(规则与 start_time 相同) |
start_date | string | 条件必填 | 全天事件的开始日期 YYYY-MM-DD |
end_date | string | 条件必填 | 全天事件的结束日期 YYYY-MM-DD,含当天 |
is_all_day | boolean | 否 | 是否为全天事件 |
description | string | 否 | 事件描述 |
location | string | 否 | 事件地点 |
timezone | string | 否 | 定时事件的 IANA 时区(例如,America/New_York) |
end_timezone | string | 否 | 结束时间的 IANA 时区;当其与开始时间不同时使用(例如航班)。默认使用 timezone |
rrule | string | 否 | 重复规则(例如,FREQ=WEEKLY;BYDAY=MO) |
rdates | string[] | 否 | 额外的发生日期(RFC 5545 RDATE) |
exdates | string[] | 否 | 排除的发生日期(RFC 5545 EXDATE) |
alarms | object[] | 否 | 事件提醒(参见 提醒) |
update_event
仅修改传入的字段。
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
event_id | string | 是 | 事件的引用。建议使用 get_events/create_event 返回的不透明 handle;对于本次会话中之前获取的事件,也可以使用单独的 UID(参见引用事件) |
title | string | 否 | 新标题 |
start_time | string | 否 | 新的开始时间(ISO 8601,规则与创建时相同) |
end_time | string | 否 | 新的结束时间(ISO 8601) |
start_date | string | 否 | 全天事件的新开始日期(YYYY-MM-DD) |
end_date | string | 否 | 全天事件的新结束日期(YYYY-MM-DD) |
is_all_day | boolean | 否 | 改为全天事件 |
description | string | 否 | 新描述 |
location | string | 否 | 新地点 |
timezone | string | 否 | IANA 时区(例如,America/New_York) |
end_timezone | string | 否 | 结束时间的 IANA 时区;当其与开始时间不同时使用 |
rrule | string | 否 | 重复规则 |
rdates | string[] | 否 | 替换 RDATE(省略则保留,空数组则清除) |
exdates | string[] | 否 | 替换 EXDATE(省略则保留,空数组则清除) |
alarms | object[] | 否 | 替换提醒(省略则保留,空数组则清除,传入列表则替换;参见提醒) |
scope | string | 条件必填 | 指定重复事件编辑影响哪些发生项(参见编辑重复事件)。当 handle 指向系列中的某个发生项时必填 |
delete_event
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
event_id | string | 是 | 事件的引用。建议使用不透明的 handle;对于本次会话中之前获取的事件,也可以使用单独的 UID |
scope | string | 条件必填 | 指定重复事件删除会移除哪些发生项(参见编辑重复事件)。当 handle 指向系列中的某个发生项时必填 |
引用事件
get_events 和 create_event 会为每个事件返回两个标识符:uid 和不透明的 handle。将 handle 传给 update_event 和 delete_event。它是自包含的,可在全新进程中使用,因此常规流程是先调用 get_events(或 create_event)获取 handle,再用它调用 update_event/delete_event。跨会话无需额外查询。
也可以使用单独的 uid,但仅限于在同一会话中较早获取的事件,此时会通过短期内存缓存解析。服务端不支持无状态的 UID 查询:iCloud 会拒绝 CalDAV calendar-query UID prop-filter(HTTP 412),而不带筛选条件的查询会返回整个日历。建议使用 handle。
handle 包含事件的 ETag。如果自 handle 签发后,事件在其他地方发生了更改,编辑操作会报告冲突,而不是覆盖较新的版本;请重新运行 get_events 获取新的 handle,然后重试。成功时,update_event 会返回一个携带新 ETag 的更新后 handle,因此在一连串编辑中,下一次编辑应使用该 handle。
编辑重复事件
get_events 会为重复系列中的每次出现返回一条结果,每条结果都有自己的 handle。编辑或删除某个出现项的 handle 时,请设置 scope:
this_occurrence:仅更改或取消该实例。this_and_future:更改该实例及之后的所有实例。all_events:整个系列。
当 handle 指向系列中的某个出现项时,必须设置 scope。未设置时,操作会被拒绝,因此单个出现项的编辑不会意外更改整个系列。对于独立事件,请省略 scope。rrule、rdates 和 exdates 不能与 this_occurrence 或 this_and_future 组合使用。
提醒
create_event 和 update_event 接受一个 alarms 数组。每个条目都是一个对象:
| 字段 | 必填 | 描述 |
|---|---|---|
trigger | 是 | 相对时长(-PT15M、-P1D)或绝对 UTC 时间点(20260115T093000Z) |
action | 否 | DISPLAY(默认值)、AUDIO 或 EMAIL |
description | 否 | 提醒文本(DISPLAY 的默认值为 "Reminder") |
summary | 否 | 主题行,仅适用于 EMAIL |
repeat_count | 否 | 重复次数 |
repeat_duration | 否 | 重复间隔(RFC 5545 时长) |
调用 update_event 时,省略 alarms 可保留现有提醒;传入空数组可清除提醒;传入列表则会替换提醒。
故障排除
新事件不会立即显示。 iCloud 不保证事件会立即可见,因此刚创建的事件可能会在短时间内未出现在下一次 get_events 的结果中(CDN 索引延迟)。create_event 的成功响应是权威结果。请勿重新创建事件。
get_events 返回大小或数量错误。 查询范围过大。请一次查询一周或一个月;响应最多包含 1000 个事件。
身份验证失败。 请使用应用专用密码,而不是 Apple ID 密码,并将 ICLOUD_USERNAME 设置为完整的 iCloud 电子邮件地址。
不确定该使用哪个 calendar_id。 调用 list_calendars,并使用目标日历的 id。
重复事件编辑被拒绝或更改了每个实例。 请设置 scope(参见编辑重复事件):出现项的 handle 需要设置为 this_occurrence、this_and_future 或 all_events。
时间看起来因时区而偏移。 get_events 使用 UTC 日期边界(参见 get_events)。创建定时事件时请传入 timezone,并使用额外一天的方法来解析本地日期。
安全
此服务以安全为首要考虑进行设计,并遵循 OWASP MCP Top 10 指南。
隐私
该服务仅与您的机器(通过 STDIO)和 iCloud(caldav.icloud.com)通信。它不收集遥测数据,也不会将您的日历数据发送到其他任何地方。凭据来自环境变量,且绝不会记录到日志中。
安全控制
| 控制措施 | 实现方式 |
|---|---|
| 凭据存储 | 仅使用环境变量,绝不记录到日志中或对外暴露 |
| 输入验证 | 验证所有输入(日历 ID、日期、时间、文本字段) |
| SSRF 防护 | 阻止内部 IP、localhost 和危险的 URI 方案 |
| 速率限制 | 滑动窗口:每分钟 60 次读取、20 次写入 |
| 错误处理 | 从错误信息中清除密码、令牌、路径和电子邮件地址 |
| 注入防护 | 正确转义 ICS 内容,并测试命令注入 |
| ETag 规范化 | 符合 RFC 7232,移除引号、W/ 前缀和 XML 实体 |
| Content-Length 防护 | 在缓冲前提前拒绝过大的响应 |
| 熔断器 | 防止故障级联,并支持自动恢复 |
| 审计日志 | 通过 MCP logging protocol(MCP08)记录 CUD 操作 |
| ReDoS 防护 | 测试所有正则表达式模式,防止灾难性回溯 |
| Unicode 安全 | 防护形近字符、规范化和编码绕过 |
OWASP MCP Top 10 覆盖情况
| 风险 | 缓解措施 | 测试数 |
|---|---|---|
| MCP01:令牌管理不当 | 在日志和错误信息中遮蔽凭据,并安全存储 | 14 |
| MCP02:权限提升 | 固定工具集,不进行动态注册 | 5 |
| MCP03:工具参数注入 | 输入验证、参数化操作 | 8 |
| MCP04:敏感数据暴露 | 错误信息清理、凭据遮蔽 | 10 |
| MCP05:命令注入 | 将输入视为数据,而非执行内容 | 3 |
| MCP06:提示词注入 | 将恶意文本作为数据存储,而非进行解释 | 3 |
| MCP08:不安全日志记录 | 速率限制、敏感数据清理 | 31 |
| MCP09:资源耗尽 | 速率限制、输入大小限制、DoS 防护 | 25 |
| MCP10:上下文过度共享 | 状态隔离,不会在请求间泄露数据 | 3 |
完整的安全文档和漏洞披露流程请参阅 SECURITY.md。
测试
测试分布在两个位置:MCP 服务模块和 vendored icaldav-core
iCalendar 库。两者均可通过以下命令运行:
./gradlew test
测试覆盖率(MCP 服务)
| 类别 | 测试数 | 描述 |
|---|---|---|
| 安全 | 282 | 对抗性输入、OWASP MCP Top 10、ReDoS、Unicode |
| CalDAV 协议 | 181 | XML 解析、HTTP 客户端、模型、ETag 规范化 |
| ICS 格式 | 150 | RFC 5545 解析、构建、修补 |
| 错误处理 | 56 | 安全错误响应、凭据清理 |
| 集成 | 45 | 端到端工具、符合 MCP 规范、注释 |
| 输入验证 | 44 | 所有参数验证规则 |
| 服务层 | 26 | 日历操作、缓存 |
| 速率限制 | 18 | 并发访问、窗口重置 |
| 取消 | 12 | 操作取消、清理 |
| 日志记录 | 9 | 符合 MCP 日志规范 |
| 进度 | 9 | 进度报告 |
| E2E | 11 | 实时 CalDAV + 端到端集成 |
安全测试类别
| 类别 | 测试数 | 覆盖范围 |
|---|---|---|
| 对抗性输入 | 53 | SQL/NoSQL 注入、XSS、路径遍历 |
| ICS 补丁器安全 | 43 | CRLF 注入、属性注入、编码攻击 |
| Unicode 安全 | 38 | 同形异义字符、规范化、RTL 覆盖 |
| 日志记录器安全 | 31 | 日志注入、凭据清理 |
| OWASP MCP 风险 | 29 | MCP01-10 特定攻击向量 |
| 进度安全 | 27 | 令牌枚举、注入 |
| ReDoS 防护 | 25 | 灾难性回溯、资源耗尽 |
| 取消操作安全 | 22 | 重放攻击、竞态条件 |
| 凭据安全 | 14 | 令牌屏蔽、安全存储 |
运行特定测试
# All tests
./gradlew test
# Security tests only
./gradlew test --tests "*SecurityTest*"
./gradlew test --tests "AdversarialTest"
# OWASP MCP specific tests
./gradlew test --tests "OwaspMcpSecurityTest"
# Unicode security tests
./gradlew test --tests "UnicodeSecurityTest"
# ReDoS protection tests
./gradlew test --tests "ReDoSSecurityTest"
# CalDAV tests
./gradlew test --tests "*CalDav*"
# ICS tests
./gradlew test --tests "*Ics*"
架构
+------------------------------------------------------------------+
| MCP Server (STDIO Transport) |
| |
| +----------------+ +----------------+ +----------------------+ |
| | Rate Limiter | | Input | | Secure Error | |
| | 60r/20w/min | | Validator | | Handler | |
| +----------------+ +----------------+ +----------------------+ |
| |
| +----------------+ +----------------+ +----------------------+ |
| | MCP Logger | | Cancellation | | Progress | |
| | (RFC 5424) | | Manager | | Reporter | |
| +----------------+ +----------------+ +----------------------+ |
| |
| Tools: list_calendars | get_events | create_event | |
| update_event | delete_event |
| |
| Resources: calendar://calendars |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| CalendarService |
| Orchestrates CalDAV operations, caches calendar metadata |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| CalDAV Client Layer |
| |
| +-------------------+ +-------------------+ +----------------+ |
| | OkHttpCalDav | | IcsParser | | IcsBuilder | |
| | Client | | (icaldav-core) | | (icaldav-core)| |
| +-------------------+ +-------------------+ +----------------+ |
| |
| +-------------------+ +-------------------+ +----------------+ |
| | ICloudXml | | IcsPatcher | | EtagUtils | |
| | Parser | | (event edits) | | (RFC 7232) | |
| +-------------------+ +-------------------+ +----------------+ |
| |
| +-------------------+ |
| | Credential | |
| | Manager | |
| +-------------------+ |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| iCloud CalDAV API |
| caldav.icloud.com |
+------------------------------------------------------------------+
开发
构建
# Build
./gradlew build
# Build fat JAR
./gradlew fatJar
# Run tests
./gradlew test
# Clean build
./gradlew clean build
项目结构
src/main/kotlin/org/onekash/mcp/calendar/
├── Main.kt # MCP server entry point
├── caldav/ # CalDAV protocol implementation
│ ├── CalDavClient.kt # Client interface
│ ├── CalDavModels.kt # Domain models
│ ├── OkHttpCalDavClient.kt
│ ├── ICloudXmlParser.kt
│ └── EtagUtils.kt # RFC 7232 ETag normalization
├── ics/ # ICS format handling (via icaldav-core)
│ ├── IcsParser.kt # Parse iCalendar data
│ ├── IcsBuilder.kt # Generate iCalendar data
│ └── IcsPatcher.kt # Patch existing events (CRLF-safe)
├── service/ # Business logic
│ └── CalendarService.kt # CalDAV orchestration + event cache
├── security/ # Security controls
│ └── CredentialManager.kt
├── validation/ # Input validation
│ └── InputValidator.kt
├── error/ # Error handling
│ └── SecureErrorHandler.kt
├── ratelimit/ # Rate limiting
│ └── RateLimiter.kt
├── logging/ # MCP logging
│ └── McpLogger.kt
├── progress/ # Progress reporting
│ └── ProgressReporter.kt
└── cancellation/ # Operation cancellation
└── CancellationManager.kt
使用 MCP Inspector 进行测试
ICLOUD_USERNAME="[email protected]" \
ICLOUD_PASSWORD="test-app-password" \
npx @mcp-use/inspector java -jar build/libs/icloud-calendar-mcp-*-all.jar
贡献
欢迎贡献!请参阅 CONTRIBUTING.md 了解指南。
安全问题
如需报告安全漏洞,请参阅 SECURITY.md 了解我们的负责任披露流程。请勿公开提交安全漏洞相关问题。
许可证
本项目采用 Apache License 2.0 许可,详情请参阅 LICENSE 文件。
致谢
- Anthropic 提供的 Model Context Protocol
- Anthropic 和 JetBrains 提供的 MCP Kotlin SDK
- ical4j,用于底层 ICS 解析(通过捆绑的 icaldav-core 库)
- OkHttp,用于 HTTP 客户端
- OWASP MCP Top 10,提供安全指南
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。