icloud-mcp
Unified Apple/iCloud MCP server for Calendar, Contacts, and Mail using CalDAV, CardDAV, IMAP, and SMTP protocols with app-specific passwords.
README
icloud-mcp
Unified Apple/iCloud MCP server for Calendar, Contacts, and Mail: one static Go binary, Model Context Protocol JSON-RPC on stdio.
Remote protocols only (CalDAV, CardDAV, IMAP, SMTP with app-specific
passwords). Not macOS EventKit, AppleScript, browser automation, or a private
Apple API. Runs headless on Linux and macOS; pure Go also builds for Windows
(CI smoke-builds windows/amd64; GitHub Release archives ship linux/amd64,
linux/arm64, and darwin/arm64). Suitable for agents and orchestration, not only
a desktop chat app.
Host-agnostic. Any MCP client that can spawn a child with an environment and
wire stdin/stdout works: personal agents, Hermes, OpenClaw, IDE bridges,
runners, or other stdio hosts. No preferred model vendor, chat product, or
reseller. Configuration is the process environment only; the binary does not
parse host-specific config files or .env.
| Domain | Protocol | Default |
|---|---|---|
| Calendar | CalDAV HTTPS | Always on (read + write unless global read-only) |
| Contacts | CardDAV HTTPS | Off until ICLOUD_MCP_ENABLE_CONTACTS |
| Mail read | IMAP TLS | Off until ICLOUD_MCP_ENABLE_MAIL |
| Mail mutation | IMAP | Off until Mail + ICLOUD_MCP_ENABLE_MAIL_WRITE |
| Mail send | SMTP STARTTLS | Off until Mail + ICLOUD_MCP_ENABLE_MAIL_SEND + recipient policy |
Reminders, Notes, Photos, Drive, Messages, and similar apps are out of scope until Apple documents a suitable remote third-party connector. Details: Supported scope.
Quick start
go install github.com/ThomasCrouzet/icloud-mcp/cmd/icloud-mcp@latest
# or: make build
# or: make release VERSION=v0.4.0
# or: make release-all VERSION=v0.4.0
- Create an app-specific password (never the main Apple Account password).
- Export a minimal environment (recommended first deploy: Calendar read-only):
export ICLOUD_EMAIL='you@icloud.com'
export ICLOUD_PASSWORD='your-app-specific-password'
export ICLOUD_MCP_READ_ONLY=true
export ICLOUD_MCP_DEFAULT_TZ=Europe/Paris # owner IANA zone; default UTC
- Register command = absolute path to
icloud-mcpand this env in your MCP host (YAML, JSON, TOML, UI, or orchestrator; format is host-specific). - Stdio = JSON-RPC; stderr = logs and mutation audit. Reload the host after env changes.
With that config the process exposes 7 tools (Calendar reads + local
helpers + icloud_capabilities). No Contacts or Mail client is constructed.
Optional domains (still host-agnostic export form):
# Contacts reads (writes also need ICLOUD_MCP_READ_ONLY=false)
export ICLOUD_MCP_ENABLE_CONTACTS=true
# Mail reads: IMAP identity may differ from ICLOUD_EMAIL (e.g. name@icloud.com)
export ICLOUD_MCP_ENABLE_MAIL=true
export ICLOUD_MAIL_ADDRESS='mailbox@icloud.com'
# export ICLOUD_MAIL_PASSWORD='...' # optional; else copy of ICLOUD_PASSWORD
# Mail mutation (flags / move / trash); independent of send
export ICLOUD_MCP_ENABLE_MAIL_WRITE=true
export ICLOUD_MCP_READ_ONLY=false
# Mail send: requires exact recipient allowlist even under global read-only
export ICLOUD_MCP_ENABLE_MAIL_SEND=true
export ICLOUD_MCP_SMTP_ALLOWED_RECIPIENTS='alice@example.com,bob@example.net'
Boot-only file:// secrets (regular files only, at most 4 KiB, mode 0600 or
stricter; never re-read after start):
export ICLOUD_EMAIL='file:///run/secrets/icloud-email'
export ICLOUD_PASSWORD='file:///run/secrets/icloud-password'
export ICLOUD_MAIL_ADDRESS='file:///run/secrets/icloud-mail-address'
export ICLOUD_MAIL_PASSWORD='file:///run/secrets/icloud-mail-password'
See .env.example for the full 12-variable contract.
MCP tools
Maximum 23 tools. Disabled tools are absent from tools/list; disabled
domain clients are not constructed.
| Count | When |
|---|---|
| 10 | Default: Calendar read+write + icloud_capabilities |
| 7 | Global read-only, optional domains off (recommended first run) |
| 23 | Contacts + Mail read + mutation + send, read-only off |
ICLOUD_MCP_READ_ONLY=true removes every Calendar/Contacts write, every Mail
mutation, and Mail send. It does not enable a disabled read domain.
| Group | Tools |
|---|---|
| Global | icloud_capabilities |
| Calendar read | list_calendars, search_events, get_event, find_free_slots, validate_event, calendar_capabilities |
| Calendar write | create_event, update_event, delete_event |
| Contacts read | list_address_books, search_contacts, get_contact |
| Contacts write | create_contact, update_contact, delete_contact |
| Mail read | list_mailboxes, search_messages, get_message |
| Mail mutation | set_message_flags, move_message, trash_message |
| Mail send | send_message |
Highlights: occurrence-aware Calendar update/delete with strong If-Match;
Contacts opaque book IDs and vCard 3.0 writes; Mail identity
(mailbox, UIDVALIDITY, UID), PEEK reads, SMTP exact-recipient policy.
set_message_flags fails closed with protocol_error when CONDSTORE is
advertised and tagged MODIFIED cannot be observed (go-imap beta.8). Full
behavior notes: docs/caldav-compatibility.md,
docs/carddav-compatibility.md,
docs/mail-compatibility.md.
Idempotency: create_event / create_contact use server-side UID keys
(client_uid or alias idempotency_key): a repeat create conflicts if the UID
already exists (never silent overwrite). update_event / update_contact
optional idempotency_key is process-local only (in-memory cache, 15
minute TTL, cleared on process restart). Same key + same params returns the
cached success; same key + different params is conflict. Prefer combining
update keys with a strong etag. See docs/error-codes.md.
Configuration
Exactly 12 product environment variables:
| Variable | Default | Contract |
|---|---|---|
ICLOUD_EMAIL |
none | Required Calendar/Contacts identity. file:// supported (regular file, <=4 KiB, mode 0600+). |
ICLOUD_PASSWORD |
none | Required app-specific password; Mail fallback. file:// as above. |
ICLOUD_MCP_READ_ONLY |
false |
Global mutation kill switch. |
ICLOUD_MCP_LOG_LEVEL |
info |
Stderr level; accepted forms are documented below. |
ICLOUD_MCP_DEFAULT_TZ |
UTC |
IANA zone for offset-less Calendar inputs and recurring-write fallback. |
ICLOUD_MCP_ENABLE_CONTACTS |
false |
Contacts tools; writes only if not read-only. |
ICLOUD_MCP_ENABLE_MAIL |
false |
Mail reads; requires Mail address/password. |
ICLOUD_MAIL_ADDRESS |
none | Full IMAP/SMTP address when Mail is on. file:// as above. |
ICLOUD_MAIL_PASSWORD |
ICLOUD_PASSWORD |
Optional dedicated Mail app password. file:// as above. |
ICLOUD_MCP_ENABLE_MAIL_WRITE |
false |
Three IMAP mutation tools. |
ICLOUD_MCP_ENABLE_MAIL_SEND |
false |
send_message (independent of Mail write). |
ICLOUD_MCP_SMTP_ALLOWED_RECIPIENTS |
none | Required if send requested: exact addresses, or literal * (boot warning; prefer exact). |
Booleans accept only unset, 0, false, 1, or true. Invalid values fail at
boot. Config is validated before any network access: Mail write/send without
Mail, Mail without address/password, or send without recipient policy are boot
errors (including under read-only for the send policy). Global read-only can
coexist with write/send flags but suppresses their registration.
Log levels are trimmed and case-insensitive: debug/-4, info,
warn/warning/2, and error/4. Unset or unrecognized values use info.
Flags: -version; optional -health 127.0.0.1:port (loopback-only /healthz
and /status JSON with domains and rate limits); optional
-audit-format=json|text (default json mutation audit on stderr).
Dates
- Calendar input
start/end: RFC3339 with offset, or wall clock without offset inICLOUD_MCP_DEFAULT_TZ. Prefer no-offset for the user's local time. Recurring or explicit-timezone creates write TZID + VTIMEZONE; non-recurring timed defaults to UTCZon the wire. All-day usesVALUE=DATE. - Calendar output: timed events always use RFC3339 with an explicit numeric
offset in
ICLOUD_MCP_DEFAULT_TZ(never bareZ). All-day dates areYYYY-MM-DD. Seecalendar_capabilities.outputFormat. - Contacts birthdays: write
YYYY-MM-DDonly. - Mail search:
sinceinclusive,beforeexclusive (YYYY-MM-DD).
Agent error codes and retry policy: docs/error-codes.md. Host wiring examples: docs/agent-hosts.md. Product roadmap: ROADMAP.md.
Security (summary)
Untrusted remote text can influence an LLM on the host; labels are not a boundary. A compromised model can call every registered tool. Same model for every host and vendor.
- Egress fixed: Calendar
caldav.icloud.com/p[0-9]{1,3}-caldav.icloud.com:443; Contacts matching contacts hosts; IMAPimap.mail.me.com:993; SMTPsmtp.mail.me.com:587with mandatory STARTTLS. No configurable destinations, no proxy env for DAV, TLS 1.2+ verified. - Isolation: separate credentials, transports/dialers, limiters, semaphores, and protocol stacks per domain; no union authenticated HTTP client.
- Secrets: redacted (including Basic and SASL PLAIN forms); boot-only
file://reads require mode 0600 or stricter; noos/exec, telemetry, or disk write after boot. - Audit: mutations log
domain,resourceType, process-local HMACresourceTokenonly (never raw paths, UIDs, mailboxes, recipients). - Residual risk: one process holds every enabled domain's credentials; feature flags do not remove compiled code. Prefer read-only, least domains, dedicated Mail password, or separate processes when stronger isolation is required.
Full policy: SECURITY.md, docs/security.md. Architecture: docs/architecture.md.
Limits (summary)
| Tool deadline | 25s (DAV HTTP 30s) |
| Stdio / MCP result | 1 MiB frame; 256 KiB result; reflected protocol errors capped |
| Calendar | 366-day search; 400 returned / 2,500 per calendar / 10,000 multi-calendar materialization; 2,000 expansions / 100k steps per series / 250k steps per search; 60 read / 20 write per minute; concurrency 4 / 2 |
| Contacts | 100 books; 100 summaries; 2000 cards scanned; 60/20 per minute; concurrency 4 |
| 60 read / 20 mutation / 20 send per minute; semaphores 2 / 1 / 1; no mutation/send retry | |
| Writes | No automatic replay of Calendar PUT/DELETE, Contacts writes, IMAP mutations, or SMTP; ambiguous outcomes use outcome_unknown |
Lifecycle: eager Calendar discovery at boot; lazy Contacts discovery; fresh IMAP/SMTP session per call. Rates, XML/iCal/vCard/MIME budgets, and retry rules: docs/testing.md, docs/architecture.md.
Supported scope
| Data | Connector | Support |
|---|---|---|
| Calendar | CalDAV | Always |
| Contacts | CardDAV | Optional |
| Mail read / mutation / send | IMAP + SMTP | Optional, independently gated |
| Modern Reminders, Notes, Photos, Drive, Find My, Keychain, Messages, Home | No suitable official remote connector for this model | Excluded |
Modern Reminders are not treated as generic CalDAV VTODO. Apple's third-party documentation for this class of access covers Mail, Calendar, and Contacts.
Multi-account: one process holds one iCloud identity. Spawn separate
icloud-mcp processes (distinct env, optional distinct -health ports) and let
the MCP host multiplex them. See docs/agent-hosts.md.
Dependencies
Go 1.25.12 or newer, one module, exactly 10 direct dependencies. Adding another requires a written justification here.
| Dependency | Exact version | Justification |
|---|---|---|
github.com/emersion/go-webdav |
v0.7.0 |
CalDAV primitives; discovery and conditional ops stay hand-rolled |
github.com/emersion/go-ical |
v0.0.0-20250609112844-439c63cef608 |
iCalendar parse/encode |
github.com/mark3labs/mcp-go |
v0.57.0 |
MCP stdio, schemas, JSON-RPC |
github.com/teambition/rrule-go |
v1.8.2 |
Bounded recurrence with timezone preservation |
golang.org/x/time |
v0.15.0 |
Per-domain rate limiters |
github.com/emersion/go-vcard |
v0.0.0-20260618161152-d854b7e0e2d3 |
vCard 3.0/4.0 read, 3.0 write |
github.com/emersion/go-imap/v2 |
v2.0.0-beta.8 |
IMAP behind internal/mail/imapadapter |
github.com/emersion/go-message |
v0.18.2 |
MIME / plain-text bounds |
github.com/emersion/go-smtp |
v0.24.0 |
SMTP + STARTTLS |
github.com/emersion/go-sasl |
v0.0.0-20241020182733-b788ff22d5a6 |
SASL PLAIN after STARTTLS |
Build and test
make build # local host binary, VERSION defaults to dev
make test # go test ./... -race -cover
make lint # go vet + pinned golangci-lint
make release VERSION=v0.4.0 # packaged linux/arm64, digest-pinned Go 1.25.12 image
make release-all VERSION=v0.4.0 # packaged linux/amd64, linux/arm64, darwin/arm64 (host Go)
make install # host-compatible build to INSTALL_DIR (default ~/.local/bin)
Release targets reject an unset or dev version. Archives contain the binary,
LICENSE, and THIRD_PARTY_NOTICES.md; dist/ also receives a SHA-256
checksum file. GitHub tag releases run make release-all only after CI and
gitleaks succeed on that tag, with Go pinned to 1.25.12 (check-latest
disabled). Local make release remains the digest-pinned container path for
linux/arm64. Cosign keyless signatures are attached to release blobs. -version
prefers the release ldflags value and falls back to Go module build information,
so go install ...@version reports that module version.
CI: race tests, coverage floors (78% aggregate + package floors including
cmd/icloud-mcp and internal/health), fuzz smoke, govulncheck, multi-arch
build (plus windows/amd64 smoke), egress/security AST guards, gitleaks, 20 MiB
binary budget, public-text policy on tree and new commits. Live iCloud tests use
the integration build tag, are opt-in, and never run in CI. See
docs/testing.md.
Attribution
Calendar tool shape and several patterns were inspired by
github.com/roygabriel/mcp-icloud-calendar
(MIT, copyright 2026 Gabe). Code was rewritten, not copied. This server adds
hard per-domain egress, redaction, bounded parsers, conditional mutation,
Contacts, Mail, and no telemetry.
Contributing
See CONTRIBUTING.md. Run gofmt, make test, and make lint
before opening a pull request.
License
MIT. See LICENSE and third-party notices.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.