{
  "markdown": "# icloud-mcp\n\n[![CI](https://github.com/ThomasCrouzet/icloud-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/ThomasCrouzet/icloud-mcp/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/ThomasCrouzet/icloud-mcp)](https://github.com/ThomasCrouzet/icloud-mcp/releases)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\nThis unified **Apple/iCloud** MCP server supports **Calendar, Contacts, and\nMail**. It is one static Go binary. It uses\n[Model Context Protocol](https://modelcontextprotocol.io) JSON-RPC on **stdio**.\n\nThe server uses only remote protocols: CalDAV, CardDAV, IMAP, and SMTP with\napp-specific passwords. It does not use macOS EventKit, AppleScript, browser\nautomation, or a private Apple API. It runs headless on Linux and macOS. Pure\nGo also builds for Windows.\n\nCI runs a `windows/amd64` smoke build. GitHub Release archives contain\nlinux/amd64, linux/arm64, and darwin/arm64 builds. Agents, orchestrators, and\ndesktop chat applications can use the server.\n\n**Host-agnostic.** Any MCP client can use the server if it starts child processes\nwith an environment and connects stdin and stdout. Compatible clients include\npersonal agents, Hermes, OpenClaw, IDE bridges, runners, and other stdio hosts.\nThe server does not prefer a model vendor, chat product, or reseller. Configure\nthe server only through its process environment. The binary does not parse\nhost-specific config files or `.env`.\n\n| Domain | Protocol | Default |\n|--------|----------|---------|\n| Calendar | CalDAV HTTPS | Always on (read + write unless global read-only) |\n| Contacts | CardDAV HTTPS | Off until `ICLOUD_MCP_ENABLE_CONTACTS` |\n| Mail read | IMAP TLS | Off until `ICLOUD_MCP_ENABLE_MAIL` |\n| Mail mutation | IMAP | Off until Mail + `ICLOUD_MCP_ENABLE_MAIL_WRITE` |\n| Mail send | SMTP STARTTLS | Off until Mail + `ICLOUD_MCP_ENABLE_MAIL_SEND` + recipient policy |\n\nReminders, Notes, Photos, Drive, Messages, and similar apps are **out of scope**.\nThey remain out of scope until Apple documents a suitable remote third-party\nconnector. See [Supported scope](#supported-scope).\n\n## Quick start\n\n```bash\ngo install github.com/ThomasCrouzet/icloud-mcp/cmd/icloud-mcp@latest\n# or: make build\n# or: make release VERSION=v0.4.0\n# or: make release-all VERSION=v0.4.0\n```\n\n1. Create an [app-specific password](https://appleid.apple.com). Never use the\n   main Apple Account password.\n2. Export a minimal environment. For the first deployment, use Calendar\n   **read-only** mode:\n\n```bash\nexport ICLOUD_EMAIL='you@icloud.com'\nexport ICLOUD_PASSWORD='your-app-specific-password'\nexport ICLOUD_MCP_READ_ONLY=true\nexport ICLOUD_MCP_DEFAULT_TZ=Europe/Paris   # owner IANA zone; default UTC\n```\n\n3. Register the absolute `icloud-mcp` path as the **command** in your MCP host.\n   Register the environment as **env**. The host can use YAML, JSON, TOML, a UI,\n   or an orchestrator. The exact format depends on the host.\n4. Stdio carries JSON-RPC. **stderr** carries logs and the mutation audit.\n   Reload the host after environment changes.\n\nThis configuration exposes **7 tools**: Calendar reads, local helpers, and\n`icloud_capabilities`. The process does not construct a Contacts or Mail client.\n\nOptional domains (still host-agnostic `export` form):\n\n```bash\n# Contacts reads (writes also need ICLOUD_MCP_READ_ONLY=false)\nexport ICLOUD_MCP_ENABLE_CONTACTS=true\n\n# Mail reads: IMAP identity may differ from ICLOUD_EMAIL (e.g. name@icloud.com)\nexport ICLOUD_MCP_ENABLE_MAIL=true\nexport ICLOUD_MAIL_ADDRESS='mailbox@icloud.com'\n# export ICLOUD_MAIL_PASSWORD='...'   # optional; else copy of ICLOUD_PASSWORD\n\n# Mail mutation (flags / move / trash); independent of send\nexport ICLOUD_MCP_ENABLE_MAIL_WRITE=true\nexport ICLOUD_MCP_READ_ONLY=false\n\n# Mail send: requires exact recipient allowlist even under global read-only\nexport ICLOUD_MCP_ENABLE_MAIL_SEND=true\nexport ICLOUD_MCP_SMTP_ALLOWED_RECIPIENTS='alice@example.com,bob@example.net'\n```\n\nYou can load secrets from `file://` only at boot. Each file must be a regular\nfile of at most 4 KiB with mode 0600 or stricter. The server does not read the\nfile again after startup:\n\n```bash\nexport ICLOUD_EMAIL='file:///run/secrets/icloud-email'\nexport ICLOUD_PASSWORD='file:///run/secrets/icloud-password'\nexport ICLOUD_MAIL_ADDRESS='file:///run/secrets/icloud-mail-address'\nexport ICLOUD_MAIL_PASSWORD='file:///run/secrets/icloud-mail-password'\n```\n\nSee [.env.example](.env.example) for the full 12-variable contract.\n\n## MCP tools\n\nThe server exposes a maximum of **23** tools. Disabled tools are absent from\n`tools/list`. The process does not construct clients for disabled domains.\n\n| Count | When |\n|------:|------|\n| **10** | Default: Calendar read+write + `icloud_capabilities` |\n| **7** | Global read-only, optional domains off (recommended first run) |\n| **23** | Contacts + Mail read + mutation + send, read-only off |\n\n`ICLOUD_MCP_READ_ONLY=true` removes every Calendar/Contacts write, every Mail\nmutation, and Mail send. It does not enable a disabled read domain.\n\n| Group | Tools |\n|-------|--------|\n| Global | `icloud_capabilities` |\n| Calendar read | `list_calendars`, `search_events`, `get_event`, `find_free_slots`, `validate_event`, `calendar_capabilities` |\n| Calendar write | `create_event`, `update_event`, `delete_event` |\n| Contacts read | `list_address_books`, `search_contacts`, `get_contact` |\n| Contacts write | `create_contact`, `update_contact`, `delete_contact` |\n| Mail read | `list_mailboxes`, `search_messages`, `get_message` |\n| Mail mutation | `set_message_flags`, `move_message`, `trash_message` |\n| Mail send | `send_message` |\n\n**Highlights:** Calendar update and delete operations understand occurrences\nand use strong `If-Match`. Contacts uses opaque book IDs and writes vCard 3.0.\nMail uses `(mailbox, UIDVALIDITY, UID)` identities, PEEK reads, and an exact\nrecipient policy for SMTP. `set_message_flags` fails closed with\n`protocol_error` when CONDSTORE is advertised. This occurs because go-imap\nbeta.8 cannot observe tagged MODIFIED. See the full behavior notes:\n[docs/caldav-compatibility.md](docs/caldav-compatibility.md),\n[docs/carddav-compatibility.md](docs/carddav-compatibility.md),\n[docs/mail-compatibility.md](docs/mail-compatibility.md).\n\n**Idempotency:** `create_event` and `create_contact` use server-side UID keys.\nUse `client_uid` or its `idempotency_key` alias. A repeated create conflicts if\nthe UID exists. It never silently overwrites the object. The optional\n`idempotency_key` for `update_event` and `update_contact` is\n**process-local only**. Its in-memory cache has a **15 minute TTL** and clears\nwhen the process restarts.\n\nThe same key and parameters return the cached success. The same key with\ndifferent parameters returns `conflict`. Combine an update key with a strong\n`etag` when possible. See\n[docs/error-codes.md](docs/error-codes.md).\n\n## Configuration\n\nExactly **12** product environment variables:\n\n| Variable | Default | Contract |\n|----------|---------|----------|\n| `ICLOUD_EMAIL` | none | Required Calendar/Contacts identity. `file://` supported (regular file, <=4 KiB, mode 0600+). |\n| `ICLOUD_PASSWORD` | none | Required app-specific password. Mail uses it as a fallback. `file://` as above. |\n| `ICLOUD_MCP_READ_ONLY` | `false` | Global mutation kill switch. |\n| `ICLOUD_MCP_LOG_LEVEL` | `info` | Stderr level. See the accepted forms below. |\n| `ICLOUD_MCP_DEFAULT_TZ` | `UTC` | IANA zone for offset-less Calendar inputs and recurring-write fallback. |\n| `ICLOUD_MCP_ENABLE_CONTACTS` | `false` | Contacts tools. Writes require read-only mode to be off. |\n| `ICLOUD_MCP_ENABLE_MAIL` | `false` | Mail reads. Requires a Mail address and password. |\n| `ICLOUD_MAIL_ADDRESS` | none | Full IMAP/SMTP address when Mail is on. `file://` as above. |\n| `ICLOUD_MAIL_PASSWORD` | `ICLOUD_PASSWORD` | Optional dedicated Mail app password. `file://` as above. |\n| `ICLOUD_MCP_ENABLE_MAIL_WRITE` | `false` | Three IMAP mutation tools. |\n| `ICLOUD_MCP_ENABLE_MAIL_SEND` | `false` | `send_message` (independent of Mail write). |\n| `ICLOUD_MCP_SMTP_ALLOWED_RECIPIENTS` | none | Required if send is on. Use exact addresses or literal `*`. Literal `*` causes a boot warning. Use exact addresses when possible. |\n\nBooleans accept only unset, `0`, `false`, `1`, or `true`. Invalid values cause a\nboot failure. The server validates the configuration **before** network access.\nMail write or send without Mail causes a boot error. Mail without an address or\npassword also causes a boot error. Send without a recipient policy causes a\nboot error, including in read-only mode.\n\nGlobal read-only can coexist with write and send flags, but it prevents their\nregistration.\n\nThe server trims log levels and ignores case. It accepts `debug` or `-4`,\n`info`, `warn`, `warning`, or `2`, and `error` or `4`. Unset or unrecognized\nvalues use `info`.\n\nUse `-version` to print the version. The optional\n`-health 127.0.0.1:port` flag serves `/healthz` and `/status` on loopback. Both\nendpoints return JSON with domains and rate limits. The optional\n`-audit-format=json|text` flag selects the mutation audit format on stderr.\nThe default format is `json`.\n\n### Dates\n\n- Calendar **input** `start` and `end` accept RFC3339 with an offset. They also\n  accept wall-clock values without an offset in `ICLOUD_MCP_DEFAULT_TZ`. Use\n  values without an offset for the user's local time. Recurring creates and\n  creates with an explicit timezone write TZID and VTIMEZONE. Non-recurring\n  timed creates default to UTC `Z` on the wire. All-day creates use\n  `VALUE=DATE`.\n- Calendar **output** uses RFC3339 for timed events. It always includes an\n  explicit numeric offset in `ICLOUD_MCP_DEFAULT_TZ`, never bare `Z`. All-day\n  dates use `YYYY-MM-DD`. See `calendar_capabilities.outputFormat`.\n- Contacts birthdays: write `YYYY-MM-DD` only.\n- Mail search: `since` inclusive, `before` exclusive (`YYYY-MM-DD`).\n\nAgent error codes and retry policy: [docs/error-codes.md](docs/error-codes.md).\nHost wiring examples: [docs/agent-hosts.md](docs/agent-hosts.md). Product roadmap:\n[ROADMAP.md](ROADMAP.md).\n\n## Security (summary)\n\nUntrusted remote text can influence an LLM on the host. Labels do not form a\nsecurity boundary. A compromised model can call every **registered** tool. This\nrisk applies to every host and vendor.\n\n- **Egress fixed:** Calendar uses `caldav.icloud.com` and matching\n  `p[0-9]{1,3}-caldav.icloud.com:443` hosts. Contacts uses matching Contacts\n  hosts. IMAP uses `imap.mail.me.com:993`. SMTP uses\n  `smtp.mail.me.com:587` with mandatory STARTTLS. Destinations are not\n  configurable. The server ignores DAV proxy environment variables and verifies\n  TLS 1.2 or later.\n- **Isolation:** each domain has separate credentials, transports, dialers,\n  limiters, semaphores, and protocol stacks. No authenticated HTTP client\n  connects to multiple domains.\n- **Secrets:** the server removes secrets, including Basic and SASL PLAIN forms.\n  Boot-only `file://` reads require mode 0600 or stricter. The server does not\n  use `os/exec` or telemetry. It does not write to disk after boot.\n- **Audit:** mutation logs include `domain`, `resourceType`, and the process-local\n  HMAC `resourceToken`. They never contain raw paths, UIDs, mailboxes, or\n  recipients.\n- **Residual risk:** one process holds credentials for all enabled domains.\n  Feature flags do not remove compiled code. For stronger isolation, use\n  read-only mode and enable fewer domains. You can also use a dedicated Mail\n  password or separate processes.\n\nFull policy: [SECURITY.md](SECURITY.md), [docs/security.md](docs/security.md).\nArchitecture: [docs/architecture.md](docs/architecture.md).\n\n## Limits (summary)\n\n| | |\n|--|--|\n| Tool deadline | 25s (DAV HTTP 30s) |\n| Stdio / MCP result | 1 MiB frame. 256 KiB result. Reflected protocol errors have a limit. |\n| Calendar | Search range: 366 days. Results: 400 total, 2,500 per calendar, and 10,000 materialized across calendars. Recurrence: 2,000 expansions, 100k steps per series, and 250k steps per search. Rate: 60 reads and 20 writes per minute. Concurrency: 4 reads and 2 writes. |\n| Contacts | 100 books. 100 summaries. 2000 cards scanned. Rate: 60 reads and 20 writes per minute. Concurrency: 4. |\n| Mail | Rate: 60 reads, 20 mutations, and 20 sends per minute. Semaphores: 2 reads, 1 mutation, and 1 send. No mutation or send retry. |\n| Writes | No automatic replay of Calendar PUT/DELETE, Contacts writes, IMAP mutations, or SMTP. Ambiguous outcomes use `outcome_unknown`. |\n\nThe server discovers Calendar at boot and Contacts when first used. Each call\nuses a fresh IMAP or SMTP session. See these documents for rates, parser\nbudgets, and retry rules:\n[docs/testing.md](docs/testing.md), [docs/architecture.md](docs/architecture.md).\n\n## Supported scope\n\n| Data | Connector | Support |\n|------|-----------|---------|\n| Calendar | CalDAV | Always |\n| Contacts | CardDAV | Optional |\n| Mail read / mutation / send | IMAP + SMTP | Optional, independently gated |\n| Modern Reminders, Notes, Photos, Drive, Find My, Keychain, Messages, Home | No suitable official remote connector for this model | Excluded |\n\nThe server does not treat modern Reminders as generic CalDAV VTODO. Apple's\nthird-party documentation for this type of access covers Mail, Calendar, and\nContacts.\n\n**Multi-account:** one process holds one iCloud identity. Start a separate\n`icloud-mcp` process for each identity. Give each process a separate environment\nand, if needed, a separate `-health` port. The MCP host can multiplex these\nprocesses. See [docs/agent-hosts.md](docs/agent-hosts.md).\n\n## Dependencies\n\nUse Go 1.25.13 or newer. The project has one module and exactly **10** direct\ndependencies. If you add a direct dependency, add its justification here.\n\n| Dependency | Exact version | Justification |\n|------------|---------------|---------------|\n| `github.com/emersion/go-webdav` | `v0.7.0` | CalDAV primitives. Discovery and conditional operations remain hand-written. |\n| `github.com/emersion/go-ical` | `v0.0.0-20250609112844-439c63cef608` | iCalendar parse/encode |\n| `github.com/mark3labs/mcp-go` | `v0.57.0` | MCP stdio, schemas, JSON-RPC |\n| `github.com/teambition/rrule-go` | `v1.8.2` | Bounded recurrence with timezone preservation |\n| `golang.org/x/time` | `v0.15.0` | Per-domain rate limiters |\n| `github.com/emersion/go-vcard` | `v0.0.0-20260618161152-d854b7e0e2d3` | vCard 3.0/4.0 read, 3.0 write |\n| `github.com/emersion/go-imap/v2` | `v2.0.0-beta.8` | IMAP behind `internal/mail/imapadapter` |\n| `github.com/emersion/go-message` | `v0.18.2` | MIME / plain-text bounds |\n| `github.com/emersion/go-smtp` | `v0.24.0` | SMTP + STARTTLS |\n| `github.com/emersion/go-sasl` | `v0.0.0-20241020182733-b788ff22d5a6` | SASL PLAIN after STARTTLS |\n\n## Build and test\n\n```bash\nmake build        # local host binary, VERSION defaults to dev\nmake test         # go test ./... -race -cover\nmake lint         # go vet + pinned golangci-lint\nmake release VERSION=v0.4.0      # packaged linux/arm64, digest-pinned Go 1.25.13 image\nmake release-all VERSION=v0.4.0  # packaged linux/amd64, linux/arm64, darwin/arm64 (host Go)\nmake install      # host-compatible build to INSTALL_DIR (default ~/.local/bin)\n```\n\nRelease targets reject an unset version or a `dev` version. Archives contain\nthe binary, `LICENSE`, and `THIRD_PARTY_NOTICES.md`. The `dist/` directory also\ncontains a SHA-256 checksum file. GitHub tag releases run `make release-all`\nonly after CI and gitleaks succeed for that tag. They use Go 1.25.13 with\n`check-latest` disabled.\n\nLocal `make release` uses the digest-pinned container path for linux/arm64.\nRelease blobs include cosign keyless signatures. `-version` first uses the\nrelease ldflags value. If this value is absent, it uses Go module build\ninformation. Thus, `go install ...@version` reports the module version.\n\nCI runs race tests, fuzz smoke, govulncheck, gitleaks, and\nmulti-architecture builds. It also checks coverage, egress and security AST\nguards, binary size, and public text. Aggregate coverage must be at least 78%.\nPackage floors include `cmd/icloud-mcp` and `internal/health`. The build check\nincludes windows/amd64 smoke. The binary budget is 20 MiB.\n\nThe public-text policy checks the tree and new commits. Live iCloud tests use\nthe `integration` build tag. These tests are optional and never run in CI. See\n[docs/testing.md](docs/testing.md).\n\n## Attribution\n\nCalendar tool shape and several patterns were inspired by\n[`github.com/roygabriel/mcp-icloud-calendar`](https://github.com/roygabriel/mcp-icloud-calendar)\n(MIT, copyright 2026 Gabe). Code was rewritten, not copied. This server adds\nhard per-domain egress, redaction, bounded parsers, conditional mutation,\nContacts, Mail, and no telemetry.\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). Run `gofmt`, `make test`, and `make lint`\nbefore opening a pull request.\n\n## License\n\nMIT. See [LICENSE](LICENSE) and [third-party notices](THIRD_PARTY_NOTICES.md).\n",
  "bytes": 17001,
  "sha": "ba78b4e48365c2fc83d081eadf6c844912d721043f26229a496565afec7db860",
  "repo_slug": "thomascrouzet/icloud-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_thomascrouzet_icloud_mcp_048f071a/readme"
}