使用说明
iCloud MCP连接器
HTTP 模型上下文协议(MCP) 使用iCloud向MCP感知客户端(例如,claude自定义连接器、IDE)公开iCloud服务的服务器 应用程序特定密码.
支持: iCloud日历(CalDAV)+iCloud邮件(IMAP/SMTP)。
非官方。保持此服务的私密性;它会将您的iCloud应用程序特定密码转发到Apple的服务器。
我为什么要建造这个?
我构建此功能是为了在Claude Custom Connector中使用,因此与手动更改相比,我可以更改我的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)
- 日历工具 (Deep Research只读配置文件,
DR_PROFILE=1):search(query)→ 在时间窗口中对摘要/描述进行基本文本搜索fetch(ids)→ 取生食text/calendarICS blob用于搜索结果
- 邮件工具 (选择加入,
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+
- 苹果ID(电子邮件 身份,而不是电话号码)
- iCloud 应用程序特定密码 (可撤销)--一个密码适用于日历和邮件
- 网络访问
https://caldav.icloud.com,imap.mail.me.com,smtp.mail.me.com
环境
创建一个 .env 旁边 server.py (自动加载):
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 # OK
MCP端点: 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日期时间;搜索是[开始, 结束)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日期时间;搜索是[开始, 结束)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
创建最小值 通风口.
-
tzid默认为TZIDenv如果省略;在该区域中假定初始日期时间,并将其存储为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:- 如果提供,请使用与中相同的形状替换任何现有的RRULE
create_event.
- 如果提供,请使用与中相同的形状替换任何现有的RRULE
clear_recurrence:- 如果
True,删除任何RRULE并将事件转换回单个非重复实例。 - 如果
True和recurrence还提供了,clear_recurrence获胜(无复发)。
- 如果
- 退货
True关于成功,False如果uid在±3年的窗口期内未发现。
delete_event(calendar_name_or_url, uid) -> bool
删除第一个匹配项 uid 在±3年的窗口期内。
- 退货
True如果删除,False如果没有找到。
日期/时间备注
- 接受天真或
Z/偏移日期时间(YYYY-MM-DDTHH:MM:SS,可选Z或-04:00等等) - 新的/编辑的事件发出
DTSTART;TZID=...和DTEND;TZID=...使用提供tzid或TZID环境 - 更新尝试在存在时重用原始TZID
LOCATION当location已提供且非空;更新事件时传递空字符串会删除现有位置。
邮件工具参考
启用 MAIL_ENABLED=1.使用相同 APPLE_ID 和 ICLOUD_APP_PASSWORD 就像日历一样。没有额外的依赖关系——纯Python stdlib(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]
互联网邮件访问协议 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设置为运行深度研究的只读工具集。这只暴露了:
- 搜索(查询)->[{id,title,snippet}]
- fetch(id)->[{id,mimeType:'文本/日历',内容}]
例子:
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 Custom Connectors一起使用,您需要一个转发到本地服务器的公共HTTPS端点。
看 部署.md 用于:
- Cloudflare Tunnel(稳定主机名,免费)
- ngrok(快速测试)
- VPS+Caddy/Nginx(永久)
安全性:添加身份验证(Cloudflare访问、基本身份验证代理、IP分配列表)。做 非 暴露此未经验证的信息;它拥有实时日历写入权限。
您需要一个转发到本地的公共HTTPS URL http://127.0.0.1:8000.
故障排除
| 症状 | 可能原因/修复 |
|---|---|
401 Unauthorized |
Apple ID或应用程序特定密码错误;确保 .env 用途 电子邮件,不是电话。 |
| 空事件结果 | 日历URL或时间窗口错误;记住 end 是独家的。 |
| 更新/删除无操作 | UID不在±3年扫描窗口中,或与您查询的日历不同。 |
| 时区漂移 | 通行证 tzid 明确地(例如。, America/New_York)或使用UTC ...Z. |
安全
- 使用 应用程序特定密码 并根据需要旋转
- 保持此服务器私有(隧道ACL、IP分配列表、身份验证代理)
- 该项目重写了最小的VEVENT;高级字段(与会者、警报、重复异常)在更新时不会保留
许可证
MIT许可证。
祝你日程安排愉快,希望这对你有所帮助!