Skip to main content
Glama
s-morgan-jeffries

apple-mail-mcp

Server Configuration

Describes the environment variables required to run the server.

NameRequiredDescriptionDefault

No arguments

Capabilities

Features and capabilities supported by this server

CapabilityDetails
tools
{
  "listChanged": true
}
logging
{}
prompts
{
  "listChanged": false
}
resources
{
  "subscribe": false,
  "listChanged": false
}
extensions
{
  "io.modelcontextprotocol/ui": {}
}
experimental
{}

Tools

Functions exposed to the LLM to take actions

NameDescription
list_accountsA

List all configured email accounts in Apple Mail.

Returns each account's id (UUID), display name, email addresses, account type, and enabled state. Account ids are stable across name changes; prefer them over names for identifying accounts.

Returns: Dictionary containing the accounts list.

Example: >>> list_accounts() {"success": True, "accounts": [ {"id": "B21B254B-...", "name": "Gmail", "email_addresses": ["me@gmail.com"], "account_type": "imap", "enabled": True}, ... ]}

list_rulesA

List all Mail.app rules (read-only).

Returns each rule's display name and enabled state. Rule names are NOT guaranteed unique — Mail allows duplicates — and rules have no stable id via AppleScript. This tool is read-only; mutation (enable/disable, create, delete) is tracked as a separate enhancement.

Returns: Dictionary containing the rules list.

Example: >>> list_rules() {"success": True, "rules": [ {"name": "Junk filter", "enabled": True}, {"name": "News From Apple", "enabled": False}, ... ], "count": 2}

delete_ruleA

Delete a Mail.app rule by 1-based positional index.

Destructive — requires user confirmation via MCP elicitation before running. Cannot be undone (Mail.app does not version rule history).

create_ruleA

Create a new Mail.app rule.

