icloud-headless-mcp
Run your iCloud from anywhere, no Mac required. One self-hosted MCP server over stdio for Mail, Calendar, Contacts, Notes, Reminders and Drive: approval-gated writes, reproducible on any always-on Linux host, including Hermes.
Install / Use
claude mcp add sjdonado -- npx -y github:sjdonado/icloud-headless-mcpIf the server publishes to npm under a different name, use that package instead — check the repo README.
MCP Server
Model Context Protocol server
Quality Score
Category
Development & EngineeringSupported Platforms
Our assessment of icloud-headless-mcp
icloud-headless-mcp scores 83/100 on our quality scale, 3035th of 4,659 Development & Engineering skills we index.
Its MCP Server is 25 KB long, well organised into 15 sections with 18 code examples: a thorough specification that gives an agent plenty to work with.
It has 3 GitHub stars, so there is little community track record yet; judge it on its content.
Maintenance, license and trust
- The repository was last updated 33 days ago, so icloud-headless-mcp is actively maintained.
- It is released under the MIT license, a permissive license that allows use, modification and commercial use with attribution.
- Its trust signals score 92/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
icloud-headless-mcp compared with similar skills
All 4 of these similar skills score higher than icloud-headless-mcp; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| icloud-headless-mcp (this skill)by sjdonado | 83 | 3 | 33d ago | MCP Server |
| Agent-Reachby Panniantong | 100 | 106.8k | 5d ago | CLAUDE.md |
| taste-skillby Leonxlnx | 100 | 106.2k | today | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 78.4k | today | CLAUDE.md |
| ai-job-searchby ai-job-search-dev | 100 | 67.9k | 3d ago | CLAUDE.md |
Frequently asked questions
- How do I install icloud-headless-mcp?
- Run
claude mcp add sjdonado -- npx -y github:sjdonado/icloud-headless-mcp. The install tabs above show the steps for each supported agent. - Which AI agents does icloud-headless-mcp work with?
- It is written for Claude Code and Claude Desktop, as a MCP Server file. Other agents that read the same format can often use it too.
- Is icloud-headless-mcp safe to use?
- It is MIT-licensed and scores 92/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is icloud-headless-mcp still maintained?
- The repository was last updated 33 days ago, so icloud-headless-mcp is actively maintained.
Skill content
View source on GitHubiCloud Headless MCP
Give any AI agent your iCloud: Calendar, Contacts, Mail, Notes, Reminders and Drive. One binary, a headless Chrome, two environment variables. No Mac required.

