Apple Mail MCP
A read-only stdio MCP bridge to Apple Mail that exposes five tools for listing permitted accounts and mailboxes, searching mail, and reading message content.
Summary: search and read mail only — no send, delete, or move tool is present in the schema.
list_accounts— lists permitted accounts and their stable IDs (no input).list_mailboxes— returns nested mailbox paths (arrays of names) for a givenaccount_id.search_messages— case-insensitive subject/sender search in one mailbox (account_id+mailbox_path); an empty query lists messages. Supportsquery,unread_only,limit(≤100),scan_limit(≤1000), andoffsetpaging; results are not guaranteed chronological and a slow scan can stop early, sonext_offsetshould be followed even on empty pages.search_inboxes— runs the same search across every permitted account's Inbox in one call (query,unread_only,limit_per_account≤50,scan_limit≤1000), returning the first page per account; a failing account is reported in its own entry, and time-skipped accounts appear innot_reached.read_message— fetches one message bymessage_idwithin itsaccount_id/mailbox_path, with a bounded plain-text body viamax_chars(≤100,000).Every tool is annotated
readOnlyHint: true,destructiveHint: false,openWorldHint: false.Note: the README also documents
search_all_accounts,get_thread,get_statistics,list_attachments, and the opt-increate_draft, none of which appear in this server schema.
Integrates with Apple's Mail app on macOS, enabling listing accounts and mailboxes, searching messages across mailboxes or inboxes, reading bounded plain-text message content, and optionally creating visible drafts for review without sending.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Apple Mail MCP check my inboxes for any unread messages"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Apple Mail MCP
Specification — tool contracts, limits, and verification status.
A local stdio MCP server for Apple's Mail app on macOS, using the official MCP Python SDK and Mail's installed scripting dictionary. No email credentials or remote server required.
Tools
list_accounts: permitted account IDs, names, and email addresses.list_mailboxes: nested mailbox paths for an account.search_messages: paginated, case-insensitive subject/sender search in a mailbox. Optionalsearch_body(also matches content, slower per message), inclusivesince/untilISO-8601 bounds and a case-insensitive recipient address substring narrow the same search.search_inboxes: the same search (includingsearch_body,since,until) over the inbox of every permitted account in one call (first page from each;unread_only: truereviews what is new). Go deeper in one account withsearch_messagesand thenext_offsetit returns.search_all_accounts: the same cross-account search over each account'sinbox,sentordraftsmailbox, for questions like "what did I send since Monday" or "what is still in Drafts". Sent and Drafts are found through Mail's own special mailboxes, so provider-specific names (Sent Items,Sent Mail,Sent Messages) need no guessing.read_message: message metadata, To/Cc/Bcc recipients, date sent, and bounded plain-text content. Bcc is only known for messages sent from this Mac's accounts.get_thread: same-mailbox messages linked by RFCMessage-ID,References, andIn-Reply-To; summaries by default, with bounded optional bodies. Mail exposes no native conversation-membership property through JXA, so results identify the method and whether the bounded mailbox scan was complete. A completed header scan is not a guarantee of Mail UI's subject-based grouping or messages filed in another mailbox. Usecontinuation_scan_offsetwith the same anchor to retrieve later mailbox windows; combine them client-side because the server retains no cursor state. Optional bodies have a 250,000-character aggregate response cap.get_statistics: exact total/unread counts for one explicitly selected account and mailbox. Optional date-window counts are returned only when the bounded scan completes; otherwise the count is unavailable with a reason. No implicit all-account aggregation.list_attachments: paginated attachment metadata (name, MIME type, approximate size, downloaded state) for one message. Attachment bytes are not retrieved or written.create_draft: save a visible draft (To, optional Cc/Bcc) for review in Mail; never sends it. Off by default — see Configuration.
Use account IDs and mailbox path arrays returned by the listing tools. Search
scans at most 200 messages by default (up to 1,000), in Mail's native order, and
stops after about 30 seconds on a slow mailbox. Every search page says whether it is the
whole answer: partial: true with a stop_reason (result_limit, scan_limit or
time_budget) and a plain-language partial_reason means it is not, and scanned
counts the messages examined. Follow
next_offset until null, including on empty pages. Results are not guaranteed
newest-first. Mailbox changes between pages can cause skips or duplicates.
Account-scoped mailboxes only; local “On My Mac” mailboxes and attachment downloads
are not implemented. Date-only search bounds mean whole UTC calendar days; date-time
bounds must include a timezone. Recipient matching is a case-insensitive substring of
To/Cc/Bcc addresses. Search order is still Mail's native order, not necessarily newest-first.
No send, delete, move, or arbitrary scripting tool is exposed.
Related MCP server: readNdraft IMAP MCP
Setup
Requires macOS, configured Apple Mail accounts, and uv.
git clone https://github.com/jenyago/apple-mail-mcp.git
cd apple-mail-mcp
uv sync --lockedRebuild .venv with uv sync --locked on each Mac; do not copy it between machines.
Account IDs and addresses must be discovered again on the destination Mac.
Configuration
Environment variables, all optional:
Variable | Effect |
| Comma-separated email addresses. Only accounts owning one of them are visible or reachable; everything else is refused inside the JXA bridge. Unset = every account. Set but empty = the server refuses to start. |
|
|
| Audit log path. Default |
uv run python server.py --list-accounts prints every account (addresses, name, ID) to
your terminal, so you can pick addresses for APPLE_MAIL_ACCOUNTS without routing the
list through a model. It is a command-line option only, never an MCP tool.
The audit log is one JSON line per call, mode 0600: time, operation, account, mailbox
path, message ID, result count, outcome. It never records thread bodies, attachment names,
attachment bytes, search filters, or file paths. Blocked attempts are logged with
blocked_by_allowlist. It also never contains subjects, senders, message bodies, or
draft content.
Draft inputs are limited to 100 To, 100 Cc and 100 Bcc recipients, a 998-character subject, a 100,000-character body, and a 320-character sender value.
Security model
Email is attacker-controlled input. A message body can carry instructions aimed at the model reading it. This server cannot stop that; it limits what the server itself can do (read-only by default, account allowlist, no send/delete/move tool, audit log). What decides the outcome is what else the session can do after reading a hostile email: if it also has tools that send mail, post messages, fetch URLs, or a pre-approved shell, an injected instruction can use them without a prompt.
Read mail in a dedicated session that has no other tools — see the Claude Code recipe.
Do not register this server at user scope next to pre-approved outbound tools.
macOS grants Automation permission to the launching app, not to this server. Any process started from that app can drive Mail with
osascript, including sending. The "no send tool" property holds for the MCP interface only; an isolated session (no shell) is what makes it real.Mail bodies you read are sent to the model provider of your client. Use
APPLE_MAIL_ACCOUNTSto keep accounts you must not share out of reach.
Register with a client
Claude Code (recommended: isolated session)
The flags below start a session whose only tools are this server's. --restricted removes
the shell, code-running tools and WebFetch and ignores your settings files (so broad
allow rules do not apply); --strict-mcp-config skips every other MCP server, including
connectors; --tools "" removes the remaining built-in tools. Check /mcp on launch.
cat > "$HOME/.claude/mcp-apple-mail.json" <<EOF
{"mcpServers":{"apple-mail":{"type":"stdio","command":"$PWD/.venv/bin/python","args":["$PWD/server.py"],
"env":{"APPLE_MAIL_ACCOUNTS":"you@example.com"}}}}
EOF
alias claude-mail='claude --tools "" --restricted --strict-mcp-config --mcp-config "$HOME/.claude/mcp-apple-mail.json"'Run it from the cloned directory, and put the alias in your shell profile. Add
"APPLE_MAIL_ALLOW_DRAFTS":"1" to env only when you want drafts.
Registering with claude mcp add --scope user loads the server into every session,
which is only safe if none of them pre-approves outbound tools.
Codex
From the cloned directory:
codex mcp add apple-mail -- "$PWD/.venv/bin/python" "$PWD/server.py"Set APPLE_MAIL_ACCOUNTS in the server's environment as Codex's MCP configuration
allows (not verified here).
Grok Build
xAI's terminal agent runs local stdio servers. From the cloned directory:
grok mcp add apple-mail -e APPLE_MAIL_ACCOUNTS=you@example.com -- "$PWD/.venv/bin/python" "$PWD/server.py"Not verified here. Mail you read is sent to xAI, and a Grok configuration that auto-approves tool calls removes the prompt before each call. grok.com and the xAI API accept only remote (HTTP) MCP servers, which this local server deliberately is not.
Claude Desktop
Merge this entry into ~/Library/Application Support/Claude/claude_desktop_config.json,
preserving other settings. Replace both paths with absolute paths to your clone:
{
"mcpServers": {
"apple-mail": {
"command": "/absolute/path/apple-mail-mcp/.venv/bin/python",
"args": ["/absolute/path/apple-mail-mcp/server.py"],
"env": {"APPLE_MAIL_ACCOUNTS": "you@example.com"}
}
}
}Start a new session or restart the client if the tools do not appear. On first use, macOS may ask permission for the launching app to control Mail. Allow it under System Settings → Privacy & Security → Automation → Mail.
The server sends fixed JavaScript for Automation (JXA) code through stdin to
/usr/bin/osascript; user values are JSON data, never executable code. Calls
have a 45-second timeout. A draft timeout may leave a partial draft: inspect
Mail before retrying. Drafts may sync to the configured mail provider.
Email contents returned by the server are untrusted data, not instructions.
Verify
uv run python -m unittest -v
uv run python check_connection.py
uv run python check_connection.py --liveThe live check lists accounts without printing their contents, then proves against real
Mail that an allowlist filters list_accounts, blocks other accounts, and is logged, and
that search_inboxes reaches every permitted account inside its time budget, and that
search_all_accounts finds every account's Sent and Drafts mailbox.
It does not create drafts or send email. Unit tests cover script injection resistance,
permission errors, timeouts, recipient validation, allowlist parsing and fail-closed
behavior, draft gating, and that the audit log never contains message content. Protocol
tests check initialization, tool discovery, and parameter bounds.
Try it
“Use apple-mail to list my Mail accounts.”
“List the mailboxes for [account].”
“List five messages from its Inbox using apple-mail.”
“Read the first message from that result.”
“Use apple-mail to show my unread messages across all accounts.”
Optional, with drafts enabled: “Create a draft to [your own email] from [allowed sender] with subject MCP test and body This is a draft test. Do not send it.”
Verify the optional draft in Mail. No send tool is exposed. Message order is Mail's native order, not guaranteed newest-first.
License
Available Tools
5 toolslist_accountsARead-only
List permitted Apple Mail accounts and their stable IDs.
Inputs: no named parameters. Returns: result
list_mailboxesBRead-only
List account mailbox paths as arrays of names, including nested mailboxes.
Inputs: account_id (required). Returns: result
read_messageBRead-only
Read a message by ID within its account/mailbox, with bounded body length.
Inputs: account_id (required), message_id (required), mailbox_path (required), max_chars.
search_inboxesARead-only
Search the inbox of every permitted account in one call: the first page from each.
Empty query lists messages; unread_only=true reviews what is new. Each account is searched separately and a failing account is reported in its own entry without stopping the rest. Accounts skipped for lack of time are in not_reached. To go deeper in one account, call search_messages with that entry's account_id, mailbox_path and next_offset.
Inputs: query, scan_limit, unread_only, limit_per_account.
search_messagesBRead-only
Search subject/sender case-insensitively in one mailbox. Empty query lists messages.
Continue with next_offset even if this page has no matches: a scan also stops after about 30 seconds on slow mailboxes. Mail order is not guaranteed chronological; concurrent mailbox changes can affect pagination.
Inputs: account_id (required), mailbox_path (required), limit, query, +3 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
list_accounts and list_mailboxes clearly target different resources, and read_message is distinct. The only potential overlap is search_messages vs search_inboxes, but descriptions make the scope difference explicit (single mailbox vs all inboxes).
All tools follow a consistent snake_case verb_noun pattern (list_accounts, list_mailboxes, search_messages, search_inboxes, read_message). Verb choice is predictable and parallel.
Five tools is well-scoped for a read/search-oriented mail server. Each tool covers a distinct step (accounts, mailboxes, message search, inbox scan, message read) without redundancy.
The read/search surface is partially covered: accounts, mailboxes, search, and single-message read are present. However, there are no tools for sending/reply, deleting, moving, flagging read/unread, or retrieving attachments, which are notable gaps for an Apple Mail server.
Maintenance
Related MCP Connectors
Search and triage all your mailboxes; draft replies and campaigns; send only as you allow.
Read, search and draft email; nothing is sent until you confirm.
1Search Gmail and Outlook, save drafts, manage email rules, and view email stats.
Read, send, file and search email in any Gmail, Microsoft 365 or IMAP mailbox, plus its calendar.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables MCP clients to search, read, and inspect Apple Mail on macOS, including conversations and attachments. It can create new, reply, reply-all, or forward drafts, but cannot send or modify existing messages.MIT
- AlicenseAqualityBmaintenanceSafely searches, reads, flags, and drafts email through IMAP, with no send, delete, or move capabilities. Uses a local broker and OS credential store for secure authentication.16146 PyPIApache 2.0
- AlicenseAqualityBmaintenanceEnables AI assistants to read, search, send, and manage email on macOS through programmatic access to Apple Mail.29MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to read, search, organize, and draft email across all accounts configured in macOS Mail.app without passwords, API keys, or OAuth.4,105 npmMIT