ICloud CalDAV Connector
БесплатноНе проверенAn MCP server that enables interaction with iCloud Calendars via the CalDAV protocol using an app-specific password. It allows users to list, create, update, an
Описание
An MCP server that enables interaction with iCloud Calendars via the CalDAV protocol using an app-specific password. It allows users to list, create, update, and delete calendar events through MCP-aware clients like ChatGPT.
README
An HTTP Model Context Protocol (MCP) server exposing iCloud services to MCP-aware clients (e.g., claude custom connectors, IDEs) using an iCloud app-specific password.
Supported: iCloud Calendar (CalDAV) + iCloud Mail (IMAP/SMTP).
Unofficial. Keep this service private; it forwards your iCloud app-specific password to Apple’s servers.
Why did I build this?
I built this to use in Claude Custom Connector, so I can change my iCloud Calendar compared to changing it manually. Came up with this idea on a Friday night before a TOP Pset was due, and this turned out to be a fun 1-day project.
Features
- HTTP MCP server (
/mcp) +GET /health - Calendar tools (default write-capable profile):
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)
- Calendar tools (Deep Research read-only profile,
DR_PROFILE=1):search(query)→ basic text search over SUMMARY/DESCRIPTION in a time windowfetch(ids)→ fetch rawtext/calendarICS blobs for search results
- Mail tools (opt-in,
MAIL_ENABLED=1):list_mailboxes()— list all folderslist_messages(mailbox, limit, unread_only)— list messages with headersget_message(uid, mailbox)— fetch full message with bodysearch_messages(query, mailbox, limit)— IMAP TEXT searchsend_message(to, subject, body, cc?, bcc?)— send via SMTPdelete_message(uid, mailbox)— move to Trashmark_message(uid, mailbox, read)— mark read/unread
- ISO datetime input (
YYYY-MM-DDTHH:MM:SS, with optionalZor timezone offset) - Minimal ICS generation (summary/description escaping), UID matching across a ±3-year window
Requirements
- Python 3.11+
- Apple ID (email identity, not phone number)
- iCloud app-specific password (revocable) — one password works for both calendar and mail
- Network access to
https://caldav.icloud.com,imap.mail.me.com,smtp.mail.me.com
Environment
Create a .env next to server.py (auto-loaded):
[email protected] # 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
Required: APPLE_ID, ICLOUD_APP_PASSWORD.
Quick Start (local)
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 endpoint: http://127.0.0.1:8000/mcp
Tool Reference (functional details)
list_calendars() -> List[Calendar]
Returns:
name: str | nullurl: str(preferred identifier for other calls)id: str | null
list_calendars_with_events(start, end, expand_recurring=True) -> List[Calendar]
Returns only the calendars that contain at least one event in the given time window.
Args
start, end: str— ISO datetimes; search is [start, end)expand_recurring: bool— treat recurring series as concrete instances
Each returned calendar has the same shape as list_calendars().
list_events(calendar_name_or_url, start, end, expand_recurring=True) -> List[Event]
Args
calendar_name_or_url: str— display name or full CalDAV URLstart, end: str— ISO datetimes; search is [start, end)expand_recurring: bool— include concrete instances of recurring series
Returns each event with:
uid: strsummary: strstart: str(ISO)end: str | null(ISO)raw: str(original ICS text)
create_event(calendar_name_or_url, summary, start, end, tzid?, description?, location?, recurrence?) -> str
Creates a minimal VEVENT.
tziddefaults toTZIDenv if omitted; naive datetimes are assumed in that zone and stored as UTC.descriptionis optional; omit or passnullto skip it.locationis optional; omit or passnullto skip it.recurrence(optional) describes how the event should repeat, for example:{ "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" }Returns the generated
uid(random hex +@claude-mcp).
update_event(calendar_name_or_url, uid, summary?, start?, end?, tzid?, description?, location?, recurrence?, clear_recurrence=False) -> bool
Updates the whole event identified by uid (for recurring events this updates the series VEVENT, not a single instance).
- Preserves any omitted fields from the original component.
location:- If omitted (
null/ not provided), keeps the existing location. - If provided as a non-empty string, updates the event’s location.
- If provided as an empty string, clears the event’s location.
- If omitted (
recurrence:- If provided, replaces any existing RRULE using the same shape as in
create_event.
- If provided, replaces any existing RRULE using the same shape as in
clear_recurrence:- If
True, removes any RRULE and converts the event back to a single non-recurring instance. - If
Trueandrecurrenceis also provided,clear_recurrencewins (no recurrence).
- If
- Returns
Trueon success,Falseifuidnot found in ±3-year window.
delete_event(calendar_name_or_url, uid) -> bool
Deletes the first matching uid in a ±3-year window.
- Returns
Trueif deleted,Falseif not found.
Date/Time Notes
- Accepts naive or
Z/offset datetimes (YYYY-MM-DDTHH:MM:SS, optionallyZor-04:00etc.) - New/edited events emit
DTSTART;TZID=...andDTEND;TZID=...using providedtzidorTZIDenv - Updates attempt to reuse the original TZID when present
LOCATIONis emitted whenlocationis provided and non-empty; passing an empty string when updating an event removes the existing location.
Mail Tool Reference
Enable with MAIL_ENABLED=1. Uses the same APPLE_ID and ICLOUD_APP_PASSWORD as the calendar. No extra dependencies — pure Python stdlib (imaplib, smtplib).
list_mailboxes() -> List[{name}]
Returns all IMAP folders (INBOX, Sent, Drafts, Junk, Deleted Messages, etc.).
list_messages(mailbox="INBOX", limit=20, unread_only=False) -> List[Message]
Returns newest-first headers for up to limit messages. Each item:
uid: str,subject: str,from: str,date: str,read: bool
get_message(uid, mailbox="INBOX") -> Message
Fetches the full message including decoded body (text/plain preferred, HTML stripped as fallback). Returns:
uid, subject, from, to, cc, date, body, read
search_messages(query, mailbox="INBOX", limit=20) -> List[Message]
IMAP TEXT search — matches subject and body. Returns same header fields as list_messages.
send_message(to, subject, body, cc=None, bcc=None) -> bool
Sends via SMTP (STARTTLS on port 587). to and cc may be comma-separated. Returns True on success.
delete_message(uid, mailbox="INBOX") -> bool
Copies to Trash (Deleted Messages by default, override with ICLOUD_TRASH_FOLDER) then expunges. Returns True on success.
mark_message(uid, mailbox="INBOX", read=True) -> bool
Sets or clears the \Seen flag. Returns True on success.
Deep Research read-only mode
Set DR_PROFILE=1 to run a read-only tool set for Deep Research. This exposes only:
- search(query) -> [{ id, title, snippet }]
- fetch(ids) -> [{ id, mimeType: 'text/calendar', content }]
Example:
DR_PROFILE=1 HOST=127.0.0.1 PORT=8000 python server.py
Notes:
- Write tools (list_events/create_event/update_event/delete_event) are disabled in this mode.
- SCAN_DAYS controls the search window around “now” (default: 1095 days ≈ 3 years).
- Keep this service private or add auth
Example (programmatic client)
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())
Deployment / Public HTTPS
To use this with claude Custom Connectors you need a public HTTPS endpoint that forwards to your local server.
See DEPLOY.md for:
- Cloudflare Tunnel (stable hostname, free)
- ngrok (quick test)
- VPS + Caddy/Nginx (permanent)
Security: add auth (Cloudflare Access, Basic Auth proxy, IP allowlist). Do NOT expose this unauthenticated; it holds live calendar write access.
You need a public HTTPS URL that forwards to your local http://127.0.0.1:8000.
Troubleshooting
| Symptom | Likely Cause / Fix |
|---|---|
401 Unauthorized |
Wrong Apple ID or app-specific password; ensure .env uses email, not phone. |
| Empty event results | Wrong calendar URL or time window; remember end is exclusive. |
| Update/Delete no-ops | UID not in ±3-year scan window or different calendar than you’re querying. |
| Timezone drift | Pass tzid explicitly (e.g., America/New_York) or use UTC ...Z. |
Security
- Use app-specific passwords and rotate as needed
- Keep this server private (tunnel ACLs, IP allowlists, auth proxy)
- This project rewrites minimal VEVENTs; advanced fields (attendees, alarms, recurrence exceptions) are not preserved on update
License
MIT License.
Happy scheduling, I hope this helps!
Установка ICloud CalDAV Connector
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/alexey-max-fedorov/icloud-mcpFAQ
ICloud CalDAV Connector MCP бесплатный?
Да, ICloud CalDAV Connector MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для ICloud CalDAV Connector?
Нет, ICloud CalDAV Connector работает без API-ключей и переменных окружения.
ICloud CalDAV Connector — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить ICloud CalDAV Connector в Claude Desktop, Claude Code или Cursor?
Открой ICloud CalDAV Connector на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
Notion
Read and write pages in your workspace
автор: NotionLinear
Issues, cycles, triage — from Claude
автор: LinearGoogle Drive
Search and read your Drive files
автор: Googlemindsdb/mindsdb
Connect and unify data across various platforms and databases with [MindsDB as a single MCP server](https://docs.mindsdb.com/mcp/overview).
автор: mindsdbCompare ICloud CalDAV Connector with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории productivity