Apple gives Notes and Reminders no API at all, and the parts that do have one are scattered across CalDAV, CardDAV and IMAP. So most iCloud automation quietly assumes a Mac on your desk running AppleScript. This server removes the Mac: calendar, contacts and mail go over the open protocols, and Notes and Reminders are driven through the real iCloud web apps in a headless Chrome that stays signed in. It runs on your laptop, a small VPS or a Raspberry Pi, and any MCP client that can spawn a process can use it.
- 32 tools, one server entry. Read and write your calendar, search contacts, read and send mail, read and create notes, list and complete reminders, and check the iCloud Drive pull. Full list below.
- Two variables to configure. Your Apple ID and an app-specific password. Everything else has a default.
- Sign-in from your agent. When the session needs a login, the agent hands you a one-time link to the headless browser. You sign in from any browser, the page tells you when you are done, and the agent carries on.
- Safe writes. Deletes, sent mail, note rewrites and anything that reaches other people ask you first, through the MCP client itself. Every Notes and Reminders write is checked against what the app shows, not assumed from a response code.
Setup
1. Install
You need Google Chrome or Chromium on the machine (macOS finds Google Chrome.app by itself) and a Go toolchain:
go install github.com/sjdonado/icloud-headless-mcp/cmd/icloud-mcp@latest
That puts icloud-mcp in $(go env GOPATH)/bin. For an always-on Linux server with its own service account, use the one-line installer instead.
2. Make an app-specific password
At account.apple.com, open Sign-In and Security, then App-Specific Passwords, and create one. It is revocable there at any time without touching anything else. Never use your primary Apple password.
3. Add it to your agent
The command is icloud-mcp, with your Apple ID and the app-specific password in its environment. Keep the tool timeout at 330 seconds or more: a Notes or Reminders call takes 60 to 90 seconds, and a cold app tab longer.
pi (with pi-mcp-adapter), in .mcp.json or ~/.config/mcp/mcp.json:
{
"mcpServers": {
"icloud": {
"command": "icloud-mcp",
"env": { "ICLOUD_APPLE_ID": "you@icloud.com", "ICLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx" },
"requestTimeoutMs": 330000,
"directTools": true
}
}
}
Claude Code (its default tool timeout is already long enough, and it renders the approval questions inline):
claude mcp add --scope user --env ICLOUD_APPLE_ID=you@icloud.com --env ICLOUD_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx icloud -- icloud-mcp
OpenCode, in opencode.json:
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"icloud": {
"type": "local",
"command": ["icloud-mcp"],
"environment": { "ICLOUD_APPLE_ID": "you@icloud.com", "ICLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx" },
"enabled": true
}
}
}
Codex: add it, then raise the tool timeout in ~/.codex/config.toml, because Codex's default of 60 seconds is shorter than a Notes call:
codex mcp add icloud --env ICLOUD_APPLE_ID=you@icloud.com --env ICLOUD_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx -- icloud-mcp
[mcp_servers.icloud]
command = "icloud-mcp"
env = { ICLOUD_APPLE_ID = "you@icloud.com", ICLOUD_APP_PASSWORD = "xxxx-xxxx-xxxx-xxxx" }
tool_timeout_sec = 330
Executor, to share it with every agent behind one endpoint: the local Executor runs stdio servers, keeps the two values as its own secrets instead of in each agent's config, and adds its per-tool policies on top of this server's own approvals.
npm install -g executor && executor install
executor call executor mcp addServer '{"transport":"stdio","name":"iCloud","command":"icloud-mcp","env":{"ICLOUD_APPLE_ID":"you@icloud.com","ICLOUD_APP_PASSWORD":"xxxx-xxxx-xxxx-xxxx"}}'
npx add-mcp "executor mcp" --name executor
Any other MCP client: spawn icloud-mcp over stdio with those two variables set. A client that cannot show approval questions still works: the tools that ask simply refuse there, and nothing is written. On a host whose clock runs UTC (a typical VPS), also set AGENT_TZ to your IANA zone, for example Europe/Amsterdam.
Nothing else to run: the server starts the headless Chrome by itself the first time it needs it, and the browser keeps running across agent restarts.
4. Sign in once, from the agent
Ask for anything. Calendar, contacts and mail answer straight away. The first Notes or Reminders call finds no iCloud session yet, and the agent asks to open the login door:

Approve it, and the agent gives you a link, the username root and a fresh one-time password:

Open the link in any browser. It is the headless Chrome, live: sign in with your Apple ID, enter the two-factor code, and tick Trust this browser. The Paste button types your clipboard into the page, which helps with a password manager.

The moment the sign-in lands, the door stops streaming, shows this, and closes itself:

