iCloud CalDAV MCP Connector
iCloud MCP 连接器
一个 HTTP 模型上下文协议 (MCP) 服务器,使用 iCloud 应用专用密码向支持 MCP 的客户端(例如 Claude 自定义连接器、IDE)公开 iCloud 服务。
支持: iCloud 日历 (CalDAV) + iCloud 邮件 (IMAP/SMTP)。
非官方。请保持此服务私有;它会将您的 iCloud 应用专用密码转发给 Apple 的服务器。
我为什么要构建它?
我构建它是为了在 Claude 自定义连接器中使用,这样我就能通过它修改我的 iCloud 日历,而无需手动操作。这个想法是在一个周五晚上,在 TOP Pset 到期前产生的,这最终成为了一个有趣的一日项目。
功能
HTTP MCP 服务器 (
/mcp) +GET /health日历工具(默认具备写入权限的配置):
list_calendars()list_calendars_with_events(start, end, expand_recurring=True)list_events(calendar_name_or_url, start, end, expand_recurring=True)create_event(calendar_name_or_url, summary, start, end, tzid?, description?, location?, recurrence?)update_event(calendar_name_or_url, uid, summary?, start?, end?, tzid?, description?, location?, recurrence?, clear_recurrence=False)delete_event(calendar_name_or_url, uid)
日历工具(深度研究只读配置,
DR_PROFILE=1):search(query)→ 在时间窗口内对 SUMMARY/DESCRIPTION 进行基础文本搜索fetch(ids)→ 获取搜索结果的原始text/calendarICS 数据块
邮件工具(可选,
MAIL_ENABLED=1):list_mailboxes()— 列出所有文件夹list_messages(mailbox, limit, unread_only)— 列出带有标题的邮件get_message(uid, mailbox)— 获取包含正文的完整邮件search_messages(query, mailbox, limit)— IMAP 文本搜索send_message(to, subject, body, cc?, bcc?)— 通过 SMTP 发送delete_message(uid, mailbox)— 移至废纸篓mark_message(uid, mailbox, read)— 标记为已读/未读
ISO 日期时间输入 (
YYYY-MM-DDTHH:MM:SS,可选Z或时区偏移)最小化 ICS 生成(摘要/描述转义),在 ±3 年窗口内进行 UID 匹配
要求
Python 3.11+
Apple ID(电子邮件标识,非电话号码)
iCloud 应用专用密码(可撤销) — 一个密码同时适用于日历和邮件
对
https://caldav.icloud.com,imap.mail.me.com,smtp.mail.me.com的网络访问权限
环境
在 server.py 旁边创建 .env 文件(自动加载):
APPLE_ID=you@example.com # Use your Apple ID email
ICLOUD_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx # App-specific password (works for both calendar and mail)
CALDAV_URL=https://caldav.icloud.com # optional, default shown
HOST=127.0.0.1 # optional
PORT=8000 # optional
TZID=America/New_York # default TZ for new/edited events
# Deep Research: read-only calendar profile (optional)
DR_PROFILE=0 # Set to 1 to enable DR mode (default 0)
SCAN_DAYS=1095 # Time window (days) scanned by DR search/fetch (default ~3 years)
# Mail (IMAP / SMTP) — optional, disabled by default
MAIL_ENABLED=1 # Set to 1 to enable mail tools
IMAP_HOST=imap.mail.me.com # optional, default shown
IMAP_PORT=993 # optional, default shown
SMTP_HOST=smtp.mail.me.com # optional, default shown
SMTP_PORT=587 # optional, default shown
ICLOUD_TRASH_FOLDER=Deleted Messages # optional, iCloud trash folder name必需:APPLE_ID, ICLOUD_APP_PASSWORD。
快速开始(本地)
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Ensure .env exists (see above), then:
python server.py
# -> Listening on http://127.0.0.1:8000
curl http://127.0.0.1:8000/health # OKMCP 端点: http://127.0.0.1:8000/mcp
工具参考(功能细节)
list_calendars() -> List[Calendar]
返回:
name: str | nullurl: str(其他调用的首选标识符)id: str | null
list_calendars_with_events(start, end, expand_recurring=True) -> List[Calendar]
仅返回在给定时间窗口内至少包含一个事件的日历。
参数
start, end: str— ISO 日期时间;搜索范围为 [start, end)expand_recurring: bool— 将重复系列视为具体实例
每个返回的日历具有与 list_calendars() 相同的结构。
list_events(calendar_name_or_url, start, end, expand_recurring=True) -> List[Event]
参数
calendar_name_or_url: str— 显示名称或完整的 CalDAV URLstart, end: str— ISO 日期时间;搜索范围为 [start, end)expand_recurring: bool— 包含重复系列的具体实例
返回每个事件,包含:
uid: strsummary: strstart: str(ISO)end: str | null(ISO)raw: str(原始 ICS 文本)
create_event(calendar_name_or_url, summary, start, end, tzid?, description?, location?, recurrence?) -> str
创建一个最小化的 VEVENT。
tzid如果省略,默认为TZID环境变量;普通日期时间被假定在该时区并存储为 UTC。description是可选的;省略或传递null以跳过它。location是可选的;省略或传递null以跳过它。recurrence(可选)描述事件应如何重复,例如:{ "frequency": "weekly", // daily | weekly | monthly | yearly | custom "interval": 1, // optional, default 1 "by_weekday": ["MO", "WE"], // optional; for weekly/custom "by_monthday": [1, 15], // optional; for monthly/custom "end": { // optional end condition "type": "on_date", // or "after_occurrences" "date": "2025-12-31" // when type == "on_date" // or: "count": 10 // when type == "after_occurrences" } // for custom frequency you can pass a raw RRULE: // "frequency": "custom", // "rrule": "FREQ=MONTHLY;BYDAY=MO,TU;BYSETPOS=1" }返回生成的
uid(随机十六进制 +@claude-mcp)。
update_event(calendar_name_or_url, uid, summary?, start?, end?, tzid?, description?, location?, recurrence?, clear_recurrence=False) -> bool
更新由 uid 标识的整个事件(对于重复事件,这会更新系列 VEVENT,而不是单个实例)。
保留原始组件中任何省略的字段。
location:如果省略(
null/ 未提供),则保留现有位置。如果作为非空字符串提供,则更新事件的位置。
如果作为空字符串提供,则清除事件的位置。
recurrence:如果提供,则使用与
create_event中相同的格式替换任何现有的 RRULE。
clear_recurrence:如果为
True,则删除任何 RRULE 并将事件转换回单个非重复实例。如果为
True且同时提供了recurrence,则clear_recurrence优先(无重复)。
成功时返回
True,如果未在 ±3 年窗口内找到uid,则返回False。
delete_event(calendar_name_or_url, uid) -> bool
删除 ±3 年窗口内第一个匹配的 uid。
如果已删除,返回
True;如果未找到,返回False。
日期/时间说明
接受普通或
Z/偏移日期时间(YYYY-MM-DDTHH:MM:SS,可选Z或-04:00等)新建/编辑的事件使用提供的
tzid或TZID环境变量发出DTSTART;TZID=...和DTEND;TZID=...更新时会尝试在存在时重用原始 TZID
当提供
location且非空时会发出LOCATION;在更新事件时传递空字符串会删除现有位置。
邮件工具参考
通过 MAIL_ENABLED=1 启用。使用与日历相同的 APPLE_ID 和 ICLOUD_APP_PASSWORD。无额外依赖 — 纯 Python 标准库 (imaplib, smtplib)。
list_mailboxes() -> List[{name}]
返回所有 IMAP 文件夹(收件箱、已发送、草稿、垃圾邮件、已删除邮件等)。
list_messages(mailbox="INBOX", limit=20, unread_only=False) -> List[Message]
返回最多 limit 条邮件的最新标题。每项包含:
uid: str,subject: str,from: str,date: str,read: bool
get_message(uid, mailbox="INBOX") -> Message
获取完整邮件,包括解码后的正文(首选 text/plain,回退为去除 HTML 标签)。返回:
uid, subject, from, to, cc, date, body, read
search_messages(query, mailbox="INBOX", limit=20) -> List[Message]
IMAP TEXT 搜索 — 匹配主题和正文。返回与 list_messages 相同的标题字段。
send_message(to, subject, body, cc=None, bcc=None) -> bool
通过 SMTP 发送(端口 587 上的 STARTTLS)。to 和 cc 可以用逗号分隔。成功时返回 True。
delete_message(uid, mailbox="INBOX") -> bool
复制到废纸篓(默认为 Deleted Messages,可通过 ICLOUD_TRASH_FOLDER 覆盖)然后清除。成功时返回 True。
mark_message(uid, mailbox="INBOX", read=True) -> bool
设置或清除 \Seen 标志。成功时返回 True。
深度研究只读模式
设置 DR_PROFILE=1 以运行用于深度研究的只读工具集。这仅公开:
search(query) -> [{ id, title, snippet }]
fetch(ids) -> [{ id, mimeType: 'text/calendar', content }]
示例:
DR_PROFILE=1 HOST=127.0.0.1 PORT=8000 python server.py说明:
在此模式下,写入工具(list_events/create_event/update_event/delete_event)被禁用。
SCAN_DAYS 控制“当前”周围的搜索窗口(默认:1095 天 ≈ 3 年)。
请保持此服务私有或添加身份验证。
示例(编程客户端)
import asyncio, json
from fastmcp import Client
MCP_URL = "http://127.0.0.1:8000/mcp"
CAL_URL = "<paste one of your calendar URLs>"
def unwrap(res):
sc = getattr(res, "structured_content", None)
if isinstance(sc, dict) and "result" in sc:
return sc["result"]
return json.loads(res.content[0].text)
async def main():
async with Client(MCP_URL) as c:
cals = unwrap(await c.call_tool("list_calendars", {"confirm": True}))
print("Calendars:", cals[:2])
evs = unwrap(await c.call_tool("list_events", {
"calendar_name_or_url": CAL_URL,
"start": "2025-09-01T00:00:00",
"end": "2025-10-01T00:00:00",
"expand_recurring": True
}))
print("Events:", len(evs))
uid = unwrap(await c.call_tool("create_event", {
"calendar_name_or_url": CAL_URL,
"summary":"Demo",
"start":"2025-09-29T15:00:00",
"end":"2025-09-29T15:30:00",
"tzid":"America/New_York",
"location": "Bobst Library"
}))
print("Created:", uid)
asyncio.run(main())部署 / 公共 HTTPS
要将其与 Claude 自定义连接器一起使用,您需要一个转发到本地服务器的公共 HTTPS 端点。
请参阅 DEPLOY.md 以了解:
Cloudflare Tunnel(稳定的主机名,免费)
ngrok(快速测试)
VPS + Caddy/Nginx(永久)
安全性:添加身份验证(Cloudflare Access、Basic Auth 代理、IP 白名单)。不要在未经身份验证的情况下公开此服务;它拥有实时日历写入权限。
您需要一个转发到本地 http://127.0.0.1:8000 的公共 HTTPS URL。
故障排除
症状 | 可能的原因 / 修复 |
| Apple ID 或应用专用密码错误;确保 |
空的事件结果 | 日历 URL 或时间窗口错误;请记住 |
更新/删除无操作 | UID 不在 ±3 年扫描窗口内,或者与您查询的日历不同。 |
时区偏移 | 显式传递 |
安全性
使用应用专用密码并根据需要轮换
保持此服务器私有(隧道 ACL、IP 白名单、身份验证代理)
此项目重写最小化的 VEVENT;高级字段(与会者、提醒、重复异常)在更新时不会被保留
许可证
MIT 许可证。
祝您日程安排愉快,希望这对您有帮助!
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/alexey-max-fedorov/icloud-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server