SkillAgentSearch skills...

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-mcp

If the server publishes to npm under a different name, use that package instead — check the repo README.

About this skill
🔌

MCP Server

Model Context Protocol server

Quality Score

83/100

Supported Platforms

Claude Code
Claude Desktop

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.

Substance
30/30
Structure
20/20
Description
15/15
Adoption
3/20
Freshness
15/15

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.

SkillScoreStarsUpdatedFormat
icloud-headless-mcp (this skill)by sjdonado83333d agoMCP Server
Agent-Reachby Panniantong100106.8k5d agoCLAUDE.md
taste-skillby Leonxlnx100106.2ktodayCLAUDE.md
headroomby headroomlabs-ai10078.4ktodayCLAUDE.md
ai-job-searchby ai-job-search-dev10067.9k3d agoCLAUDE.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.

iCloud 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.

pi answering "what needs my attention?" through the MCP in one prompt: who sent the latest reply in a mail thread and who else is on it, open reminders across two lists with due dates, and the most recently edited notes with their folders

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:

pi asking the owner to approve opening the login door

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

pi handing back the login-door link and 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 login door streaming Apple's sign-in page from the headless browser

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

the login door's done screen: Signed in to iCloud, continue in your agent

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.

<details> <summary><code>list_events</code> with <code>start: "2026-03-09"</code>, <code>end: "2026-03-13"</code></summary>

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.

</details> <details> <summary><code>search_contacts</code> with <code>query: "lin"</code></summary>

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

View on GitHub
GitHub Stars3
CategoryDevelopment
Updated1mo ago
Forks0

Languages

Go

Trust signals

92/100

From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.

1 low
icloud-headless-mcp — MCP Server: Install & Safety Check | SkillAgent