Rules with actions that can move, forward, or delete mail (delete / forward_to / move_to / copy_to) require user confirmation — a single create can install automation that auto-forwards or deletes all future mail (#222). Organizational-only rules (mark_read, mark_flagged, flag_color) are created without a prompt. Mail.app appends new rules to the end of the rule list, so the returned rule_index equals the new total rule count.

update_ruleA

Update an existing Mail.app rule (patch semantics).

Patch semantics: only fields you provide are changed. conditions and actions, when provided, REPLACE their respective structures wholesale (not merged).

Conditional confirmation: prompts the user via MCP elicitation when the patch touches conditions or match_logic (which alter matching scope), or replaces actions with a set that includes a dangerous action (move / forward / delete / copy). An actions patch limited to organizational flags (mark_read / mark_flagged / flag_color) skips the prompt, as do patches limited to enabled and/or name (trivially reversible). The enable/disable path replaces the removed set_rule_enabled tool: call update_rule(rule_index, enabled=True|False).

Refuses to update any rule whose existing actions include something outside the supported schema (run-AppleScript, redirect, reply text, play sound, custom highlight color); raises MailUnsupportedRuleActionError. Edit such rules in Mail.app's UI.

list_mailboxesB

List all mailboxes for an account.

search_messagesA

Search for messages matching criteria. Returns metadata-only rows.

Two corpus modes:

  • source=None (default): search the given account/mailbox using the IMAP/AppleScript SEARCH path. account is required.

  • source=[id1, id2, ...]: scope the search to the specific messages identified by the given ids. account/mailbox are ignored; the connector resolves each id self-sufficiently. The resulting message dicts are post-filtered by the other criteria (sender_contains, read_status, etc.) — full filter composition. The literal token "SELECTED" may appear in the list and is server-resolved at call time to Mail.app's current UI selection (zero-or-more messages). Mixed lists like ["SELECTED", "12345"] are valid. Missing ids drop out silently (partial-results).

For thread retrieval, call get_thread(message_id) to expand an anchor into thread member ids, then optionally pipe those ids into source=[ids] for filtered metadata browsing or into get_messages([ids]) for full bodies.

get_messagesA

Get full details of one or more messages, with bodies.

Returns a list of message dicts (possibly of length 0 or 1). Pair with search_messages (metadata-only) and get_thread (thread member ids) to fetch bodies for specific messages.

update_messageA

Update one or more messages: change read state, flag, and/or move, in one atomic call (#135).

Patch semantics — caller specifies only the fields to change. All specified mutations apply in a single AppleScript pass via the bulk-update helper. Replaces the previous mark_as_read, move_messages, and flag_message tools.

Order of operations (matters for IMAP): read-state and flag changes apply first (in source mailbox), then the move. IMAP requires the message to exist in the source folder for STORE before MOVE.

get_threadA

Return all messages in the thread containing the given message.

Looks up the anchor message by its id, then reconstructs the conversation via the connector's tiered IMAP threading dispatch (Tier 1 X-GM-THRID for Gmail, Tier 3 header-search BFS fallback) or the AppleScript path. Result rows are sorted by date_received ascending.

The returned ids can be piped into search_messages(source=[ids]) for filtered metadata or get_messages([ids]) for full bodies.

Known limitation: thread members whose subject was rewritten mid-conversation are missed on the AppleScript fallback path (subject prefilter tradeoff).

get_statisticsA

Aggregate inbox statistics over a mailbox and time window.

A read-only analytics roll-up computed from a single search_messages pass — message volume, read/unread/flagged counts, read ratio, and the top senders (by full address or domain). This is the consolidated inbox-stats tool; per-folder unread counts live on list_mailboxes and are not duplicated here.

The window defaults to the last ~30 days (received_within_hours=720); pass date_from/date_to for an explicit range. Stats are computed over at most scan_limit of the most recent messages in the window — window_fully_covered is False when the window held more than that, so the numbers are a recent-sample rather than a silent truncation.

save_attachmentsA

Save attachments from a message to a directory.

get_attachment_contentA

Read one attachment's content inline, without writing it to disk.

For "triage" workflows where you want to inspect an attachment (a text file, JSON, a small PDF) before deciding what to do with it — instead of save_attachments → read the file → clean up.

create_mailboxA

Create a new mailbox/folder.

update_mailboxA

Rename and/or re-parent (move) an existing mailbox.

Two delivery paths:

  • Rename only (new_name set, new_parent is None): AppleScript. Fast, no IMAP credentials needed.

  • Move (new_parent set; optionally combined with rename): IMAP RENAME. Requires IMAP credentials in Keychain (#73 opt-in flow) — returns error_type: "imap_required" when missing.

At least one of new_name / new_parent must be provided.

Refused (#164): operations targeting the bare [Gmail] parent or any [Gmail]/... child path return error_type: "unsupported_gmail_system_label". Applies to both the source name and the resulting destination (new_parent join). Gmail's IMAP server doesn't support normal RENAME semantics for these paths; user-created Gmail labels (Newsletters, etc.) behave normally.

delete_mailboxA

Delete a mailbox via IMAP.

Mail.app's AppleScript dictionary doesn't expose a working delete primitive for mailboxes, so this operation goes through IMAP. Requires IMAP credentials in Keychain (#73 opt-in flow) — returns error_type: "imap_required" when missing.

Always elicits user confirmation (destructive). By default refuses non-empty mailboxes to prevent accidental data loss; pass delete_messages=True to cascade.

Refused (#164): targeting the bare [Gmail] parent or any [Gmail]/... child path returns error_type: "unsupported_gmail_system_label". Gmail's IMAP server doesn't support DELETE for these paths.

delete_messagesA

Delete messages (always moves to the account's Trash mailbox).

Destructive: gated behind user confirmation via MCP elicitation (issue #239), matching delete_rule / delete_mailbox / delete_template.

list_templatesA

List all stored email templates.

Templates live as files at ~/.apple_mail_mcp/templates/.md. Override the location with the APPLE_MAIL_MCP_HOME environment variable.

Returns: Dictionary with each template's name and subject (or null if no subject header is set).

get_templateA

Read a single template by name.

save_templateA

Create or overwrite a template.

delete_templateA

Delete a template by name.

Destructive — requires user confirmation via MCP elicitation before running.

render_templateA

Render a template into ready-to-send subject and body text.

No side effects — caller is responsible for passing the rendered text to create_draft or update_draft (with send_now=True when ready to send).

With message_id, the original sender's display name and email, the original subject, and today's date are auto-populated as recipient_name, recipient_email, original_subject, and today. Without message_id, only today is auto-filled. User-supplied vars always override auto-fills on conflict.

create_draftA

Create a draft (fresh, reply, or forward). Optionally send immediately.

Mail.app's actual primitive is the draft — every outgoing message is a draft until sent. This tool lets callers create one, optionally seeded from an existing message (reply or forward), and either save it for later or send it now.

update_draftA

Update an existing draft. Implemented as delete-and-recreate.

Returns a NEW draft_id — Mail.app forbids mutating saved drafts, so update is implemented by reading the draft's current state, deleting it, and creating a new draft with the merged fields. Threading headers (for reply seeds) and forward anchor are preserved via persisted seed metadata.

Field merge semantics: any non-None argument overrides the existing value. None keeps the existing value. attachment_paths=None PRESERVES existing attachments (extracted via Mail's save command); [] explicitly clears them; a list replaces.

For drafts created externally (not via create_draft), seed recovery falls back to scanning Mail.app for the In-Reply-To header — this can be slow on large mailboxes (~30s+ per call). Forward seeds without disk state are misclassified as fresh; pass an explicit body if so.

delete_draftA

Delete (move to Trash) an existing draft.

Lifecycle endpoint for cancellation. Mail.app moves the message to the Deleted Messages mailbox; recovery is technically possible but Mail.app no longer treats trashed drafts as editable, so this is effectively a one-way discard. No elicitation (recoverable from Trash) and no rate limit (local operation).

Prompts

Interactive templates invoked by user choice

NameDescription

No prompts

Resources

Contextual data attached and managed by the client

NameDescription

No resources

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/s-morgan-jeffries/apple-mail-fast-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server