Go back to the agent and ask again. That is the whole setup. If Apple then asks for a data-access approval on one of your devices, approve it there; reask_access re-sends that prompt if it expired.
The link is a Tailscale address when the machine is on a tailnet, so you can sign in from your phone, and a loopback address otherwise, for the machine itself or your own SSH tunnel. The door never listens anywhere else.
Tools
| Surface | Read | Write |
| --------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Calendar | list_calendars, list_events | create_event; update_event (asks when the event has other attendees); delete_event (asks) |
| Contacts | search_contacts | |
| Mail | list_mailboxes, list_mail, read_mail, search_mail | send_mail (asks) |
| Notes | notes_folders, notes_list, notes_read, notes_search | create_note; update_note (asks) |
| Reminders | reminder_lists, list_reminders, completed_reminders | create_reminder, complete_reminder |
| Drive | drive_status | |
| Recovery | | open_login (asks): the login door; reask_access (asks): re-sends Apple's data-access prompt; sign_out (asks): signs the server out of iCloud |
| Health | health_status, health_days, health_sleep, health_effort, health_recovery, health_sql | optional extension, see below |
Mail reads never mark a message as seen. No tool deletes or moves mail, deletes a note, or triggers the Drive pull.
What a call returns
Every tool returns its result as structuredContent and as the same JSON in a text block, for clients that ignore structured content. Every tool declares MCP annotations: reads are readOnlyHint, and delete_event, update_event, update_note and sign_out are destructiveHint. Times are ISO 8601 with the offset, in your zone. All-day dates are YYYY-MM-DD with all_day: true, and an all-day end is the last day, inclusive. A field the source does not hold is left out, never guessed.
A repeating occurrence carries its series rule. An event that sits in several calendars (a shared calendar and your own) is listed once, with every calendar in calendars.
{
"count": 2,
"window": { "start": "2026-03-09T00:00:00+01:00", "end": "2026-03-14T00:00:00+01:00", "absolute": true },
"events": [
{
"uid": "weekly-1",
"calendars": ["Personal"],
"summary": "Standup",
"start": "2026-03-09T09:00:00+01:00",
"end": "2026-03-09T09:30:00+01:00",
"all_day": false,
"recurring_instance": true,
"recurrence_rule": "FREQ=WEEKLY;BYDAY=MO"
},
{
"uid": "meet-1",
"calendars": ["Personal"],
"summary": "Budget review",
"start": "2026-03-13T10:00:00+01:00",
"end": "2026-03-13T10:45:00+01:00",
"all_day": false,
"location": "Room 4, floor 2",
"description": "Agenda:\n1. numbers",
"status": "confirmed",
"url": "https://example.com/m/1",
"organizer": { "name": "Carol", "address": "carol@example.com" },
"attendees": [
{ "name": "Alice", "address": "alice@example.com", "role": "req-participant", "participation": "accepted" },
{ "name": "Bob", "address": "bob@example.com", "role": "req-participant", "participation": "needs-action" }
],
"alarms": [
{ "action": "display", "offset": "-PT15M" },
{ "action": "audio", "at": "2026-03-13T09:00:00+01:00" }
],
"created": "2026-02-01T09:00:00+01:00",
"last_modified": "2026-02-28T17:00:00+01:00"
}
]
}
An alarm offset is an ISO 8601 duration from the start (negative is before). It carries related: "end" when the alarm is anchored to the end.
Emails and phones carry the card's label: Apple's own custom label when you set one, else its type. A birthday without a year reads --MM-DD.
{
"count": 1,
"contacts": [
{
"name": "Lin Chen",
"nickname": "Linny",
"organization": "Acme Corp",
"job_title": "Staff Engineer",
"birthday": "1990-04-02",
"emails": [{ "value": "lin@acme.test", "type": "work" }],
"phones": [{ "value": "+15550001111", "type": "cell" }],
"addresses": [
{ "type": "work", "street": "1 Main St", "city": "Springfield", "region": "IL", "postal_code": "62701", "country": "USA" }
]
}
]
}
</details>
<details>
<summary><code>list_mail</code> with <code>limit: 1</code></summary>
A listing is enough to triage: addresses, flags, size, dates, attachment names from the message structure, and a snippet of the first text part. search_mail rows have the same shape. Nothing gains \Seen: every fetch peeks, on a mailbox opened read-only.
{
"mailbox": "INBOX",
"count": 1,
"matched": 214,
"bounds": { "since": null, "until": null },
"messages": [
{
"uid": "4812",
"mailbox": "INBOX",
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
106.8kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
taste-skill
106.2kAgent skills that give coding agents taste: art direction, reference images and a browser check instead of generic AI pages
headroom
78.4kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
ai-job-search
67.9kThe job search that runs on your machine. AI job application framework built on Claude Code: evaluate postings, tailor CVs, write cover letters, prep interviews. Fork it and own it.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
