iCloud MCP 连接器

一个HTTP Model Context Protocol (MCP)服务器,通过iCloud应用特定密码向MCP客户端(如Claude自定义连接器、IDE)暴露iCloud服务(日历和邮件)。

  • by alexey-fedorov-com
  • Python
  • 本地部署
  • 2 星标
  • 更新于
在 GitHub 查看 alexey-max-fedorov/icloud-mcp

使用说明

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/calendar ICS 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 | null
  • url: 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 URL
  • start, end: str --ISO日期时间;搜索是[开始, 结束)
  • expand_recurring: bool --包括重复序列的具体实例

退货 每个事件都有:

  • uid: str
  • summary: str
  • start: str (ISO)
  • end: str | null (ISO)
  • raw: str (ICS原始文本)

create_event(calendar_name_or_url, summary, start, end, tzid?, description?, location?, recurrence?) -> str

创建最小值 通风口.

  • tzid 默认为 TZID env如果省略;在该区域中假定初始日期时间,并将其存储为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.
  • 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许可证。


祝你日程安排愉快,希望这对你有所帮助!