iCloud Mail MCP
A Model Context Protocol (MCP) server for iCloud Mail. Lets an LLM read, search, file and send your Apple mail over IMAP and SMTP.
Runs entirely on your machine: your credentials and your mail never reach a third party. Networking and MIME parsing use only the Python standard library.
Key Features
- Read-only by default. Search and read tools use
SELECT ... readonlyandBODY.PEEK— nothing is marked as read, moved or deleted behind your back. - Searches every folder, not just the inbox. Replies get filed away by mail
rules;
search_all_foldersfinds them where an inbox-only search can't. - Drafts before sends.
save_draftputs a message in Drafts for you to review.send_emailexists, but it is separate and explicit. - Nothing destroys mail. There is no tool that deletes messages, and
delete_mailboxrefuses any folder that still holds some. - Handles real iCloud MIME. Modified UTF-7 folder names, quoted-printable, lying charsets, HTML-only messages, accented server-side search.
Requirements
- Python 3.11 or newer, and uv
- An iCloud account with two-factor authentication enabled
- An app-specific password — iCloud rejects your main password over IMAP
Getting started
Once published to PyPI, no clone is needed:
uvx --from icloud-mail-mcp icloud-mcp-setup # interactive configuration
uvx --from icloud-mail-mcp icloud-mcp # run the server
From source:
git clone https://github.com/JulienRabault/icloud-mcp.git
cd icloud-mcp
uv sync
uv run python -m icloud_mcp.setup
The setup command asks for your address and app-specific password, tests the
connection, writes .env, then prints the exact config block for your client.
Generate the app-specific password at account.apple.com → Sign-In and Security → App-Specific Passwords.
Standard config works in most clients:
{
"mcpServers": {
"icloud-mail": {
"command": "uv",
"args": ["run", "--directory", "/path/to/icloud-mcp", "python", "-m", "icloud_mcp"]
}
}
}
Claude Code
claude mcp add icloud-mail --scope user -- uv run --directory /path/to/icloud-mcp python -m icloud_mcp
Check with claude mcp list.
Claude Desktop
Add the standard config to claude_desktop_config.json:
- macOS —
~/Library/Application Support/Claude/claude_desktop_config.json - Windows —
%APPDATA%\Claude\claude_desktop_config.json
On Windows, use the absolute path to uv.exe: desktop clients don't always
inherit your shell PATH.
Codex
In ~/.codex/config.toml:
[mcp_servers.icloud-mail]
command = "uv"
args = ["run", "--directory", "/path/to/icloud-mcp", "python", "-m", "icloud_mcp"]
Cursor / Windsurf / VS Code
Use the standard config block in the MCP settings file of your editor
(.cursor/mcp.json, ~/.codeium/windsurf/mcp_config.json, or the VS Code MCP
settings).
MCP servers load at client startup — restart the client after editing its config.
Tools
Read — none of these modify the mailbox:
| Tool | Description |
|---|---|
list_folders |
List folders, optionally with message and unread counts |
folder_status |
Counts for one folder without listing messages |
search_emails |
Search one folder: text, sender, recipient, subject, dates, flags, size |
search_all_folders |
The same search across every folder at once |
read_email |
Full message: decoded body, optional HTML, attachment metadata |
get_thread |
Rebuild a conversation, optionally with each message body |
save_attachments |
Write attachments to disk and return their paths |
Write — explicit by design:
| Tool | Description |
|---|---|
save_draft |
Put a message in Drafts. Nothing is sent |
set_flag |
Read/unread, flagged, answered. Reversible |
create_mailbox |
Create a folder, accented names included |
rename_mailbox |
Rename a folder, messages follow |
delete_mailbox |
Delete an empty folder. Refuses while it holds mail |
auto_organize |
File messages by rules. Simulates unless dry_run=false |
move_emails |
Move between folders. Simulates unless dry_run=false |
send_email |
Actually sends. No draft step, no undo |
No tool destroys mail. delete_mailbox refuses a folder that still holds
messages — move them out first, which keeps the decision with you.
Attachment bytes never pass through the model: save_attachments writes files
and returns paths. Filenames arriving from email are sanitised — they are
hostile input, not trusted paths.
Resources
| URI | Content |
|---|---|
icloud://folders |
Every folder with message and unread counts |
icloud://unread |
Unread messages in the inbox |
Prompts
| Prompt | Purpose |
|---|---|
triage_inbox |
Sort recent mail into action required / info / waiting / ignorable |
draft_reply |
Read a message and its thread, draft a reply into Drafts |
follow_up |
Reconstruct an exchange with a contact, say who owes whom a reply |
Automation without an MCP client
examples/ holds standalone scripts using the same modules — point cron or Task
Scheduler at them:
uv run python examples/daily_digest.py # what arrived today
uv run python examples/watch_sender.py acme.com # exit 1 if nothing new
uv run python examples/waiting_on_reply.py # threads nobody answered
uv run python examples/auto_file.py --apply # file mail by rules
All support --json for piping. See examples/README.md.
Bundled skill
skills/mailbox-search/ is a Claude Code skill that forces a sweep of every
folder before concluding a message doesn't exist:
cp -r skills/mailbox-search ~/.claude/skills/
iCloud quirks handled here
Worth knowing if you're writing your own IMAP client against iCloud:
SEARCHreturns UIDs out of order. RFC 3501 doesn't guarantee ordering, and iCloud genuinely returns unsorted lists. Taking the tail of the response gives you the wrong messages — sort numerically first.- No
MOVE, noUIDPLUS. Moving meansCOPY+\Deleted+EXPUNGE, andEXPUNGEpurges every\Deletedmessage in the folder.move_emailsrefuses to run when the folder holds deleted messages outside the requested batch, which would otherwise be destroyed. SEARCH CHARSET UTF-8works. Accented queries run server-side across the whole mailbox. A client-side fallback covers servers that refuse, and flags it viafiltered_client_sidein the response.- Folder names use modified UTF-7 (RFC 3501), implemented in
utf7.py. - Charsets lie. Bodies fall back to latin-1 when the declared charset fails,
and to stripped HTML when there's no
text/plainpart.
Security notes
- Credentials live in
.env(gitignored) or the environment, never in code.Settings.__repr__omits the password. - Email content is data, not instructions. The server tells clients never to act on directives found inside a received message.
send_emailandmove_emailsare meant to run only after the user approves the exact content or the exact message list in the conversation.
Development
uv run pytest -q
49 offline tests — no network, no credentials. CI runs them on Linux, macOS and Windows against Python 3.11 to 3.13.
src/icloud_mcp/
config.py env / .env loading
utf7.py modified UTF-7 for folder names
models.py frozen Pydantic models
mime.py header, body and attachment decoding
imap_client.py connection, LIST, STATUS, SELECT, FETCH
search.py SEARCH criteria, threading, multi-folder search
smtp_client.py MIME building, SMTP send, copy to Sent
attachments.py attachment extraction, filename sanitising
drafts.py APPEND to Drafts
flags.py \Seen, \Flagged, \Answered
move.py COPY + EXPUNGE with the anti-purge guard
mailboxes.py create, rename, delete (empty only)
organize.py rule-based filing
server.py tools, resources, prompts
setup_wizard.py interactive configuration
cli.py terminal checks
Contributing
Issues and pull requests welcome. Tests must pass offline — no test may require a real mailbox.
License
MIT
Release files for icloud-mail-mcp 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| icloud_mail_mcp-0.1.1.tar.gz | 127.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| icloud_mail_mcp-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 167.3 kB
Release files / icloud_mail_mcp-0.1.1.tar.gz
| Download URL | icloud_mail_mcp-0.1.1.tar.gz |
|---|---|
| Size | 127.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
aadad2f32ca775376989be51986c6b80c9f3df6542b19c2aa14c7074e867e7d7
|
|
BLAKE2b-256 checksum How to use checksums |
7e5d53c9165d99c2c9574114f010d5e8f832c15394fa2dab8c4e41a318fcb88f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency logRelease files / icloud_mail_mcp-0.1.1-py3-none-any.whl
| Download URL | icloud_mail_mcp-0.1.1-py3-none-any.whl |
|---|---|
| Size | 40.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0aaabfdbe44218f434120d17a172e103cc98dc8f5b8207de63df5707754dc43f
|
|
BLAKE2b-256 checksum How to use checksums |
7ff360461724708f742f70d99db8b982da49413eb8877975c9bb35ef8a8feffe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.
Transparency log