icloud-mcp is a Model Context Protocol (MCP) server for iCloud Mail that allows large language models to read, search, archive, and send Apple Mail via the IMAP and SMTP protocols. It runs entirely on the user's local machine; credentials and email content never reach third parties, and network communication and MIME parsing use only the Python standard library.
Main capabilities
- Search all folders, not just the inbox. Mail rules may archive replies to other folders, and search_all_folders can find emails that would be missed when searching only the inbox.
- Read-only by default: the search and read tools use SELECT ... readonly and BODY.PEEK, and will not mark emails as read, move them, or delete them in the background.
- Draft first, then send: save_draft places the email in the Drafts folder for the user to review; send_email exists but is separate and must be called explicitly.
- No email destruction: there is no tool to delete emails, and delete_mailbox refuses to delete a folder that still contains emails.
- Handles real iCloud MIME: including modified UTF-7 folder names, quoted-printable, inaccurate charset declarations, HTML-only emails, and server-side searches with accented characters.
- Attachment bytes do not pass through the model: save_attachments writes files and returns paths; filenames from emails are sanitized and treated as untrusted input.
Usage
Requires Python 3.11 or later and uv. Requires an iCloud account with two-factor authentication enabled, using an app-specific password (iCloud refuses to use the main password over IMAP). Generate an app-specific password at account.apple.com → Sign-In and Security → App-Specific Passwords.
After publishing to PyPI, there is no need to clone the repository. The setup command asks for the address and app-specific password, tests the connection, writes .env, and then prints a configuration block suitable for clients.
The standard configuration works for most clients. Use claude mcp list to check.
Claude Desktop configuration: add the standard configuration to claude_desktop_config.json. On macOS the path is ~/Library/Application Support/Claude/claude_desktop_config.json, and on Windows the path is %APPDATA%\Claude\claude_desktop_config.json. On Windows, use the absolute path to uv.exe, because the desktop client does not necessarily inherit the shell's PATH.
Codex configuration: configure in ~/.codex/config.toml.
Cursor, Windsurf, VS Code configuration: use the standard configuration block in the editor's MCP settings file (.cursor/mcp.json, ~/.codeium/windsurf/mcp_config.json, or VS Code MCP settings).
The MCP server loads when the client starts; after editing the configuration, restart the client.
Tool categories
Read tools do not modify the mailbox. Write tools require explicit calls by design. No tool destroys emails. delete_mailbox refuses to delete a folder that still contains emails; emails must be moved out first, leaving the decision to the user.
Automation without an MCP client
The examples/ directory contains standalone scripts that use the same modules and can be pointed at cron or Task Scheduler. All scripts support --json for pipeline processing. See examples/README.md for details.
skills/mailbox-search/ is a Claude Code skill that enforces scanning every folder before concluding that an email does not exist.
iCloud quirks handled
- UIDs returned by SEARCH are unordered. RFC 3501 does not guarantee order, and iCloud does indeed return unsorted lists. Taking the tail of the response directly yields the wrong email; it must first be sorted numerically.
- No MOVE, no UIDPLUS. Moving means COPY + \Deleted + EXPUNGE, and EXPUNGE clears all \Deleted emails in the folder. move_emails refuses to run when there are deleted emails in the folder outside the requested batch, otherwise those emails would be destroyed.
- SEARCH CHARSET UTF-8 is available. Accented queries run server-side across the entire mailbox. Client-side fallback covers servers that reject, and is flagged in the response via filtered_client_side.
- Folder names use modified UTF-7 (RFC 3501), implemented in utf7.py.
- The charset may be inaccurate. When the declared charset fails, the body falls back to latin-1; when there is no text/plain part, it falls back to stripped HTML.
- Credentials are stored in .env (gitignored) or the environment, never in code. Settings.repr omits the password.
- Email content is data, not instructions. The server tells the client never to execute instructions in received email.
- send_email and move_emails should only run after the user approves the exact content or exact email list in the conversation.
Testing and Contributing
49 offline tests, no network or credentials required. CI runs on Linux, macOS, and Windows for Python 3.11 to 3.13. Issues and pull requests are welcome. Tests must pass offline, and no test may require a real mailbox.