applemail-mcp-server
A local MCP server that gives Claude access to macOS Mail.app through JXA (JavaScript forAutomation). Every account already configured in Mail — iCloud, Gmail, Outlook/Exchange, IMAP —becomes visible, with no OAuth, no API keys, and no data leaving the machine.
Read-only by default. There is no send tool, by design.
Tools
| Tool | Writes? | What it does |
|---|---|---|
apple_mail_list_accounts |
no | Account names, types, addresses. Start here. |
apple_mail_list_mailboxes |
no | Mailboxes + unread counts (names are case-sensitive and localised). |
apple_mail_unread_summary |
no | Unread counts per account/mailbox. Reads counters only — fast on huge mailboxes. |
apple_mail_search_messages |
no | Header search with filters; returns opaque per-message handles. |
apple_mail_get_message |
no | Full headers, recipients, attachment names and body for one handle. |
apple_mail_set_message_status |
yes | Mark read/unread/flagged. Disabled unless you opt in. |
apple_mail_compose_draft |
yes | Opens a pre-filled compose window. Never sends — you click Send. |
apple_calendar_list_calendars |
no | Calendar names + writable flag. Call before creating an event. |
apple_calendar_create_event |
yes | Create one event in Apple Calendar only. Disabled unless you opt in. Skips duplicates. |
Requirements
- macOS with Mail.app configured (at least one account)
- Node.js 18+
Build
npm install
npm run build
node dist/index.js --doctor # verifies Mail.app is reachable and permissions are granted
Configure Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"apple-mail": {
"command": "/ABSOLUTE/PATH/TO/node",
"args": ["/ABSOLUTE/PATH/TO/applemail-mcp-server/dist/index.js"]
}
}
}
Use the absolute path to both node (which node) and dist/index.js — Claude Desktoplaunches the server with a stripped environment, so a bare node often will not resolve.
Then quit Claude with Cmd+Q — closing the window is not enough, the config is only read atstartup — and reopen it. If the server does not appear, check ~/Library/Logs/Claude/mcp*.log.
Configure Claude Code
claude mcp add --transport stdio --scope user apple-mail -- node /ABSOLUTE/PATH/TO/dist/index.js
macOS permissions
The first call triggers a system prompt to let the host app control Mail. If you dismissed it, goto System Settings → Privacy & Security → Automation and enable Mail under Claude (orClaude Code / Terminal, whichever launched the server). Re-launch the host app afterwards.
Environment variables
| Variable | Default | Effect |
|---|---|---|
APPLE_MAIL_MCP_ALLOW_STATUS_WRITES |
off | Set to 1 to register the read/flag tool. |
APPLE_MAIL_MCP_ALLOW_COMPOSE |
on | Set to 0 to remove the compose-window tool. |
APPLE_MAIL_MCP_ALLOW_CALENDAR_WRITES |
off | Set to 1 to register apple_calendar_create_event. |
APPLE_MAIL_MCP_TIMEOUT_MS |
90000 |
Per-operation timeout. |
For the strictest posture, leave status and calendar writes off and set APPLE_MAIL_MCP_ALLOW_COMPOSE=0.The server is then incapable of modifying anything.
Design notes
Apple Events are the bottleneck. Each property access is an IPC round-trip, so the server neverloops over messages individually — it uses bulk getters (spec.subject() returns the whole columnin one event) and pushes filters down into Mail.app via whose. since_days defaults to 30 andsearch_messages refuses rather than hangs when more than max_scan (default 400) messagesmatch, returning an error that tells the agent how to narrow the query.
Handles, not IDs. Search returns an opaque base64 handle encoding account + mailbox + Mail'sinternal row id, so a follow-up read is a direct lookup rather than a re-scan. Handles go stale ifthe message is moved or deleted; the error says so and tells you to search again.
No string interpolation into script source. Parameters are passed to osascript as a singleJSON argv entry and parsed inside the script, so mailbox names and search terms cannot inject code.
Body search is not supported. query matches subject and sender only — searching bodies overApple Events on a large mailbox is pathologically slow. Use Mail's own search for that.
Calendar writes go to Apple Calendar only. apple_calendar_create_event never touches GoogleCalendar, Outlook, or any other service — it calls Calendar.app the same way compose_draft callsMail.app. It skips (rather than duplicates) an event when one with the same title already existson the same day in the same calendar, so a daily scan can be re-run safely. Date/time extractionfrom message text is left to the calling agent, not done with regex inside this server — AppleEvents give no reliable way to validate a guessed date, so a wrong guess would silently create abad event.
Daily mail-to-calendar automation
This server never runs on its own — an agent decides when to scan mail and whether a messagedescribes something calendar-worthy, then calls apple_calendar_create_event. To get a dailyscan, schedule a Claude Code routine (see the schedule skill) that runs once a day with a promptalong these lines:
Call apple_mail_search_messages (since_days: 1) across all accounts, read anything that looks
like a meeting, appointment, or deadline with apple_mail_get_message, then call
apple_calendar_list_calendars and apple_calendar_create_event to add each one to the right Apple
Calendar. Never invent a date. Report what was created and what was skipped.
Requires APPLE_MAIL_MCP_ALLOW_CALENDAR_WRITES=1, and Mail.app / Calendar.app must both bereachable when the routine fires (the host machine needs to be on and unlocked).
Security
This server reads your entire mailbox. Two things worth keeping in mind:
- Email bodies are untrusted input. A message can contain text aimed at the model rather thanat you. The
get_messagedescription tells the model to treat message contents as data andnever as instructions, but a read-only configuration is what actually bounds the blast radius —keep the write tools off unless you need them. - Scope matters more than trust. If the mailbox holds client, government or UN correspondence,consider pointing searches at a specific account or archive mailbox rather than letting theserver roam every account.
Limitations (v0.1)
- No send, move, delete, or attachment extraction
- No true threading — search by subject or sender to group a conversation
- Plain-text bodies only (Mail returns the text part; HTML markup is not preserved)
- macOS only
License
MIT