About Icloud MCP
Icloud MCP is an MCP server published by mike-tih in the Blockchain category: iCloud MCP working with app specific tokens, providing access to Contacts, Calendar and Mail, communicating with CalDav, CardDav and IMAP.
The repository has 13 stars and 25 forks, with the last commit 28 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/mike-tih/icloud-mcpThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Icloud MCP
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository 2 days ago. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- !Dependencies pinnedNo lockfile. Installs can pull different code over time.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
README
iCloud MCP Server
Model Context Protocol server for iCloud. Gives Claude (Desktop, Code, claude.ai connectors) and any other MCP client full access to an iCloud account:
- Calendar (CalDAV): list/search/create/update/delete events, recurring events (RRULE), alerts (VALARM), IANA timezones, all-day events, iTIP invitations and cancellations by email.
- Contacts (CardDAV): list/search/get/create/update/delete, including notes; labels (home/work/custom) survive updates.
- Mail (IMAP/SMTP): folders, listing, server-side search, full messages with attachments, attachment download, sending (HTML, attachments, threaded replies), drafts, move/delete/read flags.
Runs in two modes with the same code:
| Mode | Transport | Typical use | Credentials |
|---|---|---|---|
| Local ("offline") | stdio | Claude Desktop / Claude Code launch it as a subprocess on your machine | .env or environment variables |
| Server | Streamable HTTP, stateless | Docker / Cloud Run / any host, many users | Per-request headers or HTTP Basic auth, env fallback |
Tools
| Tool | Description |
|---|---|
calendar_list_calendars |
Calendars with IDs; Reminders lists are flagged read_only |
calendar_list_events |
Events in a date range, recurring series expanded per occurrence, sorted by start |
calendar_search_events |
Text search over summary/description/location |
calendar_create_event |
Create an event: timezone, all-day, rrule, reminders (minutes before start or "09:00" time of day), attendees (invitations sent by email) |
calendar_update_event |
Partial update; change timezone/recurrence/alerts; re-sends invitations when attendees change |
calendar_delete_event |
Delete and email cancellations to attendees |
contacts_list / contacts_search / contacts_get |
Read contacts (name, phones, emails, addresses, organization, title, notes) |
contacts_create / contacts_update / contacts_delete |
Write contacts |
email_list_folders |
Folders with IMAP flags |
email_list_messages |
Newest messages of a folder with readable body_text, unread, has_attachments |
email_search |
Server-side IMAP search: query, sender, recipient, subject, body, since, before, unread_only (AND) |
email_get_message / email_get_messages |
Full message(s): headers, body_text, optional raw body_html, attachment list |
email_get_attachment |
Download an attachment: save to a local directory (stdio) or return it inline |
email_send |
Send mail: multiple recipients, CC/BCC, HTML with auto plain-text alternative, local file attachments, threaded reply (reply_to_message_id); copy stored in Sent |
email_save_draft |
Same inputs as email_send but stores the message in Drafts for the user to review and send from any mail client |
email_move / email_delete |
Move between folders; delete to Trash (Deleted Messages) or permanently |
email_mark_read / email_mark_unread |
Toggle \Seen |
Every tool carries MCP annotations (readOnlyHint, destructiveHint) so clients can ask for confirmation before writes. Errors are returned as MCP tool errors with actionable messages (e.g. wrong password vs. missing credentials).
Behaviour worth knowing
- IDs: calendar/contact/event IDs are full URLs, pass them back verbatim. Email IDs are IMAP UIDs and are only valid inside the folder they came from.
- Times: pass
YYYY-MM-DDTHH:MM:SSplus an IANAtimezone(default:DEFAULT_TIMEZONE, which falls back to the machine's zone, then UTC) orYYYY-MM-DDfor all-day events. Returned times are ISO 8601 with offset. - Recurring events:
calendar_list_eventsreturns one entry per occurrence; update/delete apply to the whole series. - Email bodies are converted from HTML to readable text and truncated to
EMAIL_BODY_MAX_CHARS(20 000) so newsletters do not flood the model context.full_html=truereturns the raw HTML too. - Attachments on disk (
save_dir,attachment_paths) are enabled by default in stdio mode and disabled in HTTP mode. Override withICLOUD_MCP_LOCAL_FILESand restrict to a folder withICLOUD_MCP_LOCAL_FILES_ROOT. - Rich text that users paste into event notes/locations or contact notes comes back from iCloud as HTML; it is converted to Markdown so links and lists survive (
ICLOUD_HTML_MODE=markdown, default), rendered to plain text (text) or passed through (raw). - Tool groups can be switched off per instance with
ICLOUD_ENABLED_CATEGORIES=calendar,contacts,email(e.g. a read-only mail agent getsemailonly). - Only iCloud hosts are ever contacted with the account's credentials: calendar, event and contact URLs on any other host are rejected, and mail headers are validated against injection.
- Outbound recipients can be restricted with
EMAIL_SEND_ALLOWLIST=@mycompany.com,partner@example.com; it applies toemail_send,email_save_draftand calendar invitations, so a prompt-injected agent cannot mail data to arbitrary addresses.
Requirements
- Python 3.11+ (tested on 3.12 and 3.14) or Docker
- An iCloud account and an app-specific password: https://account.apple.com/account/manage → Sign-In and Security → App-Specific Passwords. iCloud Mail additionally needs an active
@icloud.com/@me.com/@mac.comaddress.
Local mode (Claude Desktop / Claude Code)
1. Install
git clone https://github.com/mike-tih/icloud-mcp.git
cd icloud-mcp
uv venv && uv pip install -e . # or: python3 -m venv .venv && .venv/bin/pip install -e .
cp .env.example .env # add ICLOUD_EMAIL / ICLOUD_APP_SPECIFIC_PASSWORD
Try it:
.venv/bin/icloud-mcp --help
2. Claude Code
claude mcp add icloud -- /absolute/path/to/icloud-mcp/.venv/bin/icloud-mcp
or, without a checkout at all:
claude mcp add icloud -e ICLOUD_EMAIL=you@icloud.com -e ICLOUD_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx \
-- uvx --from git+https://github.com/mike-tih/icloud-mcp icloud-mcp
3. Claude Desktop
Config file: macOS ~/Library/Application Support/Claude/claude_desktop_config.json, Windows %APPDATA%\Claude\claude_desktop_config.json.
{
"mcpServers": {
"icloud": {
"command": "/absolute/path/to/icloud-mcp/.venv/bin/icloud-mcp",
"env": {
"ICLOUD_EMAIL": "you@icloud.com",
"ICLOUD_APP_SPECIFIC_PASSWORD": "xxxx-xxxx-xxxx-xxxx",
"DEFAULT_TIMEZONE": "Europe/Berlin"
}
}
}
}
python /absolute/path/to/icloud-mcp/run.py works as the command too. Restart Claude Desktop completely after editing the file; the server shows up under the tools icon.
Server mode (Streamable HTTP)
icloud-mcp --http # 0.0.0.0:8000/mcp, stateless
icloud-mcp --http --port 9000 --path /icloud --stateful
Or with Docker:
docker compose up -d # reads .env for optional fallback credentials
curl http://localhost:8000/health
The image runs icloud-mcp --http with PORT from the environment, so it works unchanged on Cloud Run, Fly.io, Railway etc. The server is stateless: every request carries its own credentials, no sessions are kept, and instances can be scaled horizontally.
Authentication (per request)
Checked in order:
- Headers
X-Apple-EmailandX-Apple-App-Specific-Password Authorization: Basic base64(email:app-specific-password)- Environment
ICLOUD_EMAIL/ICLOUD_APP_SPECIFIC_PASSWORD(single-user deployments)
Protecting the endpoint
Set MCP_AUTH_TOKEN to a long random secret. Every MCP request must then carry it as Authorization: Bearer <token> or X-MCP-Token: <token> (/health stays open). Without a token the server logs a warning and ignores the environment credentials over HTTP, so an unprotected port can never hand out the operator's account; per-request credentials still work. Set ICLOUD_MCP_ALLOW_ENV_CREDENTIALS=true only if you really want an open endpoint bound to one account (e.g. on a private network). docker-compose.yml publishes the port on 127.0.0.1 only.
MCP_AUTH_TOKEN=$(openssl rand -hex 32) icloud-mcp --http
claude mcp add --transport http icloud https://mcp.example.com/mcp \
-H "Authorization: Bearer <token>" \
-H "X-Apple-Email: you@icloud.com" -H "X-Apple-App-Specific-Password: xxxx-xxxx-xxxx-xxxx"
Example with Claude Code against a remote server:
claude mcp add --transport http icloud https://mcp.example.com/mcp \
-H "X-Apple-Email: you@icloud.com" -H "X-Apple-App-Specific-Password: xxxx-xxxx-xxxx-xxxx"
Always put the server behind HTTPS: app-specific passwords travel in headers.
Configuration
All settings are environment variables (a .env file next to the checkout is loaded). See .env.example for the full list. The important ones:
| Variable | Default | Purpose |
|---|---|---|
ICLOUD_EMAIL, ICLOUD_APP_SPECIFIC_PASSWORD |
– | Fallback credentials |
DEFAULT_TIMEZONE |
machine zone or UTC |
Timezone for naive event times; set explicitly on servers |
EMAIL_BODY_MAX_CHARS |
20000 |
Body truncation |
EMAIL_MAX_ATTACHMENT_BYTES |
20971520 |
Outgoing attachment budget |
EMAIL_SEND_ALLOWLIST |
– | Allowed outbound addresses/domains (send, drafts, invitations) |
ICLOUD_MCP_LOCAL_FILES |
stdio: on, HTTP: off | Allow reading/writing attachments on the server's disk |
ICLOUD_MCP_LOCAL_FILES_ROOT |
– | Confine those files to a directory |
MCP_AUTH_TOKEN |
– | Shared secret required on HTTP requests |
ICLOUD_MCP_ALLOW_ENV_CREDENTIALS |
true with token, else false | Serve the env account over HTTP |
ICLOUD_ENABLED_CATEGORIES |
calendar,contacts,email |
Tool groups to expose |
ICLOUD_HTML_MODE |
markdown |
Rich text in event/contact fields: markdown, text or raw |
MCP_TRANSPORT, PORT, MCP_SERVER_HOST, MCP_SERVER_PATH |
stdio, 8000, 0.0.0.0, /mcp |
HTTP transport |
LOG_LEVEL |
INFO |
Logging (always to stderr, stdout is reserved for stdio) |
Troubleshooting
- "Authentication required": no credentials reached the server. Check the
envblock / headers. - "iCloud rejected the credentials" / HTTP 401: wrong app-specific password, or the Apple ID password was used.
- "IMAP login failed" but calendar works: the Apple ID has no iCloud Mail address (Apple IDs created with a third-party email cannot use iCloud Mail).
- Events land at the wrong time: set
DEFAULT_TIMEZONEor passtimezoneexplicitly. - HTTP 401
unauthorizedfrom the server itself:MCP_AUTH_TOKENis set and the request did not carry it. - "Refusing to use ... URL on untrusted host": an ID was not one returned by the list tools; pass the URL verbatim.
- Server logs: everything goes to stderr; in Claude Desktop see Help → Show Logs.
Development
uv pip install -e ".[dev]"
pytest # unit tests + end-to-end through the MCP protocol with mocked IMAP
ruff check src tests
Project layout:
src/icloud_mcp/
├── server.py # FastMCP app, tool definitions, CLI entrypoint
├── auth.py # per-request credential resolution
├── config.py # environment configuration and logging
├── calendar.py # CalDAV operations, RRULE/timezone handling, iTIP mail
├── contacts.py # CardDAV operations
├── mail.py # IMAP/SMTP operations
├── mail_utils.py # MIME parsing, HTML→text, attachments, special folders, header validation
├── html_render.py # HTML→Markdown for rich text in event/contact fields
└── urls.py # iCloud host allow-list for calendar/contact URLs
License
MIT, see LICENSE.
README fetched from the repository 2 days ago. The original is authoritative.