apple-mail-mcp
An MCP server for reading, searching, sending, and organizing Apple Mail on macOS.
Read, search, list, and thread emails with sender/subject/body/date/read/flag-color filters.
Send, draft, reply, forward, and mail-merge emails, including file or inline attachments.
Manage messages: mark read/unread, flag/unflag, move, delete, and batch operations.
Inspect raw headers or original RFC822 bytes and list/save/fetch attachments.
Manage mailboxes, rules, smart mailboxes, contacts, and reusable email templates.
Run diagnostics: health checks, doctor, mail stats, unread counts, and sync status.
Optionally use IMAP/SMTP for fast server-side search, clean MIME sending, and push notifications.
Allows AI assistants to read, send, search, and manage emails in Apple Mail on macOS.
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-mcpShow me my unread emails from today"
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 Server
A Model Context Protocol (MCP) server that enables AI assistants like Claude to read, send, search, and manage emails in Apple Mail on macOS.
Note: This is the npm/Node.js package — install with
npxornpm. There is an unrelated Python project of the same name on PyPI (imdinu/apple-mail-mcp) installed viapipx/uvx. If you're usinguvxand seeing acycloptsdependency error, you're looking for that project, not this one.
What is This?
This server acts as a bridge between AI assistants and Apple Mail. Once configured, you can ask Claude (or any MCP-compatible AI) to:
"Check my inbox for unread messages"
"Find emails from john@example.com"
"Send an email to the team about the meeting"
"Create a draft email for me to review"
"Reply to that message"
"Forward this to my colleague"
"Move old newsletters to the Archive folder"
The AI assistant communicates with this server, which then uses AppleScript to interact with the Mail app on your Mac. All data stays local on your machine.
Related MCP server: macos-mail-mcp
Quick Start
Using Claude Code (Easiest)
If you're using Claude Code (in Terminal or VS Code), just ask Claude to install it:
Install the sweetrb/apple-mail-mcp MCP server so you can help me manage my Apple MailClaude will handle the installation and configuration automatically.
Or register it deterministically in one command:
claude mcp add apple-mail -s user -- npx -y apple-mail-mcpUsing the Plugin Marketplace
Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
/plugin marketplace add sweetrb/apple-mail-mcp
/plugin install apple-mailThis method also installs a skill that teaches Claude when and how to use Apple Mail effectively.
Configuring IMAP/SMTP for a plugin install: a plugin install has no editable
envblock, so supply settings via the config file at~/Library/Application Support/apple-mail-mcp/config.json— Method B in the IMAP / SMTP Setup Guide. Passwords stay in the macOS Keychain; run thedoctortool to verify.
Using the Codex Marketplace
See Apple Mail in Codex for setup and diagnostics, and the guided setup and OAuth proposal for proposed future authorization support.
Install the same public marketplace in Codex:
codex plugin marketplace add sweetrb/apple-mail-mcp
codex plugin add apple-mail@apple-mail-mcpThe Codex package includes a portable codex/plugin.json, typed stdio server
configuration in codex/mcp.json, the Apple Mail skill, and the existing assets.
It retains codex/.codex-plugin/plugin.json and codex/.mcp.json for compatible
older hosts. Both layouts register the same apple-mail MCP identity through
npx -y apple-mail-mcp@<plugin version>. Release synchronization updates both
runtime pins and manifest metadata; tests and CI reject drift. An unreleased
candidate can be discovered locally, but its exact npm version must be
published before that pinned launcher can be installed from the registry.
For ongoing upstream updates, keep the Git marketplace configured. After a new release is published, refresh its catalog:
codex plugin marketplace upgrade apple-mail-mcpInstall the published plugin version from the refreshed marketplace, then start
a new Codex session. The refresh does not replace the runtime pin with @latest;
the plugin and its pinned npm runtime must refer to the same available release.
The standalone plugin archive bundles the server and requires only Node.js 20+ on the Mac; it does not fetch a runtime from npm. Download it from the release assets, or use the candidate artifact attached to a successful CI run. See Standalone plugin archive for the distribution format and local build command.
An imported archive or separately uploaded private account plugin is a snapshot, not a subscription to this repository's releases. Updating that copy requires a replacement package. When switching to the maintained marketplace distribution, preserve settings and approvals, verify Mail access and reply behavior, then remove the redundant installation. See Updates and migration.
For a personal build, you may explicitly supply a local PNG:
node scripts/package-codex-plugin.mjs /path/apple-mail.tar.gz --icon /path/icon.png.
The optional image must be square, noninterlaced, 48–2,048 pixels per side, and
at most 4 MiB. It is bundled inside the skill's assets directory and used for
the generated portable and compatibility manifests' light/dark logo and composer
icons. Source manifests, the runtime, and default artwork remain unchanged;
omitting --icon preserves the standard archive. No image is fetched automatically.
Supply only artwork you have permission to use and distribute; this option does
not grant rights to Apple or other third-party artwork, or guarantee that a host
will refresh an existing listing's cached presentation.
The compose tools accept composition: "native-editor" to insert new prose
through Mail's visible editor, leaving its signature and reply history intact.
This opt-in path uses existing macOS Accessibility and Automation permissions;
it does not require a separate computer-control plugin. It verifies the composer
and sender, preserves the clipboard, and stops without sending if insertion
cannot be verified. See native composition for
setup, examples, and verification limits. Existing SMTP and AppleScript behavior
is unchanged when the option is omitted.
Other Hosts (Hermes, Antigravity)
Two more hosts can run the same apple-mail MCP server (npx -y apple-mail-mcp):
Hermes Agent (NousResearch) — Hermes has no plugin/marketplace drop-in, so there is nothing in this repo to install from. Register the server with the CLI:
hermes mcp add apple-mail --command npx --args -y apple-mail-mcpOr add it to
~/.hermes/config.yamlby hand:mcp_servers: apple-mail: command: npx args: ["-y", "apple-mail-mcp"]Restart your Hermes session afterward so the tools load.
Antigravity (Google) — add the server entry from
.antigravity-plugin/mcp_config.jsonto~/.gemini/config/mcp_config.json(or via Antigravity's MCP settings).
Manual Installation
1. Install the server:
npm install -g apple-mail-mcp2. Add to Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"apple-mail": {
"command": "npx",
"args": ["apple-mail-mcp"]
}
}
}3. Restart Claude Desktop and start using natural language:
"Show me my unread emails"On first use, macOS will ask for permission to automate Mail.app. Click "OK" to allow.
Configuring email (IMAP & SMTP)
The server works out of the box over AppleScript with no configuration. Two opt-in power features take a one-time setup:
Fast IMAP reads — server-side search, counts, and large-mailbox handling that AppleScript is too slow for (it times out on big Gmail mailboxes).
Clean SMTP sending —
send-emailsubmits clean MIME directly, avoiding the macOS 15+ Mail.app<blockquote>wrapping that otherwise makes sent mail look quoted/indented like a reply.
Both are driven by non-secret APPLE_MAIL_MCP_* settings — supplied via an env
block or a config.json file (for hosts like Claude Desktop that strip env)
— with passwords kept in the macOS Keychain, never in config.
👉 IMAP / SMTP Setup Guide — step-by-step: app passwords,
Keychain, both config methods, multi-account, SMTP, verification with the doctor
tool, and troubleshooting. Verify any time by running the doctor tool.
Requirements
macOS - Apple Mail and AppleScript are macOS-only
Node.js 20+ - Required for the MCP server
Node.js 22.5+ and Full Disk Access - Required by
search-contactsonly. It reads the Contacts database directly through Node's built-innode:sqlite, which does not exist before 22.5. On an older runtime, or without Full Disk Access for the Node binary, it logs one line to stderr and returns an empty list rather than an error — so "no contacts found" can mean "cannot read Contacts". Every other tool works on Node 20+. See Node runtime & TCC permissions.Apple Mail - Must have at least one account configured (iCloud, Gmail, Exchange, etc.)
Features
Messages
Feature | Description |
List Messages | List messages with pagination, sender filter, date display |
Search Messages | Search by sender, subject, content, date range, read/flagged status — across all accounts |
Read Messages | Get full email content (plain text or HTML) |
Read Headers | Get a message's raw RFC 5322 headers — the author's |
Send Email | Compose and send new emails (attach by file path or inline base64 content; optional |
Send Serial Email | Mail merge — send personalized emails to a list of recipients with {{placeholder}} support |
Create Draft | Save emails to Drafts folder (attach by file path or inline base64 content; threaded drafts filed over IMAP) |
Reply | Reply to messages (with reply-all support and attachments) |
Forward | Forward messages to new recipients (with extra attachments) |
Get Thread | Group a conversation by normalized subject (across AppleScript or IMAP) |
Mark Read/Unread | Change read status (single or batch) |
Flag/Unflag | Flag or unflag messages (single or batch) |
Delete Messages | Move messages to trash (single or batch) |
Move Messages | Organize into mailboxes (single or batch) |
List Attachments | View attachment metadata (name, type, size) |
Save Attachment | Save attachments to disk |
Fetch Attachment | Get an attachment's bytes as base64 (no disk write) |
Read/list/get tools also return structured JSON (structuredContent) alongside the text, so agents can consume results without parsing prose.
Mailbox & Account Management
Feature | Description |
List Mailboxes | Show all folders with message/unread counts |
Create/Delete/Rename Mailbox | Full mailbox lifecycle management |
List Accounts | Show configured accounts |
Unread Count | Get unread counts per mailbox |
Rules, Contacts & Templates
Feature | Description |
List Rules | View all mail rules and their enabled status |
Enable/Disable Rules | Toggle mail rules on or off |
Create/Delete Rules | Create rules with conditions + actions, or delete by name |
Search Contacts | Look up contacts from Contacts.app by name |
Email Templates | Save, list, use, and delete reusable email templates (persisted to disk across restarts) |
Diagnostics
Feature | Description |
Health Check | Verify Mail.app connectivity |
Doctor | Diagnose Mail permission, account state, and each IMAP/SMTP backend with actionable messages |
Statistics | Message and unread counts per account, recently received stats |
Sync Status | Check if Mail.app is actively syncing |
Effect reconciliation | Every delete/move reports what it actually did to the mailbox ( |
MCP resources & prompts
Resources expose read-only context the client can attach without a tool call:
mail://accounts, mail://templates, and mail://mailboxes/{account}. Prompts
package common workflows: triage-inbox, compose-reply, weekly-summary.
Tool Reference
This section documents all available tools. AI agents should use these tool names and parameters exactly as specified.
Message Operations
search-messages
Search for messages matching criteria. Searches all accounts by default.
Parameter | Type | Required | Description |
| string | No | Text to search in subject/sender |
| string | No | Text to search in the message body. IMAP backend only (server-side |
| string | No | Filter by sender email address |
| string | No | Filter by subject line |
| string | No | Mailbox to search in (omit to search all mailboxes) |
| string | No | Account to search in (omit to search all accounts) |
| boolean | No | Filter by read status |
| boolean | No | Filter by flagged status |
| string | No | Filter to messages flagged with exactly this color — see |
| string | No | Start date filter (e.g., "January 1, 2026") |
| string | No | End date filter (e.g., "March 1, 2026") |
| number | No | Max results, 1–500 (default: 50) |
| number | No | Newest matches to skip, ≥ 0 (for pagination). IMAP accounts only; without a |
Returns: List of matching messages with ID, date, subject, sender, read state,
and flagColor when flagged with an explicit color.
Flag color as a label (#286). Every row from both backends carries
flagColor (the palette name — red, orange, yellow, green, blue,
purple, gray) whenever the message is flagged with an explicit color;
omitted for an unflagged message or one flagged with Mail's plain default (no
color chosen). Combined with the flagColor filter above, a colored flag
works as a label across mailboxes: flag a set of messages with
flag-message/batch-flag-messages, then find exactly that set later with
search-messages, without moving anything into a dedicated folder. Known
limitation, inherent to Apple's $MailFlagBit0/1/2 IMAP wire encoding, not
specific to this server: red (palette index 0) sets no keyword bits, so
over IMAP it is indistinguishable from a plain colorless flag, both when
reading flagColor back and when filtering on it. AppleScript-backed
accounts are unaffected (Mail's own flag index property is 0 for red, -1
for none).
⚠️ Row dates. Since 2.19.7 every row — on both the IMAP and AppleScript
backends — carries both dateSent (the message's Date: header) and
dateReceived (mailbox arrival — IMAP INTERNALDATE or Mail's date received).
They differ legitimately by transit time; when they differ by years, the
mailbox was migrated or re-imported and the arrival timestamp was reset — trust
dateSent for chronology (#224).
dateSent is omitted (not invented) when the message carries no parseable
Date: header, or when it is more than 7 days later than dateReceived — Mail
substitutes a timestamp of its own for a Date: header it cannot parse
(#234).
Large mailboxes & partial results. Apple Mail's AppleScript bridge cannot search very large IMAP/Gmail mailboxes (tens of thousands of messages) before the Apple Event times out — empirically even reading the newest 20 messages of a 44k-message mailbox takes ~45s. To avoid burning minutes only to return a misleading empty result, an unscoped (all-mailboxes) search skips mailboxes whose message count exceeds a threshold (default 5000), enforces a per-account time budget, and reports anything it skipped or that timed out rather than silently returning nothing. When coverage is incomplete the result includes an explicit warning, e.g.:
⚠️ Partial results — this is NOT a confirmed "no such mail":
- skipped mailbox(es) too large to search via AppleScript: Gmail / All Mail (44287) — scope the search with `mailbox` + a `dateFrom`/`dateTo` window to target themTo search inside a large mailbox, scope the call with mailbox (and ideally a
dateFrom/dateTo window). Tune or disable the skip threshold with the
APPLE_MAIL_MAX_SEARCH_MAILBOX environment variable (default 5000; set to 0
to disable the guard and attempt every mailbox regardless of size).
(#24)
Envelope Index search (opt-in, read-only). For accounts with no IMAP config,
set APPLE_MAIL_MCP_ENVELOPE_INDEX=1 to answer header searches (query, from,
subject, read/flagged state, dates, mailbox) from Mail's own on-disk Envelope Index
instead of an AppleScript scan, so large mailboxes are searched completely rather than
skipped. It needs Full Disk Access for the server's Node runtime and opens the database
read-only. Anything the index can't answer completely (a flagColor filter, an unknown
mailbox, no Full Disk Access, an unrecognised schema, or a corrupt or locked database)
falls back to the AppleScript scan. The backend field below reports which one answered.
(#342)
Body search for AppleScript-only accounts (opt-in local body index). Without IMAP,
search-messages can't search message bodies (AppleScript would have to pull every
body through Mail), so a body query reports those accounts as not searched. Set
APPLE_MAIL_MCP_BODY_INDEX=1 together with APPLE_MAIL_MCP_ENVELOPE_INDEX=1 to
search them through a local full-text index of the .emlx files Mail already keeps
on disk. (#349)
What it reads, and what it needs. Message files under
~/Library/Mail, read only; the mail store is never written. Like the Envelope Index backend, it needs Full Disk Access for the app that runs the server (its Node runtime).doctorreports when it can't read the store, with the same wording.Where the index lives.
~/Library/Application Support/apple-mail-mcp/body-index.db(plus-wal,-shmand.writer-locknext to it); setAPPLE_MAIL_MCP_BODY_INDEX_FILEto move it. The directory is created0700and the files0600. The index is contentless: it stores the search tokens, not a copy of any message, but the tokens still reveal a lot about your mail — treat the file as sensitive. Expect roughly 70 MB per 22,000 messages.Deleting it. Turn the flag off (or stop the server), then delete the files:
rm -f ~/Library/Application\ Support/apple-mail-mcp/body-index.db*It is only a cache; with the flag on it is rebuilt from scratch on the next start.
First build and upkeep. One server instance (the first to start) builds and updates the index in the background in small slices; other instances — one per conversation in some hosts — search it read-only and take over if that instance exits. The walking and parsing run in a short-lived child process (the same
build/index.json the same Node runtime, so the same Full Disk Access grant applies), which exits as soon as it has caught up: the server stays responsive during a build, and the memory parsing takes goes back to the system instead of staying with the server. If that process can't start or can't read the store, the server builds on its own main thread instead anddoctorsays why (and tries a build process again an hour later). A build process that stops responding for a minute is stopped and restarted. New mail is picked up through file-system events within seconds, with a full check every 10 minutes as a safety net. A build of ~22,000 messages took about 40 s on an Apple-silicon Mac.Coverage is always reported. A body search never presents an incomplete index as "no matches". Messages in the searched scope that it couldn't read in full are counted, and the result is
partial: truewith anotSearchedMailboxesentry such asGoogle / * (1,234 messages not downloaded, 12 not indexed yet). Not downloaded means Mail has only the headers (common for older mail on IMAP/Exchange accounts set to download recent messages only); open those messages in Mail, or change the account's download setting, to make them searchable. Messages over 25 MB are not read.Requirements. Node 22.5 or later (for
node:sqlite); on older runtimes the feature turns itself off and says why in the logs anddoctor.
Coverage diagnostics (structured fields). The warning above is prose for a
human reader; the same information is also returned as structured fields on
search-messages and list-messages, so a caller can tell "nothing matched"
apart from "I did not look everywhere" without parsing the text:
Field | Type | Meaning |
| boolean | Coverage was incomplete — the result is not a confirmed "no such mail". True whenever any field below is non-empty. |
| string[] | Mailboxes never scanned because their message count exceeded |
| string[] | Mailboxes that were reached but timed out or errored mid-scan, formatted |
| string[] | Accounts whose whole-account AppleScript was killed by the per-account time budget — nothing from that account was searched. |
| string[] | IMAP-backend mailboxes that errored. These are merged into |
| object | The IMAP server's own error text for each entry in |
| object[] | IMAP messages that belong on the page but whose |
| string[] | IMAP mailboxes the search did not finish because the call was cancelled ( |
| string |
|
| string |
|
| object | Present only when |
Deadline and cancellation (#276). Tool
calls run one at a time (so they cannot race into Mail.app), which means a long search holds every
later call behind it. An IMAP search-messages therefore stops at
APPLE_MAIL_MCP_SEARCH_DEADLINE_MS (default 45000, minimum 2000, measured from when the request
arrived — time queued behind other calls counts) and returns what it found plus timedOutMailboxes,
and it stops the same way when the client cancels the request. The mailbox being searched at that
moment is abandoned by closing its IMAP connection (the next call reconnects), so the queue is
released promptly rather than after a server-side SEARCH that can take a minute and a half on a
very large mailbox. A request cancelled while still queued is skipped. Keep the deadline below your
client's own request timeout.
Treat a non-empty skippedLargeMailboxes as actionable rather than
informational: re-run scoped to the named mailbox with a dateFrom/dateTo
window, or configure the IMAP backend, which searches
those mailboxes server-side. The list fields are optional and are omitted (or
empty) when coverage was complete.
A row the IMAP backend could only read with a reduced item set (its full
BODYSTRUCTURE, or its whole FETCH response, was unparseable — e.g. a message
forwarded as an attachment many levels deep) is still returned, with a
metadataIncomplete note saying what was missing; its hasAttachments is then
inferred from the top-level Content-Type.
get-message
Get the full content of a message.
Parameter | Type | Required | Description |
| string | Yes | Message ID |
| boolean | No | Return HTML source instead of plain text |
| string | No | Mailbox holding the message (e.g. |
| string | No | Account holding the message. Pair with |
Returns: Subject line and message body (plain text by default, HTML if preferHtml is true and HTML content is available). structuredContent also carries rfcMessageId and, since 2.19.0, two dates: dateSent (the message's Date: header — Mail's date sent) and dateReceived (arrival in the mailbox — Mail's date received / IMAP INTERNALDATE). They differ legitimately by transit time; when they differ by years, the mailbox was migrated or re-imported and the arrival timestamp was reset — trust dateSent for chronology (#224). Since 2.19.6 dateSent is omitted when it is more than 7 days later than dateReceived: a message cannot be sent after it arrived, and Mail.app substitutes a timestamp of its own for a Date: header it cannot parse (#234). isHtml reports what was actually returned — a message with no text/plain part returns its HTML part with isHtml: true. Bodies are decoded by each part's declared charset, falling back to windows-1252 for bytes that are not valid UTF-8.
Numeric ids — how the message is found (#270): with account + mailbox the read opens that one mailbox and stops there. Otherwise it opens the mailbox the id was last listed or searched from. A stale remembered location is forgotten before a bounded scan, but an ambiguous remembered account or mailbox stops without a scan. That cross-mailbox scan skips any mailbox holding more than APPLE_MAIL_MAX_BYID_SCAN_MAILBOX messages (default 50000; 0 disables the guard) and stops after ~9s, because Mail's whose id is probe costs seconds per hundred thousand messages; a miss then names the mailboxes it skipped and, if it stopped early, the ones it never reached. A mailbox whose size count does not answer within 3s is remembered — on disk, for 7 days, across restarts (APPLE_MAIL_MCP_STALLED_MAILBOXES_FILE, default ~/Library/Application Support/apple-mail-mcp/stalled-mailboxes.json; delete it to forget) — and never counted again, since Mail.app keeps working on an abandoned count and everything after it would queue; the scan stops the moment such a count times out rather than queue behind it. If Mail.app is not running these reads say so instead of launching it. Pass account + mailbox to read from one of those, or use the message's imap: id, which never scans. The same resolution backs get-message-headers, get-thread's seed lookup, and the original-message read of reply-to-message / forward-message and the attachment tools.
Large messages / attachments: reading a full message routes through
osascript, whose captured output buffer defaults to 64 MB. Override it with theAPPLE_MAIL_MCP_MAX_BUFFERenvironment variable (in bytes) if you work with messages whose raw MIME (e.g. a large embedded attachment) exceeds that — a value below the message size makes the read fail with a buffer-overflow error rather than truncating (#27).
get-message-headers
Return a message's raw RFC 5322 header block — without fetching the body or any attachment — plus the parsed fields chronological and threading work needs. Added in 2.19.0 for mailboxes whose arrival timestamps were reset by a migration (#224): the Date: header is the author's send time and survives such moves; INTERNALDATE / date received does not.
Parameter | Type | Required | Description |
| string | Yes | Message ID (numeric or |
| string | No | Mailbox holding the message. With |
| string | No | Account holding the message. Pair with |
Returns: The raw header block as text. structuredContent carries raw, every header as ordered headers[] ({name, value}, folded lines joined, duplicates such as Received: kept in wire order, values left RFC 2047-encoded), headerCount, and the decoded key fields: date (ISO 8601, from the Date: header), dateHeader (verbatim), dateReceived (mailbox arrival time — IMAP INTERNALDATE or Mail's date received), messageId, subject, from, to, cc, replyTo, inReplyTo, references[] and received[] (first entry = last hop). Fields the message does not carry are omitted. backend says which backend read the block ("imap" or "applescript"), and warnings[] appears when a malformed block was repaired (#234).
Backends (get-message-headers): an imap: id reads the first 64 KiB of BODY.PEEK[] over IMAP and cuts the header block out of it (cheap even for a 20 MB message). That is deliberate: iCloud rewrites 8-bit header bytes to * in ENVELOPE and BODY[HEADER], and only BODY[] returns them as stored; each line is decoded as UTF-8, falling back to windows-1252, so a raw latin-1 display name survives. A header block larger than the window falls back to BODY.PEEK[HEADER]. A numeric id reads Mail's all headers property over AppleScript, with the same mailbox-scoped fast path as get-message. ⚠️ That property is Mail's own rendering, not the stored bytes: for a Date: value Mail cannot parse it drops the value and joins the next header onto the name (Date: Subject: …). The tool splits that back apart, reports the date as absent rather than as a Subject string, and says so in warnings[]; the imap: id for the same message has the real Date:.
get-message-rfc822
Acquire a message's complete original RFC 822 bytes exactly as the IMAP server stores them, together with the IMAP identity that links the copy back to its source (#244). Built for archival, forensic review and evidence preservation: nothing is decoded, charset-converted, line-ending-normalized or MIME-re-serialized, and the SHA-256 is computed over exactly the bytes returned. IMAP-only — it needs an imap: id, i.e. an IMAP-configured account (docs/IMAP-SETUP.md). There is no AppleScript path on purpose: Mail's bridge exposes its own rendering of a message, not the stored bytes.
Parameter | Type | Required | Description |
| string | Yes | An |
| string | No | Directory inside the configured allowed roots. Writes the bytes to a new |
| string | No | File name to use with |
| number | No | Refuse — never truncate — a message larger than this many raw bytes. Default and inline maximum 6 MiB ( |
Returns: a one-line summary as text. structuredContent carries the acquisition record: account, mailbox, uid, uidValidity (decimal string), internalDate (ISO 8601 INTERNALDATE — arrival, not the Date: header), flags[], size (the server's RFC822.SIZE), bytes (the count actually acquired and hashed), sha256 (hex, over exactly those bytes), messageId (from ENVELOPE, for convenience — the authoritative copy is in the bytes), readMethod (the IMAP commands issued), backend: "imap" and warnings[] — plus contentBase64 in inline mode or savedPath in file mode. The base64 is carried only in structuredContent; the text block never repeats it.
Read-only by construction. The mailbox is opened with EXAMINE and the body fetched with BODY.PEEK[], so \Seen is not set and no STORE, COPY, MOVE, APPEND or EXPUNGE is issued. No Mail.app, AppleScript or osascript is involved. The only side effect is the optional file, created with wx (an existing file or symlink is refused, never overwritten) and mode 0600, and only inside the allowed roots — the same policy as save-attachment.
Sizes. The inline ceiling exists because the MCP stdio transport drops any single message over 10 MB with no error text: 6 MiB of raw bytes is 8 MiB of base64. maxBytes is a ceiling, not a cut — an oversize message is refused with its RFC822.SIZE, so you can choose between raising maxBytes (inline, up to 6 MiB) and savePath (up to 25 MiB). warnings[] reports an RFC822.SIZE that disagrees with the byte count actually received (the hash still covers what was received) and a server that reported no UIDVALIDITY.
Verifying a derivative. shasum -a 256 file.eml must equal sha256. account + mailbox + uidValidity + uid is the durable pointer back to the source message — a UID on its own is meaningful only for one UIDVALIDITY, which the server may change (typically on a mailbox rebuild).
list-messages
List messages in a mailbox.
Parameter | Type | Required | Description |
| string | No | Mailbox name (omit to list from all mailboxes) |
| string | No | Account name |
| number | No | Max messages, 1–500 (default: 50) |
| number | No | Number of messages to skip, ≥ 0 (for pagination) |
| string | No | Filter by sender email address or name |
| boolean | No | Only show unread messages |
Returns: List of messages with ID, date, subject, sender, and flagColor
when flagged with an explicit color (see the flag-color note under
search-messages, which also supports filtering by it).
⚠️ Row dates. Since 2.19.7 every row — on both the IMAP and
AppleScript backends — carries both dateSent (the message's Date:
header) and dateReceived (mailbox arrival — IMAP INTERNALDATE, falling back
to the header date if the server withholds it; Mail's date received on the
AppleScript path). They differ legitimately by transit time; when they differ
by years, the mailbox was migrated or re-imported and the arrival timestamp
was reset — trust dateSent for chronology
(#224). Since 2.19.6 the
IMAP dateSent is recovered even when the server's own parse of the Date:
header failed (legacy locale dates such as jue ago 30 13:55:12 2007); both
backends omit dateSent (rather than reporting it) when it is more than 7 days
later than dateReceived (#234).
Before 2.19.7 the AppleScript path carried only dateReceived. Sort order keys
on the header date on both backends, which is the stable one.
send-email
Send a new email immediately.
⚠️ Safety: Sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling.
Parameter | Type | Required | Description |
| string[] | Yes | Recipient addresses |
| string | Yes | Email subject |
| string | Yes | Email body (plain text) |
| string[] | No | CC recipients |
| string[] | No | BCC recipients |
| string | No | Mail.app account name or exact sender address (AppleScript path: resolved to one address of an enabled account and verified against Mail's |
| (string | {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots (e.g., |
|
| No | Send transport. If omitted, SMTP is used automatically when configured (otherwise AppleScript), unless |
| string | No | SMTP only: |
| string | No | SMTP only (2.20.0): thread the message as a reply — the parent's |
| string[] | No | SMTP only (2.20.0): the |
Example:
{
"to": ["colleague@company.com"],
"subject": "Meeting Tomorrow",
"body": "Hi, just confirming our meeting at 2pm tomorrow.",
"account": "Work",
"attachments": ["/Users/me/Documents/agenda.pdf"]
}Threading headers (inReplyTo / references)
send-email and create-draft accept caller-built threading headers (2.20.0, #267) for
callers that compose the recipients and quoted text themselves. Prefer
reply-to-message — it derives recipients, In-Reply-To/References and the quote
from the original, and (since 2.20.0) takes attachments too.
Each value must be a single RFC 5322 msg-id token with angle brackets (
<left@right>, no whitespace); anything else is rejected by validation.The headers are always emitted as plain
<id>tokens — never RFC 2047-encoded, even for Outlook/Exchange IDs longer than 76 characters (a long header is folded at whitespace between tokens, which is legal and does not alter the id).Mail.app's AppleScript cannot set these headers, so they are never silently dropped:
send-emailrefuses them unless the SMTP transport is in use, andcreate-draftfiles a threaded draft over IMAP instead of through Mail.app (below).
SMTP transport
On macOS 15+ (Sequoia/Tahoe), Mail.app wraps any AppleScript-injected body in
<blockquote type="cite"> under the Apple-Mail-URLShareWrapperClass template,
so emails sent through the default applescript transport render to recipients
as if they were quoted/forwarded (Apple radar FB11734014, open since
Ventura). The SMTP transport bypasses Mail.app entirely and submits clean MIME
directly. Once SMTP is configured, send-email uses it automatically (no
need to pass transport per call); pass transport: "applescript" to force the
Mail.app path.
Two differences to know when SMTP is auto-preferred:
Sent-folder copy is best-effort over IMAP. SMTP submission itself never touches IMAP, so since 2.18.0 the server files the sent message into the Sent mailbox of whichever configured IMAP account's login matches the SMTP identity (
APPLE_MAIL_MCP_IMAP_*mirroringAPPLE_MAIL_MCP_SMTP_*), flagged\Seen. The copy carries the sameMessage-IDthat was delivered, so replies thread against it correctly, and it keeps theBccheader the recipients never see — your own archive is exactly where that belongs. Results reportsentCopy: trueorsentCopy: falsewithsentCopyError; the field is absent when no configured IMAP account matches the SMTP identity, which is a skip, not a failure. A copy that fails never fails the send — the mail has already gone. Before appending, the Sent mailbox is searched for thatMessage-ID; if the submission server already filed it (Gmail's SMTP always does — the server waits up to ~3 s for it there), nothing is appended and the result addssentCopyAlreadyFiled: truealongsidesentCopy: true. Usetransport: "applescript"if you want Mail.app itself to file the copy.accountis a From override, not account selection. Over SMTP,accountis used as the From address only when it is an email address; a Mail.app account label (e.g."Work") can't select an account over SMTP, so a call that passes one is left on the AppleScript path automatically. To force account selection, passtransport: "applescript"explicitly. For sender safety, an email-form override must match the SMTP login user, the configuredAPPLE_MAIL_MCP_SMTP_FROM, or an address listed in the comma-separatedAPPLE_MAIL_MCP_SMTP_ALLOWED_FROM; any other From address is rejected before connecting.
Both plain-text and HTML bodies are supported — over SMTP an HTML body (CLI
--html-body-file) is sent as multipart/alternative with the plain-text
fallback.
Configure SMTP via environment variables on the MCP server. The password is read from the macOS Keychain by default, so no secret goes in config:
Non-implicit-TLS SMTP connections fail closed if STARTTLS is unavailable.
APPLE_MAIL_MCP_SMTP_ALLOW_PLAINTEXT=1 is a deliberate escape hatch for a
trusted isolated server or test fixture; it disables the upgrade requirement and
can expose credentials and message content. The server emits a warning when it
is used. Keep the default unset.
Variable | Required | Default | Description |
| Yes | — | SMTP server hostname (e.g. |
| Yes | — | SMTP username |
| No |
| SMTP port |
| No |
|
|
| No |
| Set |
| No | = user | From address |
| No | — | Comma-separated sender aliases permitted as per-message From overrides |
| No | — | Password (if set, used instead of the Keychain) |
| No | = host | Keychain item service/server name |
| No | = user | Keychain item account |
Store the password in the Keychain once (an app-specific password for Gmail/
iCloud). A generic-password item with an explicit service name keeps it from
colliding with the system mail account password, and matches
APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE:
# Fastmail (Keychain service defaults to the host)
security add-internet-password -s smtp.fastmail.com -a you@example.com -w
# Gmail / Google Workspace, using a dedicated Keychain service name:
# APPLE_MAIL_MCP_SMTP_HOST=smtp.gmail.com
# APPLE_MAIL_MCP_SMTP_USER=you@gmail.com
# APPLE_MAIL_MCP_SMTP_KEYCHAIN_SERVICE=apple-mail-mcp-smtp
security add-generic-password -s apple-mail-mcp-smtp -a you@gmail.com -wOnce the env vars are set, a plain send-email (no transport) already goes
out clean:
{
"to": ["colleague@company.com"],
"subject": "Standings",
"body": "Plain body — no blockquote wrapping."
}apple-mail-send CLI (no MCP server required)
The package also installs an apple-mail-send binary — a standalone CLI over the
same SMTP path, for cron jobs, scheduled tasks, and scripts that can't run an MCP
session. It reads the identical APPLE_MAIL_MCP_SMTP_* env + Keychain config:
apple-mail-send \
--from you@example.com --to colleague@company.com \
--subject "Standings" --body-file /tmp/body.txt \
[--html-body-file /tmp/body.html] [--attach /tmp/report.pdf]Repeatable --to/--cc/--bcc/--attach; an --html-body-file is sent as a
multipart/alternative alongside the plain --body-file. Exit codes follow
sysexits.h: 0 success, 64 usage error, 66 unreadable body file, 78
SMTP not configured.
IMAP backend — opt-in
📘 For step-by-step setup (app passwords, Keychain, config methods, multi-account, upgrading, troubleshooting), see the IMAP / SMTP Setup Guide. The summary below is the reference; the guide is the walkthrough.
AppleScript runs search/list predicates client-side over the Apple Event
bridge, which is slow and can time out (false-empty) on large Gmail/IMAP
mailboxes (see #24), and
its delete/rename mailbox and draft handlers don't work on server-side
accounts at all (#42). When an account is configured for IMAP, the MCP routes to
a server-side IMAP backend (#43)
that is fast and correct on exactly those mailboxes. This is opt-in and
additive: any account without IMAP configured behaves exactly as before
(AppleScript).
What routes to IMAP when an account is IMAP-configured:
Read:
search-messages,list-messages(server-sideSEARCH, typically sub-second), andget-message.Folder ops:
create-mailbox,rename-mailbox,delete-mailbox— IMAP'sCREATE/RENAME/DELETEsucceed on the iCloud/Gmail/Workspace/Exchange mailboxes Mail.app's AppleScript bridge can't touch (#42).Message mutations:
mark-as-read/unread,flag-message/unflag-message,move-message,delete-message.Batch mutations (2.1):
batch-mark-as-read/unread,batch-flag/unflag-messages,batch-move-messages,batch-delete-messages—imap:ids are grouped by mailbox and applied as a singleUID STORE/UID MOVE; numeric ids in the same batch still use AppleScript.Counts & stats (2.1):
get-unread-countandlist-mailboxesuseSTATUS;get-mail-statsusesSTATUS+SEARCH SINCE— authoritative and fast even on huge mailboxes. As of v2.6.0 these prefer IMAP whenever it's configured (see Read routing below), merging across accounts when noaccountis given.Attachments (2.1):
list-attachments,save-attachment,fetch-attachmentuseBODYSTRUCTURE+FETCH BODY[part]forimap:ids — faster and able to see MIME-embedded attachments AppleScript misses.Threading (2.1):
get-threadlinks a conversation viaReferences/Message-ID(HEADER SEARCH) for animap:seed, falling back to subject grouping otherwise.
Message ids are backend-tagged. The IMAP read path emits self-describing ids
of the form imap:<token> (the token encodes the account, mailbox path, and
UID). Pass that id back to get-message, a message mutation, a batch op, or the
attachment/thread tools and it routes to IMAP automatically; bare numeric ids
continue to use AppleScript. So an agent never has to know which backend a
message came from — the id carries it.
Read routing (v2.6.0): reads PREFER direct IMAP whenever IMAP is configured.
The read tools — search-messages, get-thread, list-messages,
list-mailboxes, get-unread-count, get-mail-stats — now go to IMAP whenever
any APPLE_MAIL_MCP_IMAP_* account is configured, not just when an explicit
matching account is passed. There are three cases:
Explicit IMAP account — single-account IMAP (fast server-side path).
Explicit non-IMAP account — AppleScript (that account isn't on IMAP).
No
accountgiven — merge across all accounts: the query fans out over every configured IMAP account, and AppleScript runs only for the accounts no IMAP config covers (the account list is partitioned — accounts already served by IMAP are not re-scanned via AppleScript). If every Mail account is IMAP-configured, AppleScript is skipped entirely. The results are merged so no account is dropped. Message lists still de-duplicate as a safety net (preferring the IMAP copy, which carries the round-trippableimap:id) and sort newest-first; count tools (get-unread-count,get-mail-stats) count each account via exactly one backend so a coverage mismatch can never double- (or under-) count.An omitted mailbox on
search-messagessearches the account's entire store, not just one default folder (v2.17.1, #199). Per account, the fan-out uses the server-advertised RFC 6154\Allmailbox when one exists (Gmail/Workspace's[Gmail]/All Mail); otherwise it searches every selectable mailbox the server lists (iCloud, generic IMAP), merges the matches, de-duplicates by Message-ID, and sorts newest-first before applyinglimit/offset. A mailbox that can't be selected or searched is named in the result instead of silently dropping coverage. Scanning every mailbox on a large, deeply-nested account costs oneSEARCH+ a boundedFETCHper mailbox over the pooled IMAP connection — pin amailboxto skip the fan-out when you already know where to look.list-messages(no query) still defaults an omitted mailbox toINBOXon every provider — only unscoped search scans the whole account.
If IMAP is not configured at all, every read behaves exactly as before
(pure AppleScript). The three mailbox-write ops (create-mailbox,
delete-mailbox, rename-mailbox) remain conservative — they route to IMAP only
for an explicitly-named IMAP account, never on an omitted account.
Variable | Required | Default | Description |
| Yes | — | Login address; setting it enables IMAP |
| No | = user | Mail account name to match for routing |
| No |
| IMAP server hostname |
| No |
| IMAP port (993 = implicit TLS) |
| No |
| Set |
| No | — | Password (if set, used instead of the Keychain) |
| No | — | Keychain item service/server name |
| No | = user | Keychain item account |
| No | — | JSON array of additional IMAP accounts for multi-account setups (see below) |
| No |
| Set |
| No |
| Idle timeout (ms) before a pooled IMAP connection is closed ( |
| No |
| Per-account wall-clock budget for |
| No |
| Overall wall-clock deadline for one |
| No |
| Overall wall-clock deadline for one IMAP |
Multiple IMAP accounts (C2): set APPLE_MAIL_MCP_IMAP_ACCOUNTS to a JSON array, e.g.
[{"account":"Work","user":"me@co.com","host":"imap.co.com","keychainService":"imap.co.com"}].
Each entry accepts account, user, host, port, password, keychainService,
keychainAccount. Calls route to the account matching their account argument (or the
decoded imap: id), and each account keeps its own pooled connection.
Non-implicit-TLS IMAP connections require STARTTLS and fail closed when the server
does not offer a usable upgrade. APPLE_MAIL_MCP_IMAP_ALLOW_PLAINTEXT=1 is a
deliberate escape hatch for a trusted isolated server or test fixture; it disables
the upgrade requirement and can expose credentials and message content. Keep the
default unset.
As with SMTP, the password is read from the macOS Keychain by default (use
an app-specific password for Gmail/Workspace/iCloud), so no secret goes in
config. Gmail label semantics: common names (All Mail, Sent, Trash,
Spam, Important, …) map to their [Gmail]/… IMAP paths automatically.
Note: IMAP connections are pooled — one kept-alive connection per account is reused across calls (verified with a NOOP, closed after
APPLE_MAIL_MCP_IMAP_IDLE_MSof inactivity), so there's no per-call connection overhead (#50).iCloud: set
APPLE_MAIL_MCP_IMAP_HOST=imap.mail.me.com,APPLE_MAIL_MCP_IMAP_USERto your iCloud address,APPLE_MAIL_MCP_IMAP_ACCOUNTto the Mail account name (e.g.iCloud), and use an app-specific password (from appleid.apple.com) stored in the Keychain.
Connection footprint (playing nice with Gmail)
IMAP connections are a shared, capped resource: Gmail allows at most 15 simultaneous IMAP connections per account, and Apple Mail itself needs some of those slots. This server keeps its footprint small:
One pooled connection per account, reused across calls and closed after ~30s idle (tune with
APPLE_MAIL_MCP_IMAP_IDLE_MS;0= never close). So a server that isn't actively serving IMAP calls holds zero connections.IMAP IDLE is opt-in (
APPLE_MAIL_MCP_IMAP_IDLE=1). When on, it adds one persistent connection per account (a long-lived watcher), on top of the pooled request connection — leave it off if you don't need push notifications.Connections are dropped on shutdown — SIGINT/SIGTERM and stdin-EOF (the MCP client/parent going away). As of v2.6.1 the server also self-exits if it becomes orphaned (parent force-quit/crashed → reparented to launchd), polling every 30s, so it can't linger holding sockets after its session is gone.
The catch is multiple concurrent instances. A host like the Claude desktop
app spawns a separate set of MCP servers per open conversation (and respawns
them after a crash), so the footprint is per instance × accounts. With IDLE
off, an idle instance trends to 0 connections; with many active conversations
or IDLE on, the per-account total climbs toward Gmail's 15-connection cap and can
starve Apple Mail of slots (→ intermittent "cannot connect"). If you hit that,
close idle Claude conversations, keep APPLE_MAIL_MCP_IMAP_IDLE off unless you
need push, and/or lower APPLE_MAIL_MCP_IMAP_IDLE_MS.
Configuration file (when the host strips env)
Some host apps (e.g. Claude Desktop) launch the MCP server with a scrubbed
environment and ignore the env block in their server config, so there's no way
to pass APPLE_MAIL_MCP_* settings through it. In that case, put them in a JSON
file the host doesn't manage — APPLE_MAIL_MCP_CONFIG_FILE, or by default
~/Library/Application Support/apple-mail-mcp/config.json:
{
"APPLE_MAIL_MCP_IMAP_USER": "you@gmail.com",
"APPLE_MAIL_MCP_IMAP_HOST": "imap.gmail.com",
"APPLE_MAIL_MCP_IMAP_KEYCHAIN_SERVICE": "imap.gmail.com",
"APPLE_MAIL_MCP_IMAP_KEYCHAIN_ACCOUNT": "you@gmail.com",
"APPLE_MAIL_MCP_IMAP_IDLE": "1"
}The server reads it at startup and merges values into the environment without
overriding anything already set there (so an explicit env still wins). Store
only non-secret config here — passwords belong in the Keychain, never in this
file.
Push notifications (IMAP IDLE) — opt-in
When APPLE_MAIL_MCP_IMAP_IDLE=1, the server opens a dedicated, long-lived
connection to each configured IMAP account and watches its INBOX for new
mail. On arrival it pushes two MCP notifications to the client (no polling by the
client required):
notifications/message(logging) — a human-readable line, e.g.New mail in "Work": 2 new message(s) (INBOX now 1843).notifications/resources/updated— for the affected account's resourcemail://mailboxes/{account}, so a client subscribed to that resource knows to re-read it.
This requires an IMAP account to be configured (single-account env or
APPLE_MAIL_MCP_IMAP_ACCOUNTS); accounts that only use AppleScript aren't
watched. Detection is real-time via the IMAP IDLE EXISTS event where the
server pushes it, with an automatic polling fallback for servers that don't.
Dropped connections reconnect with backoff, and the watchers shut down cleanly on
SIGINT/SIGTERM.
Enable it in your MCP client config alongside the IMAP settings:
{
"mcpServers": {
"apple-mail": {
"command": "node",
"args": ["/path/to/apple-mail-mcp/build/index.js"],
"env": {
"APPLE_MAIL_MCP_IMAP_USER": "you@gmail.com",
"APPLE_MAIL_MCP_IMAP_KEYCHAIN_SERVICE": "imap.gmail.com",
"APPLE_MAIL_MCP_IMAP_IDLE": "1"
}
}
}
}Note: this is most useful with clients that surface MCP logging messages or subscribe to resource-update notifications. Clients that ignore notifications are unaffected — the feature is opt-in and adds no behavior unless enabled.
send-serial-email
Send individual personalized emails to a list of recipients (mail merge). Each recipient receives their own email — recipients don't see each other. Supports {{placeholder}} tokens in both subject and body.
Parameter | Type | Required | Description |
| object[] | Yes | List of recipients, max 100 (see below) |
| string | Yes | Email subject — use |
| string | Yes | Email body — use |
| string | No | Mail.app account label (AppleScript), or an allowed email-form SMTP From override |
| string | No |
|
| number | No | Delay between sends in ms (default: 500, max 10000) |
Each recipient object:
Field | Type | Required | Description |
| string | Yes | Recipient email address |
| object | Yes | Key-value pairs for placeholder replacement |
Example:
{
"recipients": [
{ "email": "alice@example.com", "variables": { "Name": "Alice", "Company": "Acme" } },
{ "email": "bob@example.com", "variables": { "Name": "Bob", "Company": "Globex" } }
],
"subject": "Hello {{Name}}!",
"body": "Dear {{Name}},\n\nGreat to connect about {{Company}}.\n\nBest regards"
}Returns: Per-recipient success/failure results with a summary count, plus the transport used for the batch.
⚠️ Safety: Sends real mail immediately to every recipient and cannot be unsent. Confirm the recipient list, subject, and body with the user before calling.
create-draft
Save an email to Drafts without sending.
Parameter | Type | Required | Description |
| string[] | Yes | Recipient addresses |
| string | Yes | Email subject |
| string | Yes | Email body (plain text) |
| string[] | No | CC recipients |
| string[] | No | BCC recipients |
| string | No | Account for draft. With |
| (string | {filename, contentBase64})[] | No | Up to 20 attachments: absolute file paths inside the configured read roots and/or inline |
| string | No | (2.20.0) Parent |
| string[] | No | (2.20.0) |
Returns: Confirmation that draft was created, with transport (applescript, or imap for a threaded draft).
Threaded drafts (2.20.0, #267): Mail.app's outgoing message has no header property,
so a draft with inReplyTo/references cannot go through AppleScript. Instead the
server composes it with the same MIME builder as the SMTP send (attachments included) and
files it over IMAP into the account's Drafts mailbox (SPECIAL-USE \Drafts), flagged
\Draft. The account is account when given (it must be a configured IMAP account —
pass the exact accountLabel or login from list-accounts/your APPLE_MAIL_MCP_IMAP_*
config, e.g. "user@icloud.com"; a generic provider name like "iCloud" is not a
recognized alias and is refused);
otherwise the IMAP account whose login is the SMTP identity, else the only configured
IMAP account. When none qualifies — IMAP unconfigured, an unknown account, or several
accounts and no way to choose — the call fails and files nothing; it never falls back
to an unthreaded Mail.app draft. The result names the Drafts mailbox, the account and
the draft's messageId. Without threading headers, create-draft is unchanged
(AppleScript).
get-thread
Group a conversation by normalized subject (across the AppleScript or IMAP backend).
Parameter | Type | Required | Description |
| string | Yes | A message ID in the conversation (numeric or |
| string | No | Account to search (omit to search all) |
| string | No | Mailbox to search (omit to search all) |
| number | No | Max messages in the thread (default 50) |
Returns: The conversation's messages, oldest-first.
fetch-attachment
Return an attachment's bytes as base64 (the read counterpart to inline-base64 send).
Parameter | Type | Required | Description |
| string | Yes | Message ID (numeric or |
| string | Yes | Attachment filename (from |
Returns: The attachment bytes, base64-encoded (also in structuredContent.contentBase64).
resolve-message-id
Map imap: message IDs to their numeric Mail.app IDs, via each message's RFC 5322 Message-ID (the join key both backends share). Needed for the AppleScript reply/forward path; direct SMTP replies and forwards read imap: IDs without numeric conversion. Numeric IDs pass through unchanged.
Parameter | Type | Required | Description |
| string[] | Yes | 1–100 message IDs, each numeric or |
Returns: For each input ID, its numericId (or null when it can't be resolved) and the messageId used, plus count and resolvedCount. The lookup scopes to the message's account and checks its INBOX first, to avoid scanning a large All Mail/Archive mailbox.
You do not need this for flag colors (v2.10.0+). Colors used to require the numeric-ID path, and older docs and tool descriptions said so.
flag-messageandbatch-flag-messagesnow write the color over IMAP directly, as Mail.app's$MailFlagBit0/1/2keywords, so a smart mailbox keyed on flag color matches an IMAP-flagged message. Resolving IDs just to apply a color reintroduces the AppleScript/TCC dependency 2.10.0 removed. Flag, move, mark, and delete all acceptimap:IDs as-is.
reply-to-message
Reply to an existing message.
Parameter | Type | Required | Description |
| string | Yes | Message ID to reply to |
| string | No | Source account for a numeric ID; provide together with |
| string | No | Source mailbox path for a numeric ID; provide together with |
| string | Yes | Reply body (plain text; HTML tags such as |
| boolean | No | Reply to all recipients (default: false) |
| boolean | No | Send immediately (default: true, false = save as draft) |
| string | No |
|
| string | No |
|
| (string | {filename, contentBase64})[] | No | Up to 20 attachments, same rules as |
Example - Reply to sender only:
{
"id": "12345",
"body": "Thanks for the update!"
}Example - Reply all, save as draft:
{
"id": "12345",
"account": "Work",
"mailbox": "INBOX",
"body": "I'll review this and get back to everyone.",
"replyAll": true,
"send": false
}Example - Reply all with an attachment (2.20.0):
{
"id": "imap:eyJhIjoiV29yayIsInAiOiJJTkJPWCIsInUiOjQyfQ",
"body": "Here is the PDF you asked for.",
"replyAll": true,
"attachments": [
"/Users/me/Documents/quote.pdf",
{ "filename": "notes.txt", "contentBase64": "SGVsbG8K" }
]
}Attachments (2.20.0, #267): same schema, limits (20 items, 25 MiB each) and read
roots as send-email, validated before the original is fetched — an out-of-roots path
fails with exactly send-email's error. Recipients, threading headers and the quote are
identical to the same call without attachments. Over SMTP the files ride the same
nodemailer path send-email uses, so the Sent-folder copy (sentCopy) carries them too.
On the AppleScript transport they are added with make new attachment after the
reply content is set and before save/send (inline items are written to 0600 temp
files and removed afterwards), so a send: false draft contains both the attachments and
the quoted original. make new attachment returns before Mail has loaded the file, so the
script waits until Mail counts every file (bounded at 15 s) before saving or sending
(2.20.16, #293; the same wait applies to send-email and create-draft). Results report
attachmentCount.
Delivery: imap: IDs are read directly from their encoded account, mailbox, and UID. Numeric IDs are read through Mail.app. With SMTP configured, replies use clean MIME with the original Message-ID in In-Reply-To and the full References chain. Only the original plain-text body is quoted; the new text is not. Pass transport: "smtp" to require this path, or transport: "applescript" to use Mail.app explicitly.
Numeric source scope: Pass account and mailbox together using the account
and full mailbox path from the original search or listing. Both the original read
and the Mail reply target use that scope; a missing or ambiguous account, mailbox,
or message stops the operation instead of scanning elsewhere. For Mail.app
composition, the original must have a usable Message-ID, which is checked again
against the selected message before the composer is created. An imap: ID already
encodes its source: omit both fields for that form. Supplying only one field, or
supplying either with an imap: ID, returns an error. Without explicit scope,
numeric IDs retain the remembered-location and bounded-scan strategy described
under numeric IDs, but an ambiguous remembered account or mailbox
now stops instead of falling back to a scan. For scoped SMTP, the configured From
address must belong to the selected enabled Mail account; an SMTP login can be a separate
service username. A local "On My Mac" source has no Mail account whose SMTP From
can be verified, so scoped SMTP stops rather than selecting an identity.
AppleScript transport quoting (v2.19.12): every draft (send: false) and any send that falls back to AppleScript (SMTP not configured) also gets the original quoted — an "On <date>, <sender> wrote:" attribution line followed by the > -prefixed original body, built the same way as the SMTP path. Mail's own reply/forward AppleScript commands generate this quote internally but expose no way to read it back, so it's built here instead of relying on Mail's (unreadable) copy. Unlike SMTP, this path has no sending-identity restriction — it's read-only source lookup, not sending.
Sender on the AppleScript transport (2.20.15, #294): Mail's reply/forward commands set no sender, so Mail would answer from its default sending account. The reply or forward is now sent from the account the original sits in: the original's to/cc address that belongs to that account — or, for a message you sent, its own From address — else the account's first address. The sender is read back before saving or sending; a mismatch deletes the compose and returns an error. A message in an "On My Mac" mailbox has no account, so its recipients are matched against every enabled account and, with no match, the sender is left to Mail. The same applies to forward-message.
Failure behavior: SMTP and AppleScript-content replies stop if the original source or plain-text body cannot be read, rather than silently dropping the quote. Numeric-message replies can recover text from raw MIME when Mail.app's decoded content is unavailable. A genuinely empty original body is allowed; an unreadable body is not. Native-editor mode preserves Mail's own reply history and requires the original source to be readable, but does not reconstruct its plain-text quote. Once SMTP is selected, a missing password, unreadable source, missing reply address or Message-ID, or SMTP failure returns an error. It does not silently switch to AppleScript or create an unthreaded new message. With SMTP unconfigured and transport omitted, AppleScript remains available. send: false saves a Mail.app draft; combining it with transport: "smtp" is rejected before composing.
The direct IMAP source read is bounded to 25 MiB, including MIME attachments. Its account login must match the SMTP login, configured From, or an explicitly configured APPLE_MAIL_MCP_SMTP_ALLOWED_FROM identity; otherwise the call fails rather than sending from an unrelated account. The SMTP configuration still represents a single sending identity, not a per-account SMTP registry.
Success includes transport, attachmentCount and, for SMTP when returned by the server, the new messageId. Over SMTP a best-effort Sent-folder copy is filed over IMAP exactly as for send-email (sentCopy / sentCopyError, see SMTP transport). send-email is for new conversations: adding Re: to its subject does not add threading headers. Use this reply tool with the original id instead.
⚠️ Safety: With the default send: true, sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling (or pass send: false to save a draft for review).
forward-message
Forward a message to new recipients.
Parameter | Type | Required | Description |
| string | Yes | Message ID to forward |
| string | No | Source account for a numeric ID; provide together with |
| string | No | Source mailbox path for a numeric ID; provide together with |
| string[] | Yes | Recipients to forward to |
| string | No | Message to prepend (plain text) |
| boolean | No | Send immediately (default: true, false = save as draft) |
| string | No |
|
| string | No |
|
| (string | {filename, contentBase64})[] | No | Up to 20 additional attachments, same rules as |
Delivery: uses the same source lookup, transport selection, 25 MiB source limit, account-identity check, and failure behavior as reply-to-message. A forward deliberately starts a new conversation, so it has no In-Reply-To or References headers. The existing plain-text forwarding behavior is unchanged: original attachments are not reattached. See SMTP transport. On the AppleScript transport, a body is merged with the forwarded-message header block and original text the same way the SMTP path builds it (see reply-to-message's AppleScript transport quoting note); with no body, Mail's own forward content is left untouched — unless attachments are given, in which case the forwarded-message block is built the same way so Mail has a paragraph to anchor the new files after.
Attachments (2.20.0, #267): attachments adds new files to the forward, with or without a prepended body, under the same rules, validation and per-transport mechanics as reply-to-message's attachments. They are independent of the original's attachments (which the SMTP path still does not reattach).
SMTP forwarding requires a readable plain-text original. Scoped AppleScript-content forwards that add a body or attachments also stop if the original text is unreadable; without added content, Mail retains its own forward history. Native-editor mode preserves Mail's native history. An intentionally empty plain-text message is still valid.
⚠️ Safety: With the default send: true, sends real mail immediately and cannot be unsent. Confirm the recipients, subject, and body with the user before calling (or pass send: false to save a draft for review).
mark-as-read / mark-as-unread
Change read status of a message.
Parameter | Type | Required | Description |
| string | Yes | Message ID |
flag-message / unflag-message
Flag or unflag a message. flag-message optionally takes a flag color; unflag-message removes the flag entirely (which also clears any color).
Parameter | Type | Required | Description |
| string | Yes | Message ID |
| string | No | ( |
Flag colors are an Apple Mail feature — the message's flag index (0 red, 1 orange, 2 yellow, 3 green, 4 blue, 5 purple, 6 gray), which is the property a Mail smart mailbox can match on. The color is applied on both routes (since 2.10.0): AppleScript sets the flag index directly, and for an IMAP-routed id (imap:…) the color is written as Mail.app's $MailFlagBit0/1/2 keywords — a 3-bit field holding the same palette index. \Flagged on its own really is colorless, but those keywords ride alongside it in an ordinary UID STORE, so a smart mailbox keyed on flag color matches an IMAP-flagged message too. You do not need to resolve to a numeric id just to color a flag.
To read a color back, or find messages by color, use
search-messages (or list-messages for
read-back only) — both routes now return flagColor (the palette name) on
every flagged-with-a-color row, and search-messages takes a flagColor
filter using this same palette (#286). The IMAP route additionally still
carries the raw 0-6 flagColorIndex for back-compat.
delete-message
Delete a message (move to trash).
Parameter | Type | Required | Description |
| string | Yes | Message ID |
structuredContent carries countDelta — what the delete actually did to the
source mailbox. See Auditing destructive operations.
⚠️ Safety: Destructive. Requires explicit user confirmation; search/list first to confirm the message id.
move-message
Move a message to a different mailbox.
Parameter | Type | Required | Description |
| string | Yes | Message ID |
| string | Yes | Destination mailbox — full path ( |
| string | No | Account containing mailbox |
A destination is matched first as a full path, then as a leaf name. If a leaf
name matches more than one mailbox (e.g. Archive under both Work and
Thornlands), the move is refused with an error naming every candidate — pass
the full path. The same applies to batch-move-messages, delete-mailbox and
rename-mailbox.
structuredContent carries countDelta — what the move actually did to the
source mailbox. See Auditing destructive operations.
list-attachments
List attachments on a message.
Parameter | Type | Required | Description |
| string | Yes | Message ID |
Returns: List of attachments with name, MIME type, and size — the decoded byte count,
i.e. what fetch-attachment returns.
A numeric id resolves the same way the read tools resolve one (2.20.21): the mailbox the id
was listed from when that is known, else a bounded cross-mailbox scan that skips very large
mailboxes and stops at its time budget. An id that several mailboxes hold is refused, with the
candidates named, rather than answered for whichever copy came first — so list or search the
mailbox you mean immediately before calling this. imap:… ids are never ambiguous.
Sizes over IMAP. BODYSTRUCTURE reports a part's size in its transfer encoding, so a
base64 attachment's raw figure is ~4/3 of the file. list-attachments converts it: a
Content-Disposition size= parameter wins when present; 7bit/8bit/binary parts are exact
as reported; base64 parts are measured exactly from a short tail of each part (padding and
line wrap), fetched in one batched FETCH for the whole message; quoted-printable parts up
to 64 KiB are fetched and counted exactly. If a part can't be measured (the probe fails, the
wrap is irregular, or a quoted-printable part is larger), the size is an estimate within a
few bytes for base64 — an upper bound for quoted-printable — and the entry carries
sizeApproximate: true. fetch-attachment always reports the exact bytes.
save-attachment
Save a message attachment to disk.
The destination must not already exist: save-attachment fails closed instead
of overwriting an existing file. AppleScript and MIME fallback paths stage the
bytes privately, commit with an exclusive create, and leave the saved file
owner-readable/writable (0600).
A numeric id resolves the same way the read tools resolve one (2.20.21): the mailbox the id
was listed from when that is known, else the bounded cross-mailbox scan. An id that several
mailboxes hold saves nothing rather than writing some other message's attachment under the
name you asked for.
Parameter | Type | Required | Description |
| string | Yes | Message ID |
| string | Yes | Filename of the attachment |
| string | Yes | Directory to save to |
Batch Operations
All batch operations accept an array of message IDs (max 100 per batch) and return per-item success/failure results.
Numeric IDs are scoped to the mailbox you listed them from. Mail.app numbers messages per
mailbox, so on a label store (Gmail, iCloud) one message answers to the same id in INBOX,
Important and All Mail at once — and deleting the All Mail copy is not the same operation as
deleting the INBOX copy. Each id is therefore bound to the mailbox it was listed/searched from and
the operation is applied only there, so list or search the mailbox immediately before acting on
it. An id the server hasn't seen listed is accepted only when exactly one mailbox holds it;
if several do, that id fails with the candidate mailboxes named instead of being applied to an
arbitrary copy. imap:… ids carry their own account + mailbox + UID and are never ambiguous.
Say which mailbox with sourceMailbox / sourceAccount. The binding above is remembered
per running server, so a client that reconnects, restarts, or replays a saved list of ids has
nothing recorded and every id takes the slower whole-tree path — where, on a label store, it is
likely to be refused as ambiguous. Passing the source mailbox explicitly is the reliable way to
stay scoped, and it overrides the remembered location. These parameters name where the ids came
from; for batch-move-messages that is distinct from mailbox, the destination.
sourceMailbox and sourceAccount are an atomic scope pair: provide both for numeric ids.
The server never fills in a missing account from mutable default-send state, because the same
mailbox name can exist in more than one account and numeric ids are only unique within an account
and mailbox. A whitespace-only source field is rejected. sourceAccount by itself pins nothing,
since the mailbox is what an id is scoped to; imap:… ids ignore both fields because they carry
their own account, mailbox, and UID identity.
A repeated id is one message. ids is treated as a set: a duplicate names the same message,
so it is operated on once, and the batch returns one result per distinct id. success is
therefore a count of messages, not of list positions.
batch-delete-messages
Parameter | Type | Required | Description |
| string[] | Yes | Message IDs to delete (max 100) |
| string | No | Mailbox the numeric ids were listed from — pins them to it. Ignored for |
| string | No | Account the numeric ids were listed from. Required when |
structuredContent carries countDelta — what the batch actually did to each
source mailbox. See Auditing destructive operations.
⚠️ Safety: Destructive. Requires explicit user confirmation; search/list first to confirm the message ids.
batch-move-messages
Parameter | Type | Required | Description |
| string[] | Yes | Message IDs to move (max 100) |
| string | Yes | Destination mailbox |
| string | No | Account containing mailbox |
| string | No | Mailbox the numeric ids were listed from — pins them to it. Ignored for |
| string | No | Account the numeric ids were listed from. Required when |
structuredContent carries countDelta — what the batch actually did to each
source mailbox. See Auditing destructive operations.
batch-mark-as-read / batch-mark-as-unread
Parameter | Type | Required | Description |
| string[] | Yes | Message IDs (max 100) |
| string | No | Mailbox the numeric ids were listed from — pins them to it. Ignored for |
| string | No | Account the numeric ids were listed from. Required when |
batch-flag-messages / batch-unflag-messages
Parameter | Type | Required | Description |
| string[] | Yes | Message IDs (max 100) |
| string | No | ( |
| string | No | Mailbox the numeric ids were listed from — pins them to it. Ignored for |
| string | No | Account the numeric ids were listed from. Required when |
Mailbox Operations
list-mailboxes
List all mailboxes for an account.
Parameter | Type | Required | Description |
| string | No | Account to list from, or |
Returns: List of mailbox paths (account-relative, e.g. Archive/Inbox for
a nested mailbox — a top-level Inbox stays Inbox) with message and unread
counts. A source that could not be read is named (partial: true +
failedAccounts) rather than dropped, and a listing Mail refused outright
returns an error naming the accounts that do exist — never an empty list.
Nested mailboxes and Gmail labels. Every mailbox parameter across this
server (search-messages, list-messages, get-unread-count, move-message,
delete-mailbox, rename-mailbox, create-rule's moveTo) accepts either the full
path or a leaf name that is unique across the account — the same rule
move-message has always used. A leaf name that matches more than one mailbox
(e.g. a top-level Inbox and an Archive/Inbox on an Exchange account) is
refused rather than guessed; pass the full path to disambiguate. This also
means Gmail's nested special mailboxes now report their real path, e.g.
[Gmail]/All Mail rather than All Mail — a visible change from before 2.17.0.
Matching ignores letter case and Unicode normalization form: a typed
precomposed México (NFC) finds a mailbox the server stores decomposed (NFD —
common for folders created on a Mac, and what iCloud keeps), and the server's
own stored spelling is what gets selected. Two mailboxes whose names differ
only in case or normalization look identical in any listing, so a name that
matches both is refused with an error saying so — rename one of them. For the
same reason create-mailbox treats a normalization-equivalent existing name as
already existing, and rename-mailbox refuses to create such a twin.
Messages awaiting expunge are not listed. On IMAP accounts, search,
list, thread and mail-stats queries only match messages not flagged
\Deleted (IMAP UNDELETED). iCloud keeps flagged-but-never-expunged
messages out of its message counts yet still returns them from UID SEARCH,
which made a filter-less list-messages fail on such a mailbox before 2.19.16.
Very large IMAP mailboxes are read newest-first in bounded windows
(#256). Above 10,000
messages, a filter-less list-messages/search-messages pages by message
sequence number: page offset of limit is sequence numbers
N-offset-limit+1 .. N-offset, so it fetches UIDs and flags for that range
alone (plus a small margin; \Deleted ones are skipped and the page topped up
from below) and full rows for the page. The cost is the page, not the offset:
limit: 1 at offset 790,000 of a 793,614-message mailbox is one ~33-message
FETCH, not a whole-mailbox SEARCH or a walk over every skipped message.
Offsets count sequence positions. iCloud keeps messages awaiting expunge out of
the sequence space, so there they are exact; on a server that keeps them in it,
a page shifts by the number of such messages above it (they are never
returned).
A filtered search there runs the same criteria over newest-first sequence
windows (5,000 messages, growing to 50,000) and stops once the page is full;
the reported total then reads at least N unless the walk reached the bottom
of the mailbox. A filtered search's deep offset has no such shortcut — which
messages match is only known by searching the skipped windows — so its cost
still grows with the offset (in 50,000-message SEARCH windows); narrow it
with dateTo instead. A SEARCH or FETCH that fails names the mailbox, its size and
the server's own reason (or a likely timeout) instead of reporting "no
messages".
Mail's local "On My Mac" mailboxes are not children of any account — they
hang off the application — so they are reported under the synthetic account label
On My Mac. An unscoped call includes them (listed last); account="On My Mac" lists only them. on my computer, local and local folders are accepted
as aliases.
They deliberately do not appear in list-accounts, which reports real
accounts only: the local store is a store, not an account. Nothing selects it
implicitly — omitting account still resolves to a real account for every other
tool.
The mail inside them is reachable too: list-messages and search-messages
accept account="On My Mac", and get-message resolves an id that lives only in
a local mailbox. An id present both in an account mailbox and locally is
reported as ambiguous rather than silently resolving to the account copy.
get-unread-count
Get unread message count.
Parameter | Type | Required | Description |
| string | No | Mailbox to check (omit for INBOX) |
| string | No | Account to check (omit to sum each account's INBOX) |
Returns: The unread count for the requested scope.
Omitting
mailboxcounts INBOX, not a cross-mailbox total. This changed in 2.8.15: summing every mailbox was slow and wrong on Gmail, where one message appears in INBOX, All Mail and every label it carries. For account-wide totals useget-mail-stats.
create-mailbox
Create a new mailbox.
Parameter | Type | Required | Description |
| string | Yes | Mailbox name |
| string | No | Account to create in |
delete-mailbox
Delete a mailbox.
Parameter | Type | Required | Description |
| string | Yes | Mailbox name |
| string | No | Account containing mailbox |
⚠️ Safety: Destructive — deletes the mailbox and its contents. Requires explicit user confirmation; list mailboxes first to confirm the name.
rename-mailbox
Rename a mailbox (creates new, moves messages, deletes old).
Parameter | Type | Required | Description |
| string | Yes | Current mailbox name |
| string | Yes | New mailbox name |
| string | No | Account containing mailbox |
Smart Mailbox Operations (intelligente Postfächer)
Requires Full Disk Access. These tools read and write
~/Library/Mail/V*/MailData/SyncedSmartMailboxes.plist, which is TCC-protected. Without Full Disk Access for the server's Node runtime the read simply finds nothing, and the tools report "no smart mailboxes" or "launch Mail at least once" rather than a permission error — see Node runtime & TCC permissions.
Smart mailboxes are Apple Mail's criteria-based virtual views — not real folders, so no messages are moved. AppleScript's smart mailbox / intelligentes Postfach terms don't compile reliably on localized (e.g. German) macOS, so these tools read and edit ~/Library/Mail/V*/MailData/SyncedSmartMailboxes.plist directly.
How writes stay safe: creating or deleting a smart mailbox first backs the plist up to SyncedSmartMailboxes.plist.bak, edits a temp copy with plutil/PlistBuddy, validates it with plutil -lint, and only then atomically renames it into place. Your existing smart mailboxes — including any with date/data criteria — are never rewritten, only the single target entry is added or removed. These tools do not quit or restart Mail: quit Mail first for reliable results, since a running Mail may not show a new smart mailbox until it's relaunched and can overwrite plist edits it didn't make.
list-smart-mailboxes
List existing smart mailboxes.
Parameters: None
Returns: List of smart mailbox names + criteria summary.
create-smart-mailbox
Create a smart mailbox with a simple contains rule.
Parameter | Type | Required | Description |
| string | Yes | Name for the smart mailbox |
| string | No | Match if From contains this |
| string | No | Match if Subject contains this |
| string | No | Match if Body contains this |
Provide at least one of the three *Contains fields.
⚠️ Safety: edits SyncedSmartMailboxes.plist (backed up + atomic, existing smart mailboxes preserved). Quit Mail first for reliable results; the new smart mailbox appears the next time Mail launches.
delete-smart-mailbox
Delete a smart mailbox by name.
Parameter | Type | Required | Description |
| string | Yes | Smart mailbox name |
⚠️ Safety: destructive — removes the smart mailbox from SyncedSmartMailboxes.plist (backed up + atomic; every other smart mailbox is preserved). Not undoable in-app. Confirm the exact name with list-smart-mailboxes first, and quit Mail first for reliable results.
create-newsletter-smart-mailboxes
High-level tool: scan recent messages in your INBOXes, detect likely newsletters (volume + signals like List-Unsubscribe, noreply, repetitive subjects), and create smart mailboxes for them (names prefixed "NL: ...").
Parameter | Type | Required | Description |
| boolean | No | Default true — only propose, do not create |
| number | No | Min messages from a sender (default 3) |
| number | No | Lookback window in days (default 90) |
Defaults to a safe dry run that only proposes. Pass dryRun: false to actually create the smart mailboxes for newsletters cluttering your Inbox.
⚠️ Safety: with dryRun: false this edits SyncedSmartMailboxes.plist (backed up + atomic, existing entries preserved) and can create many smart mailboxes at once — review a dry run first. Scans up to ~400 recent messages per inbox via AppleScript, which can be slow on large mailboxes.
Account Operations
list-accounts
List all configured Mail accounts.
Parameters: None
Returns: List of account names and email addresses.
Rules
list-rules
List all mail rules.
Parameters: None
Returns: List of rule names and enabled status.
enable-rule / disable-rule
Enable or disable a mail rule.
Parameter | Type | Required | Description |
| string | Yes | Rule name |
create-rule
Create a Mail rule with one or more conditions and actions.
Parameter | Type | Required | Description |
| string | Yes | Rule name (must be unique) |
| object[] | Yes | One or more |
| object | Yes | At least one of |
| boolean | No |
|
| boolean | No | Whether the rule is enabled on creation (default |
Each condition is { field, operator, value } where field is one of from, to, cc, subject, content and operator is one of contains, notContains, equals, beginsWith, endsWith. Actions: markRead / markFlagged / delete (booleans), moveTo (mailbox name) with optional moveToAccount.
New rules are created disabled by default, including rules that delete or move
messages. Review the conditions and actions with list-rules and in Mail.app,
then call enable-rule explicitly when the rule is approved. Set
enabled: true only when immediate activation is deliberate.
Example:
{
"name": "Newsletters",
"conditions": [{ "field": "from", "operator": "contains", "value": "newsletter" }],
"actions": { "markRead": true, "moveTo": "Reading" }
}delete-rule
Delete a mail rule by name.
Parameter | Type | Required | Description |
| string | Yes | Rule name |
⚠️ Safety: Destructive. Requires explicit user confirmation; list rules first to confirm the name.
Contacts
search-contacts
Search the macOS Contacts database by name, organization, nickname, or email substring.
Since 2.8.7 this reads the AddressBook SQLite files directly rather than driving Contacts.app over AppleScript, so Contacts.app need not be running and no Automation grant is involved — but the Node runtime does need Full Disk Access, and Node 22.5+ (see Requirements). Without either, the tool returns an empty list rather than an error.
Parameter | Type | Required | Description |
| string | Yes | Substring matched against full name, organization, nickname, or any email address |
Returns: List of contacts with name, email addresses, and phone numbers. Results are not truncated — a broad query returns every match.
Templates
Email templates are persisted to disk so they survive server restarts, stored as JSON at APPLE_MAIL_MCP_TEMPLATES_FILE (default ~/Library/Application Support/apple-mail-mcp/templates.json).
save-template
Save or update an email template.
Parameter | Type | Required | Description |
| string | Yes | Template name |
| string | Yes | Default subject line |
| string | Yes | Template body |
| string[] | No | Default recipients |
| string[] | No | Default CC recipients |
| string | No | Template ID (for updating) |
list-templates
List all saved templates.
Parameters: None
get-template
Get a template by ID.
Parameter | Type | Required | Description |
| string | Yes | Template ID |
delete-template
Delete a template.
Parameter | Type | Required | Description |
| string | Yes | Template ID |
⚠️ Safety: Destructive — removes the template from the on-disk store. Requires explicit user confirmation; list templates first to confirm the id.
use-template
Create a draft from a template, with optional overrides.
Parameter | Type | Required | Description |
| string | Yes | Template ID |
| string[] | No | Override recipients |
| string[] | No | Override CC |
| string | No | Override subject |
| string | No | Override body |
Diagnostics
health-check
Verify Mail.app connectivity and permissions.
Parameters: None
Returns: Status of all health checks (app running, permissions, account access).
doctor
Run a full setup diagnostic: Mail.app automation permission, account state (flagging disabled accounts), and each configured IMAP/SMTP backend — each reported as ok / warn / fail with an actionable message.
Parameters: None
Returns: A per-check report (structuredContent carries the raw {healthy, checks[]}).
get-mail-stats
Get mail statistics.
Parameter | Type | Required | Description |
| string | No | Limit to one account (uses fast IMAP |
Returns: Total and per-account message/unread counts, plus recently received stats (24h, 7d, 30d). The scoped IMAP path also returns a perMailbox breakdown.
Gathering stats costs one IMAP STATUS per mailbox, and Gmail lists every label
as a mailbox, so a large account is not instant. Accounts are counted
concurrently, and each is bounded by APPLE_MAIL_MCP_STATS_BUDGET_MS
(default 25000). In the merged all-accounts path an account that fails or
overruns is reported via partial: true + failedAccounts rather than being
folded in as a silent zero; a scoped call to a single account returns an error
naming the budget instead. Raise the budget if you have a very large account.
The whole call is additionally bounded by one wall-clock deadline,
APPLE_MAIL_MCP_STATS_DEADLINE_MS (default 50000), which covers the Mail.app
account enumeration as well as every per-account read. Per-step budgets alone
were not enough: their worst cases add up, and the sum could exceed a
client's request timeout, so the call died with nothing returned instead of
degrading. Keep the deadline below your MCP client's request timeout — whatever
cannot be read inside it is named in failedAccounts, so you always get a
partial answer rather than a dead call.
Concurrent get-mail-stats calls do not run concurrently. Tool calls are
serialized so they cannot race into Mail.app's single-threaded AppleScript
dispatch, so each call waits for the ones ahead of it and per-call latency grows
with queue depth — N concurrent calls take about N × the single-call cost. Since
this is the most expensive read tool, that is very visible here: measured on 3
IMAP accounts, three concurrent calls returned at 5.5s / 10.3s / 15.6s against a
~5.2s solo cost. The deadline is measured from when the request arrived, so
that wait is spent from the same budget as the work: a call that waited ≥1s
reports queueWaitMs, and one that arrives with its deadline already spent
returns straight away naming the queue rather than starting work whose answer
would land after your client has given up. Issue these calls one at a time, and
prefer get-unread-count when a single number will do.
get-sync-status
Check Mail.app sync activity.
Parameters: None
Returns: Whether sync is detected, pending uploads, recent activity, and seconds since last change.
Auditing destructive operations
delete-message, move-message, batch-delete-messages and
batch-move-messages report what they actually did, not merely that Mail.app did
not raise an error. This exists because of
#155: a batch delete was
reported to have removed two messages whose ids were never passed, and nothing in
the server recorded enough to explain it.
countDelta — always on, no configuration
Every one of those four tools counts the affected source mailbox immediately
before and immediately after the mutation, inside the same AppleScript it was
already running (no extra osascript invocations, no measurable cost), and
returns the comparison in structuredContent:
{
"ok": true,
"success": 2,
"failed": 0,
"countDelta": [
{
"account": "you@gmail.com",
"mailbox": "INBOX",
"before": 412,
"after": 408,
"expected": 2,
"observed": 4,
"status": "over"
}
]
}status is deliberately not a pass/fail flag:
| Meaning | Warns? |
| Exactly as many messages left the mailbox as the operation acted on. | No |
| More left than were operated on. Messages are unaccounted for. | Yes |
| No comparison this server is willing to assert. | No |
unknownReason distinguishes four cases that are not interchangeable:
| Meaning |
| Mail would not report a count at all ( |
| No expectation is predictable, so no comparison exists — a move whose destination is the source mailbox. |
| The count did not move. On a store that flags deletions instead of removing them this is the ordinary, correct reading for an operation that fully succeeded. |
| The count moved, but by less than the operation accounted for. A flag-only store cannot produce this, which is why it is worth telling apart. |
underwas removed in 2.11.0. It used to mean "fewer left than expected" and was documented as routine. Field evidence retired it — see Whyobservedis a lower bound.
What an over warning does and does not tell you
Only over produces a warning in the tool's text response. Be precise about what
that warning proves, because a warning is useful only for as long as it is
trusted:
It establishes that more messages left the source mailbox across the window of the operation than the operation accounted for. That is the data-loss direction, and it is the #155 signature.
It does not establish that this server removed them. The reading is a before/after pair around a window, so anything else that removes mail from that mailbox inside the window reads identically: a Mail.app rule firing mid-batch, a server-side filter, another client (phone, webmail, a second Mail.app) deleting or moving, or an IMAP expunge landing between the two counts.
Concurrent departure is the benign cause to rule out first, and the warning
text says so. What the asymmetry argument actually buys is the other half:
concurrent arrivals cannot produce over, because a message arriving
mid-operation raises the after-count and biases the reading short.
That is why over is the interesting direction — a strong signal, not a proof.
Setting APPLE_MAIL_MCP_AUDIT_LOG is what settles which one you have: the
collateral diff below names the messages that disappeared, and "the
newsletter my rule files every morning" is a very different report from a message
nothing should have touched.
Why observed is a lower bound (#155)
observed is the movement of Mail's count. It is a lower bound on how many
messages left, not a count of how many left.
On iCloud, @scottstern0325 ran the check that settled this: for two batches
reporting observed: 0, the messages were located in Trash, matched by
date received + sender against the audit log's pre-image. The deletes had
happened. Mail's count had not caught up. Across four readings — 0 of 4, 0 of 1
(a single-id delete), 15 of 16, and 14 of 15 — the shortfall bore no relation
to batch size, which is what a lagging count looks like and not what a
store-behaviour rule looks like.
So a short reading is not evidence about your operation. The per-id outcomes are what report success; this number is not. Do not retry on the strength of it — that is how a message gets deleted twice.
Two things follow:
A count that does not move at all is still the ordinary reading on a store that flags deletions instead of removing them (Gmail label mailboxes, IMAP accounts with "move deleted messages to Trash" off). It reports
unknownReason: "count-did-not-move"and says so, and it is never warned about — a warning that fires on every ordinary Gmail delete would be ignored exactly when it matters.To confirm where messages went, match them at the destination by
date receivedplus sender — not by the numeric ids you passed. Ids are renumbered by the move and do not survive it.
Removed in 2.11.0: the "reported success with no observed effect" warning.
Shipped in 2.10.30, it fired when the count was flat, the snapshot read cleanly
and nothing had disappeared. Its premise was that the snapshot corroborated the
count — but both are read back-to-back in the same script, and the record that
prompted it turns out to have had both instruments stale at once. It therefore
fired on stores that had done exactly what they were asked. It is gone rather
than narrowed; over is the only surviving assertion.
Three more honesty rules:
The expectation is per source mailbox. On a Gmail label store, deleting the
INBOXcopy drops the\Inboxlabel and deleting the[Gmail]/All Mailcopy trashes the message — different operations, but either way the mailbox the ids came from loses exactly one entry per id. That is what is compared. A move's destination count is not checked.A move whose destination is the source mailbox is not compared at all. No message should leave, but what Mail does to the count when a message is re-filed into the mailbox it already occupies is unspecified — so
expectedisnull,statusisunknown,notesays why, and no warning is raised. A warning computed against a guessed expectation would fire on an operation that did exactly what it was asked to, which is the one thing this instrumentation must never do.This makes a self-move a blind spot for the always-on layer, and the cost is worth stating plainly: if messages genuinely do disappear during a self-move, nothing warns you, because there was no expectation to compare against.
statusisunknownrather thanmatch, so the result does not claim the operation was clean — but it does not flag it either. The collateral diff still names anything that vanished, so enableAPPLE_MAIL_MCP_AUDIT_LOGif you need coverage for same-mailbox moves.A repeated id is one message. The batch tools operate on each distinct id once and return one result per distinct id, so
successcounts messages rather than list positions — andexpectedstays comparable with the mailbox instead of double-counting a duplicate into a falseover.
imap: ids are reconciled too, as of 2.15.0. batch-delete-messages and
batch-move-messages return the same countDelta structure on the IMAP path, so
one shape covers both backends and a mixed batch reports an entry per source
mailbox from whichever backend handled it. The entries are concatenated, never
summed — Mail's own count can lag (#155) while the server's STATUS cannot, and
averaging the two would hide which reading you were looking at.
Only the operations that actually remove messages from their source reconcile.
batch-mark-as-read and the flag tools change no count, so emitting
expected: N, observed: 0 for them would manufacture an alarm; they report no
countDelta at all.
Note the mis-targeting class countDelta was originally built for cannot occur
on the IMAP path — a UID names exactly one message in exactly one mailbox — so
there the value is effect confirmation rather than target confirmation.
Single-message tools carry a post-condition check instead. delete-message and
move-message on an imap: id return a verification object in
structuredContent:
{
"verification": {
"verdict": "verified",
"how": "COPYUID: UID 5 arrived in \"Archive\" as UID 91"
}
}
| Meaning |
| The effect was observed — either the server's UIDPLUS |
| The server accepted the command and nothing could confirm the effect. Populates |
unverified is not a failure and must not be rendered as one — it means
"accepted, no observation either way". It is reported rather than hidden because
an absent verification must never read as a successful one, the same rule the
collateral diff follows. A message that is still in the source mailbox after an
accepted move is reported unverified rather than failed, because a Gmail label
store can legitimately keep a message visible in an all-mail view after a move.
APPLE_MAIL_MCP_AUDIT_LOG — opt-in forensic log
Variable | Default | Description |
| (off) | Absolute path to an NDJSON file. Setting it enables the audit log and the collateral diff below |
|
| Set |
|
| Skip the collateral snapshot for mailboxes larger than this many messages. |
|
| How many messages the collateral snapshot reads from Mail per request. Lower it if Mail declines slices on a very large mailbox |
When set, each destructive operation appends one JSON object per line
containing: timestamp, tool name, server version, the arguments it was called
with, the pre-image of every message it resolved (account, mailbox, numeric
id, RFC Message-ID, date received), the per-id outcome (ok / notfound /
error + reason), the countDelta above, and the collateral diff.
The pre-image is the part that matters after the fact: a Mail.app numeric id is unique only within a mailbox and is reused, so on its own it proves nothing about which message was acted on. The RFC Message-ID does.
The record is framed against its own contents. The Message-ID and (when
enabled) the subject are written by whoever sent the mail, so the control
characters this server frames records with are stripped out of every such value
before it is written — a Message-ID crafted to close a record and open a forged
one cannot invent evidence in the log it is being recorded in. The same stripping
is applied to every other value read out of Mail at runtime (date received,
mailbox and account names, the text of an error Mail raised, the candidate list
behind an "ambiguous id" refusal), so no emitter is an exception. A value that
arrives with those characters in it (which a well-formed Message-ID never does)
is logged with each of them replaced by U+FFFD, so the record shows that the
value was altered rather than quietly shortening it.
Collateral identification — which messages actually disappeared
Also gated on APPLE_MAIL_MCP_AUDIT_LOG. The mailbox's (numeric id, Message-ID)
pairs are captured before and after the mutation and diffed, so the log names
every message that left — including ones the caller never listed:
{
"account": "you@gmail.com",
"mailbox": "INBOX",
"snapshot": "ok",
"disappeared": [
{ "id": "75811", "messageId": "a@example.com" },
{ "id": "75814", "messageId": "d@example.com" }
],
"unrequested": [{ "id": "75814", "messageId": "d@example.com" }],
"appeared": []
}A non-empty unrequested is the #155 symptom, with names attached. Please
attach that line to the issue if you ever see one.
id is always the plain decimal id you passed, even on a mailbox whose ids
exceed AppleScript's 2^29 integer range (where Mail hands them back as
9.99999999E+8). That normalisation is also what keeps unrequested truthful:
compared in the raw form, a message you explicitly asked to delete would be
reported here as collateral.
This is O(mailbox size), so it is bounded by APPLE_MAIL_MCP_AUDIT_SNAPSHOT_MAX.
When the bound bites, the record says so explicitly ("snapshot": "skipped" with
a reason) rather than omitting the field — a silently skipped snapshot would read
as "nothing collateral happened", which is worse than no snapshot at all.
countDelta is unaffected by the skip and still reconciles the counts.
Partial snapshots on large mailboxes
The mailbox is read in APPLE_MAIL_MCP_AUDIT_SNAPSHOT_CHUNK-sized slices, each
retried once on its own. It used to be a single whole-mailbox read, which meant
Mail declining that one request lost the entire diff — and the bigger the
mailbox, the more likely that was. The mechanism that attributes collateral
damage was therefore least reliable exactly when the blast radius was largest.
When a slice still will not read, the snapshot is reported as partial and it
names its own gap:
{
"snapshot": "partial",
"unobserved": [{ "phase": "after", "ranges": "251-500" }],
"appeared": [],
"skipReason": "Mail would not read 251-500 (after) of this mailbox, so the snapshot has a hole in it. …"
}Each half of the diff is withheld when the snapshot that could refute it has a hole, because a wrong name here is worse than a missing one:
Hole in |
|
|
neither ( | reported | reported |
| reported (an undercount — a message never read before cannot be missed after) | withheld |
| withheld — a message absent from a partial | reported |
both | withheld | withheld |
An absent field means "not computable", never "empty". Check
"snapshot": "ok" before reading disappeared as a clean bill of health.
⚠️
"ok"is necessary, not sufficient. The enumeration's range is bounded by Mail's own message count, and #155 established that count can lag the mutation. A count reading low truncates the enumeration silently — the unread tail is never requested, so it never registers as a failed slice and the status still says"ok"— and messages past that bound would then look like they disappeared. Until that is fixed, treat adisappearedentry as a lead to check, not a proof.
Privacy, and what the file costs you
Default: identifying metadata only — Message-ID, date, mailbox, account, numeric id. Enough to say which message, nothing about what it says.
Subjects are behind their own opt-in (
APPLE_MAIL_MCP_AUDIT_SUBJECTS=1) because a subject line is frequently the entire sensitive payload, and it is not needed to diagnose #155.Message bodies are never logged, under any setting.
The file grows without bound and is never rotated or truncated by this server. Point it somewhere you control, and delete it when you are done. It is written with your user's permissions, wherever you point it; there is no default location precisely so that turning it on is a deliberate act.
Writes go to that file and nowhere else. Diagnostics go to stderr; nothing is ever written to stdout, which is the JSON-RPC transport.
Usage Patterns
Basic Workflow
User: "Check my inbox for new emails"
AI: [calls list-messages]
"You have 12 messages. Here are the most recent..."
User: "Show me emails from Sarah"
AI: [calls search-messages with query="Sarah"]
"Found 3 emails from Sarah across all mailboxes..."
User: "Read the first one"
AI: [calls get-message with id="..."]
"Subject: Project Update..."Working with Accounts
By default, operations use Mail.app's configured default send account. Search operations check all accounts when no account is specified. To work with specific accounts:
User: "What email accounts do I have?"
AI: [calls list-accounts]
"You have 3 accounts: iCloud, Gmail, Work Exchange"
User: "Show unread emails in my Work account"
AI: [calls list-messages with account="Work Exchange", mailbox="INBOX"]
"Your Work account has 5 unread messages..."To pin which account is used when a tool call omits account, set the
APPLE_MAIL_MCP_DEFAULT_ACCOUNT environment variable to an account name or
email. When unset (the default), the server falls back to Mail.app's
default-send account if it is enabled, otherwise the first enabled account. A
disabled account is never selected implicitly — this env var (an explicit,
deliberate pin) is one of the few ways to target one (#47).
Sending Emails Safely
User: "Draft an email to the team about the deadline"
AI: [calls create-draft with to=["team@..."], subject="...", body="..."]
"I've created a draft. Please review it in Mail.app before sending."
User: "Send it"
AI: [User opens Mail.app and sends manually, or AI calls send-email]Sending Personalized Emails (Mail Merge)
User: "Send a personalized email to Alice (alice@acme.com), Bob (bob@globex.com),
and Carol (carol@initech.com). Subject: 'Project Update for {{Company}}',
Body: 'Hi {{Name}}, here is the latest update for {{Company}}.'"
AI: [calls send-serial-email with recipients, subject template, and body template]
"Successfully sent 3 email(s):
- alice@acme.com: sent
- bob@globex.com: sent
- carol@initech.com: sent"Organizing Messages
User: "Move all newsletters to Archive"
AI: [calls search-messages to find newsletters]
AI: [calls move-message for each, with mailbox="Archive"]
"Moved 8 newsletters to Archive"Documentation
Installation Options
npm (Recommended)
npm install -g apple-mail-mcpFrom Source
git clone https://github.com/sweetrb/apple-mail-mcp.git
cd apple-mail-mcpThe repo ships prebuilt, dependency-free build/index.js and build/cli.js bundles, so a bare clone runs with nothing but Node installed. npm install and npm run build are only needed when you change the source.
You can also install straight from GitHub with
npm install -g github:sweetrb/apple-mail-mcp, but that builds from source (requires pnpm) — prefer the registry package above.
If installed from source, use this configuration:
{
"mcpServers": {
"apple-mail": {
"command": "node",
"args": ["/path/to/apple-mail-mcp/build/index.js"]
}
}
}Running from a clone in Claude Code (project-scope .mcp.json)
This repo ships a .mcp.json at its root so that, when you run claude from inside a clone, the server is registered automatically as a project-scope server — no manual config needed. Just launch Claude Code from the repo directory and approve the server when prompted (the bundled build/index.js is committed, so no build step is required).
The entrypoint is written as:
"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]CLAUDE_PROJECT_DIR is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. You must launch claude from inside the repo for this to work — the bare . fallback is only a last resort and is not reliable, because it resolves against the launching process's working directory, not the repo.
Why not
${CLAUDE_PLUGIN_ROOT}?CLAUDE_PLUGIN_ROOTis set only for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't useCLAUDE_PROJECT_DIR(in a plugin, that points at the user's project, not the plugin's own directory). Claude Code does not support nested defaults like${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}, so a single entrypoint string cannot serve both contexts. The two distribution paths are therefore decoupled: the plugin carries its own MCP config in.claude-plugin/plugin.json(using${CLAUDE_PLUGIN_ROOT}), while the root.mcp.jsonis dedicated to the clone workflow (using${CLAUDE_PROJECT_DIR:-.}). Becauseplugin.jsondeclares its ownmcpServers, the plugin does not also auto-load the root.mcp.json, so there is no double-registration.
Heads-up on scope precedence: project-scope (
.mcp.json) outranks user-scope. If you also have anapple-mailentry registered at user scope (e.g. an absolute path in~/.claude.json), the project-scope entry wins and the user-scope one is ignored entirely. Pick one — for local development on this repo, the project-scope.mcp.jsonis the intended source. To pin a specific local build instead, register it at local scope (claude mcp add apple-mail -s local -- node /abs/path/build/index.js), which outranks project scope.
Security and Privacy
No third parties - The server talks only to Mail.app on this Mac (AppleScript) and, when you configure them, directly to your own mail provider over TLS (IMAP/SMTP). Nothing is sent to this project or any other service. With the default AppleScript backend everything stays on-device; the opt-in IMAP/SMTP backends necessarily reach your provider, which is what they are for.
Permission required - macOS will prompt for automation permission on first use.
No credential storage - The server doesn't store any passwords or authentication tokens.
Email safety - Use
create-draftto review emails before sending.Attachment read boundary - Outbound file attachments may come from ordinary files under the home directory,
/Volumes, or temporary directories by default. Hidden files and known credential/configuration locations (including.ssh,.aws,.config/gh, Keychains, and applicationconfig.jsonfiles) are denied. SetAPPLE_MAIL_MCP_ATTACHMENT_READ_ROOTSto a colon-separated list of explicit absolute additional roots when a deliberate other location is required. Paths are canonicalized before use, so symlink escapes are rejected./tmpis world-writable and is a convenience root, not a user-content trust boundary. Inline base64 attachments are unaffected.
Known Limitations
Limitation | Reason |
macOS only | Apple Mail and AppleScript are macOS-specific |
MCP | The |
Attachment read path restrictions | Outbound file attachments must use full absolute paths inside the default home-directory, |
Smart mailboxes need Mail quit | Smart mailboxes are supported (see Smart Mailbox Operations), but |
Very large mailboxes not searchable via AppleScript | Apple Mail's AppleScript bridge times out on mailboxes with tens of thousands of messages, so unscoped |
Can't delete/rename server-side mailboxes or mutate drafts via AppleScript | Mail.app's AppleScript bridge can only |
Message ID format | Message IDs must be numeric (AppleScript ids) or |
Batch size cap | Batch operations are limited to 100 messages per request |
Date filter format | Date filters must be valid parseable dates (e.g., "January 1, 2026" or "2026-03-15"); bare numbers or non-date strings are rejected |
Attachment save path restrictions |
|
Attachment count limit |
|
IMAP attachment fetch size |
|
Mail.app <blockquote> wrapping on macOS 15+ (workaround in v1.6.0)
On macOS 15+ Mail.app wraps AppleScript-injected message bodies in
<blockquote type="cite"> under the Apple-Mail-URLShareWrapperClass template,
so mail sent via the default applescript transport renders to recipients as
quoted/forwarded content (Apple radar FB11734014, open since Ventura, no
fix). Since v1.6.0, send-email accepts transport: "smtp" to bypass Mail.app
and send clean MIME directly — see SMTP transport. The
AppleScript path is still the default and still exhibits Apple's wrapping.
(#12)
Reply / Forward from Background Processes (Fixed in v1.4.0)
Prior to v1.4.0, reply-to-message and forward-message would send messages with empty body text when the MCP server ran as a background process (e.g., spawned via execSync from Node.js, which is how Claude Code invokes it).
Root cause: The AppleScript reply msg with opening window command creates a GUI compose window asynchronously. When set content runs immediately after, the window may not be ready, and the content assignment is silently ignored. Delays (delay 1, delay 2) were unreliable — the compose window's readiness depends on system load, Mail.app state, and whether the process has GUI access.
Fix: Replaced with opening window with without opening window for both reply and forward commands. With this approach, set content works immediately and reliably from background processes. In-Reply-To and References headers are still set correctly by Mail.app, and no GUI compose window is opened.
Update (v2.5.0): when SMTP is configured, reply-to-message and forward-message now prefer clean direct SMTP instead of AppleScript — the same prefer-direct model as send-email. Replies are threaded with RFC 5322 In-Reply-To/References headers built from the original message; forwards start a new conversation. With transport omitted, the AppleScript without opening window path above is used only when SMTP is not configured or a draft is requested. Since v2.17.8, a selected SMTP path returns source/configuration/threading errors instead of silently falling back; explicit transport: "applescript" remains available.
See #7 for full details and the list of approaches that were tested.
Backslash Escaping (Important for AI Agents)
When sending content containing backslashes (\) to this MCP server, you must escape them as \\ in the JSON parameters.
Why: The MCP protocol uses JSON for parameter passing. In JSON, a single backslash is an escape character. To include a literal backslash in content, it must be escaped as \\.
Correct — email containing a shell path with an escaped space:
{
"to": ["colleague@company.com"],
"subject": "File Location",
"body": "Run: cp ~/Library/Mobile\\ Documents/report.pdf ~/Desktop/"
}→ arrives as: Run: cp ~/Library/Mobile\ Documents/report.pdf ~/Desktop/
In a JSON string literal, \\ — two characters — denotes one literal backslash. Four backslashes (\\\\) denote two literal backslashes, so send those only when the text genuinely contains \\.
Incorrect — the unescaped backslash makes this invalid JSON:
"body": "Run: cp ~/Library/Mobile\ Documents/report.pdf ~/Desktop/"\ (backslash-space) is not a valid JSON escape sequence, so the call is rejected — or, with a laxer parser, the backslash is silently dropped.
Common patterns requiring escaping:
Shell escaped spaces:
Mobile\ Documents→Mobile\\ Documentsin JSONRegex patterns:
\d+→\\d+in JSONA literal double backslash:
\\→\\\\in JSON
If you see errors when sending emails with backslashes, double-check that backslashes are properly escaped in the JSON payload.
Troubleshooting
"Mail.app not responding"
Ensure Mail.app is not frozen
Try opening Mail.app manually
Restart the MCP server
"Permission denied"
macOS needs automation permission
Go to System Settings > Privacy & Security > Automation
Ensure your terminal/Claude has permission to control Mail
On a Mac that isn't set to US English, macOS words this refusal in the system language —
Not authorised to send Apple events to Mail. (-1743)on en-GB/en-AU/en-IE, and fully translated on fr/de/es. Before v2.17.10 the server didn't recognise those spellings, sohealth-check/doctorreportedpermissions: okand then blamed missing accounts ("No Mail accounts found. Set up an account in Mail.app first."). If you see that on a Mac whose Mail accounts are already configured, upgrade and re-rundoctor.
"Message not found"
Message may have been deleted or moved
Message IDs change if the message is moved between mailboxes
Use
search-messagesto find the current message ID"not found in the mailboxes scanned" with a list of mailboxes means the numeric-id scan skipped those because they hold more than
APPLE_MAIL_MAX_BYID_SCAN_MAILBOXmessages (default50000). The message may be in one of them: passaccount+mailboxto read it from there, or use itsimap:id (#270)."stopped … before reaching: …" lists the mailboxes the scan never got to (its ~9s budget ran out, or a mailbox's size count timed out and Mail.app was left busy finishing it). A timed-out mailbox is remembered for 7 days and skipped from then on, so retrying shortly usually covers the rest. "Mail.app did not answer within 3s" means Mail.app was still busy with such a count when the lookup started — retry in a little while.
"Mail.app is not running" — numeric-id reads need Mail.app open; the server never launches it for them.
imap:ids read over IMAP without Mail.app.
"... is present in more than one mailbox"
A bare numeric ID identifies a message only within a mailbox, and a label store (Gmail, iCloud) reports the same message under the same ID in
INBOX,ImportantandAll Mailat once. The server refuses rather than guessing which copy you meant.Fix it by running
list-messages/search-messageson the mailbox you actually want to act on, then using the IDs from that result — the operation is then scoped to that mailbox.It only affects IDs the server hasn't seen listed (carried over from an earlier session, or typed by hand).
imap:…IDs encode their own mailbox and never hit this.
search-messages says "Partial results" or skips a mailbox
This is expected for very large IMAP/Gmail mailboxes (e.g. Gmail's
All Mail,Important): Apple Mail can't scan them via AppleScript before timing out, so they're skipped and named in the result rather than silently returning empty.To search inside one, scope the call with
mailboxand adateFrom/dateTowindow.Raise or disable the threshold with
APPLE_MAIL_MAX_SEARCH_MAILBOX(default5000;0disables the guard) — note that disabling it can make a single search take minutes.A
Partial resultswarning means coverage was incomplete; it is not a confirmed "no such mail."
"Account not found"
Account names must match exactly (case-sensitive)
Use
list-accountsto see exact account names
"Failed to send email"
Check your network connection
Verify Mail.app can send emails manually
Check if the account is configured correctly in Mail.app
"invalid outputSchema … unsupported dialect" — every tool is refused
Full text:
Tool '<name>' has an invalid outputSchema: JSON Schema declares an unsupported dialect ("$schema": "http://json-schema.org/draft-07/schema#"). The default validator supports JSON Schema 2020-12 only.The server connects, but no tool is usable.Upgrade to 2.10.12 or later. Earlier versions advertised their tool schemas in JSON Schema draft-07 (the MCP SDK's converter default); MCP has since standardized on 2020-12 and clients reject anything else. 2.10.12 normalizes every advertised
inputSchema/outputSchemato 2020-12 on the way out. See issue #147.Nothing to configure — restart your host app after upgrading so it re-reads the tool list.
apple-mail server fails to connect when run from a clone
The root
.mcp.jsonresolves its entrypoint via${CLAUDE_PROJECT_DIR:-.}/build/index.js. Launchclaudefrom inside the repo directory —CLAUDE_PROJECT_DIRonly resolves to the repo root in that case; the bare.fallback uses the launching shell's working directory and will point at the wrong place otherwise.If you've been editing the source, rerun
npm run build— the server isbuild/index.js, and the committed bundle only reflects your changes after a rebuild.Run
claude mcp listto check status. If you see a conflicting scopes warning forapple-mail, you have it registered at more than one scope; project-scope wins. See Running from a clone for how scope precedence resolves.If
claude mcp get apple-mailshows ⏸ Pending approval, approve the project-scope server (Claude Code prompts on startup, or run it again after approving).
Development
This repo is pnpm-only — package.json's preinstall guard hard-fails an npm install, because npm resolves off-lockfile and the committed bundle would then mismatch CI.
corepack enable && pnpm install --frozen-lockfile # Install dependencies
pnpm run build # Typecheck, then bundle src/index.ts + src/cli.ts into build/ (esbuild)
pnpm test # Run unit tests
pnpm run test:integration # Run integration tests (requires Mail.app)
pnpm run test:all # Run all tests (unit + integration)
pnpm run lint # Check code style
pnpm run format # Format codeAuthor
Rob Sweet - President, Superior Technologies Research
A software consulting, contracting, and development company.
Email: rob@superiortech.io
GitHub: @sweetrb
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Related Projects
Part of a family of macOS MCP servers:
apple-notes-mcp — MCP server for Apple Notes (create, search, update, and export notes)
apple-numbers-mcp — MCP server for Apple Numbers (read and write .numbers spreadsheets)
apple-photos-mcp — MCP server for Apple Photos (query metadata and export originals)
Recurring macOS permission prompts
If macOS keeps re-prompting for Full Disk Access or Automation for node (often after a brew upgrade), see docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md — the fix is to run this server under the official, Developer-ID-signed Node so the grant survives Node updates.
Available Tools
52 toolsbatch-delete-messagesA
Use when: deleting multiple messages in one call (1–100 ids; moves them to Trash). Returns: counts of how many were deleted and how many failed, plus the distinct reasons for any failures. Do not use when: deleting just one (use delete-message) or filing messages away (use batch-move-messages). Safety: destructive and applies to many messages at once — require explicit user confirmation, and search-messages/list-messages first to confirm every id is correct before deleting. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| sourceAccount | No | Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing. | |
| sourceMailbox | No | Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| errors | No | |
| failed | No | |
| mailbox | No | |
| success | No | |
| countDelta | No | |
| errorsTruncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully carries the transparency burden. It discloses that messages are moved to Trash, returns counts and failure reasons, warns it is destructive, requires explicit user confirmation, and explains the subtle id-scoping rule (ambiguous ids are 'refused, not guessed'). No contradiction with annotations since none exist.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is organized into clear labeled sections ('Use when', 'Do not use when', 'Safety'), each sentence earning its place. It is slightly longer than necessary—some redundancy between 'moves them to Trash' and 'destructive'—but every detail serves a purpose, making it highly scannable and dense.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the moderate complexity from the bulk operation, scoping rules, and failure modes, the description covers all essentials: what it does, what it returns, when not to use it, and critical safety/precondition steps. It does not explicitly state idempotency or concurrency behavior, but nothing important is missing for this tool's usage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 67% (sourceAccount and sourceMailbox have descriptions, ids does not). The description adds substantial meaning to the 'ids' parameter (1–100, mailbox-scoped) and explains how sourceMailbox/sourceAccount pin ids to a mailbox and how ambiguous matches are handled, going well beyond the schema's basic field descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'deleting multiple messages in one call (1–100 ids; moves them to Trash).' It uses a specific verb-resource pairing and explicitly distinguishes itself from siblings by naming 'delete-message' (single) and 'batch-move-messages' (filing), making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit 'Use when' and 'Do not use when' guidance, naming specific alternative tools (delete-message, batch-move-messages) and instructing to 'search-messages/list-messages first to confirm every id is correct before deleting.' This is model guidance for an agent.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
batch-flag-messagesA
Use when: flagging multiple messages (1–100 ids) in one call, optionally with a color (red/orange/yellow/green/blue/purple/gray).
Returns: counts of how many were flagged and how many failed.
Do not use when: flagging just one (use flag-message) or removing flags (use batch-unflag-messages). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.
Note: the color is applied on both routes — AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads — so a mixed batch of numeric and imap: ids all end up colored, and search-messages' flagColor filter finds the whole set again later regardless of mailbox.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| color | No | Optional flag color (Apple Mail palette: red, orange, yellow, green, blue, purple, gray — 'grey' accepted). Omit for Mail's default flag. The color is applied on both routes: AppleScript sets the flag index, and IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads — so a smart mailbox keyed on flag color matches either way. | |
| sourceAccount | No | Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing. | |
| sourceMailbox | No | Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| errors | No | |
| failed | No | |
| mailbox | No | |
| success | No | |
| errorsTruncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so well: it discloses the ambiguous-id refusal policy, the sourceMailbox/sourceAccount pinning requirement, and the dual-route color mechanism (AppleScript flag index vs IMAP $MailFlagBit keywords). It omits auth/permission requirements and atomicity guarantees, but covers the behaviors that most affect correct invocation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the Use-when/Returns/Do-not-use-when structure, so the actionable content comes first and the longer Note is relegated to the end. The Note is somewhat dense but each clause carries distinct technical information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a non-annotated batch mutation tool with an output schema, this is close to complete: usage triggers, exclusions, id-format handling, parameter pinning, and return semantics are all covered. Only error handoff and permission prerequisites are absent, which is a modest gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75%, and the description adds real meaning beyond it — the pinning semantics of sourceMailbox/sourceAccount (which mailbox the numeric ids came from, and that ambiguity is refused rather than guessed) and that color applies on both routes. This compensates for the remaining schema gaps.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (flag) plus resource (messages) and batch scope (1-100 ids), and explicitly distinguishes itself from flag-message and batch-unflag-messages. An agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit 'Use when' and 'Do not use when' clauses naming the exact alternatives (flag-message for single, batch-unflag-messages for removal) and directs the agent to search-messages/list-messages first to obtain ids. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
batch-mark-as-readA
Use when: marking multiple messages (1–100 ids) as read in one call. Returns: counts of how many were marked read and how many failed. Do not use when: marking just one (use mark-as-read) or marking unread (use batch-mark-as-unread). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| sourceAccount | No | Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing. | |
| sourceMailbox | No | Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| errors | No | |
| failed | No | |
| mailbox | No | |
| success | No | |
| errorsTruncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and discloses return values ('counts of how many were marked read and how many failed') and an important edge case ('an id that matches in several mailboxes is refused, not guessed'). It does not mention idempotency or auth, but the core behavioral traits are covered.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with labeled sections ('Use when', 'Returns', 'Do not use when') and a concise final instruction. Every sentence serves a purpose without unnecessary padding or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's parameter count and output schema, the description provides complete contextual guidance: usage conditions, alternatives, return counts, prerequisites, and ambiguous-id handling. It is sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema lacks a description for the 'ids' parameter and only partially covers sourceAccount/sourceMailbox. The description adds essential meaning by explaining that ids come from search-messages/list-messages and clarifying that sourceMailbox/sourceAccount is needed to pin numeric ids to a specific mailbox.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states the verb and resource: 'marking multiple messages (1–100 ids) as read in one call.' It also distinguishes itself from siblings by naming 'mark-as-read' for single messages and 'batch-mark-as-unread' for unread.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes explicit 'Use when' and 'Do not use when' clauses with alternative tool names, and provides a clear prerequisite: 'Get the ids from search-messages or list-messages first.' It also explains the sourceMailbox/sourceAccount requirement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
batch-mark-as-unreadA
Use when: marking multiple messages (1–100 ids) as unread in one call. Returns: counts of how many were marked unread and how many failed. Do not use when: marking just one (use mark-as-unread) or marking read (use batch-mark-as-read). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| sourceAccount | No | Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing. | |
| sourceMailbox | No | Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| errors | No | |
| failed | No | |
| mailbox | No | |
| success | No | |
| errorsTruncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Since no annotations are provided, the description carries full responsibility. It discloses the return value ('counts of how many were marked unread and how many failed'), the failure/refusal behavior for ambiguous ids ('an id that matches in several mailboxes is refused, not guessed'), and the importance of sourceMailbox/sourceAccount for pinning ids. This goes beyond the schema and provides critical runtime behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and well-organized, with clear 'Use when', 'Returns', 'Do not use when', and 'Pass' sections. Every sentence adds either usage guidance, a return detail, or parameter semantics, with no filler. It is front-loaded and scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a batch tool with 3 parameters and an output schema, the description is complete. It explains when to use, what to expect in the return, how to obtain inputs, and how to disambiguate ids. It even handles edge cases like ambiguous mailboxes. The presence of an output schema reduces the need to describe return structure, but the description still provides the essential counts summary.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema description coverage is 67%, but the description compensates by explaining the ids parameter's numeric/IMAP format and the purpose of sourceAccount/sourceMailbox. It clarifies that sourceAccount alone pins nothing and that sourceMailbox must be paired with it, and that imap ids ignore the mailbox scope. This adds meaningful semantic context beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb and resource: 'marking multiple messages (1–100 ids) as unread in one call.' It also distinguishes from siblings by explicitly mentioning when to use mark-as-unread for single messages and batch-mark-as-read for the read counterpart. This is a specific, unambiguous purpose.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description begins with an explicit 'Use when' condition and provides a 'Do not use when' clause with direct references to alternative tools. It also gives a prerequisite step ('Get the ids from search-messages or list-messages first'), which is valuable guidance for correct usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
batch-move-messagesA
Use when: moving multiple messages (1–100 ids) into the same destination mailbox/folder in one call, e.g. bulk archiving. Returns: counts of how many were moved and how many failed, plus the distinct reasons for any failures. Do not use when: moving just one (use move-message) or deleting (use batch-delete-messages). Use list-mailboxes to confirm the destination name exists. Safety: moves many real messages at once — confirm the destination mailbox, and search-messages/list-messages first to confirm the ids. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from — not the destination) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| account | No | Account containing the destination mailbox | |
| mailbox | Yes | ||
| sourceAccount | No | Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing. | |
| sourceMailbox | No | Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| errors | No | |
| failed | No | |
| mailbox | No | |
| success | No | |
| countDelta | No | |
| errorsTruncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description bears full responsibility for behavioral disclosure. It warns that the operation moves many real messages, explains that ambiguous ids are refused rather than guessed, describes return counts and failure reasons, and explains the sourceMailbox/sourceAccount scoping semantics.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with labeled sections (Use when, Returns, Do not use when, Safety) and every sentence adds useful information. It is longer than average but each part earns its place given the complexity and safety considerations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given there are 5 parameters, no annotations, and the high-stakes mutating nature of the operation, the description provides comprehensive context: when to use, when not to use, prerequisites, return values, safety warnings, and precise parameter semantics. It is sufficient for correct tool invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema covers only 60% of parameters, but the description significantly compensates. It clarifies the distinction between destination mailbox and sourceMailbox, requires sourceAccount pairing, explains imap: id behavior is ignored for source scoping, and clarifies that ids must be pinned to the source mailbox.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the operation: moving multiple messages (1–100 ids) into the same destination mailbox/folder in one call. It explicitly references the tool name's scope and distinguishes it from related tools by exclusion.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use guidance, concrete when-not-to-use cases with alternative tool names (move-message, batch-delete-messages), and prerequisite advice to verify destination with list-mailboxes and confirm ids with search/list-messages.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
batch-unflag-messagesA
Use when: removing flags from multiple messages (1–100 ids) in one call. Returns: counts of how many were unflagged and how many failed. Do not use when: unflagging just one (use unflag-message) or adding flags (use batch-flag-messages). Get the ids from search-messages or list-messages first. Pass sourceMailbox/sourceAccount (the mailbox you listed the ids from) so each numeric id is pinned to that mailbox; an id that matches in several mailboxes is refused, not guessed.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes | ||
| sourceAccount | No | Account the numeric ids were listed from. Required when sourceMailbox is supplied; on its own it pins nothing. | |
| sourceMailbox | No | Mailbox the numeric ids were listed from (e.g. 'INBOX'). Must be paired with sourceAccount to form an unambiguous scope. Ignored for imap: ids. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| errors | No | |
| failed | No | |
| mailbox | No | |
| success | No | |
| errorsTruncated | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries full burden. It discloses return counts (unflagged and failed), the requirement for sourceMailbox/sourceAccount to pin numeric ids, and that ambiguous ids are refused rather than guessed. This is rich behavioral context beyond what the schema provides.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, front-loaded with the primary use case, and each sentence earns its place. No redundancy or filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a batch mutation tool with an output schema, the description covers usage, alternatives, parameter disambiguation, and return behavior. It is sufficiently complete for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 67%, with sourceAccount and sourceMailbox already described. The description adds crucial semantics for these parameters (pinning, refusal behavior) and clarifies the ids use case (1-100 messages). While it doesn't detail id format, the schema's pattern handles that.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states the tool removes flags from multiple messages (1-100 ids) in one call, with a specific verb and resource. Explicitly distinguishes from siblings by naming unflag-message for single and batch-flag-messages for adding flags.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' guidance with alternative tool names. Also instructs to get ids from search-messages or list-messages beforehand, giving clear prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create-draftA
Use when: composing an email the user should review in Mail.app before sending — the safe default for any new message (to/cc/bcc are arrays, optional attachments). With inReplyTo/references the draft is threaded: it is composed by this server and filed over IMAP into the account's Drafts mailbox (Mail.app's AppleScript cannot set those headers), so an IMAP account is required. Returns: a confirmation that the draft was created, with recipients and attachment count; for a threaded draft also its Drafts mailbox, account and Message-ID (transport: imap). Do not use when: the user has already confirmed they want it sent now (use send-email), or is replying to an existing message (use reply-to-message with send=false — it derives recipients, threading and the quote and takes attachments). Safety: low risk — creates a draft only and sends nothing; the user must open Mail.app and send it themselves.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | CC recipients | |
| to | Yes | ||
| bcc | No | BCC recipients | |
| body | Yes | ||
| account | No | Account to create draft in: a Mail account name or an exact sender address, resolved and verified before the draft is saved (no match or a different sender → error, no draft). With inReplyTo/references this must name a configured IMAP account (label or login); when omitted, the IMAP account matching the SMTP identity, else the only IMAP account, is used. | |
| subject | Yes | ||
| inReplyTo | No | Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you. | |
| references | No | Thread chain, oldest first: the parent's own References followed by the parent's Message-ID (each an angle-bracketed token). Emitted verbatim, never RFC 2047-encoded. | |
| attachments | No | Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each. | |
| composition | No | Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| from | No | AppleScript draft: the sender Mail.app used |
| account | No | Threaded draft only: IMAP account it was filed in |
| mailbox | No | Threaded draft only: Drafts mailbox path |
| inReplyTo | No | |
| messageId | No | Threaded draft only: the draft's Message-ID |
| transport | No | |
| recipients | No | |
| references | No | |
| attachmentCount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the full burden and does so well: it discloses that a threaded draft is filed over IMAP and therefore needs an IMAP account, that the AppleScript path cannot set those headers, the transport used, the required desktop/permission state for composition, and that failure never falls back or sends an unverified message. It also states the risk profile directly ('creates a draft only and sends nothing').
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (Use when / Returns / Do not use when / Safety) front-load the decision-relevant content. The sentences are dense and the Returns clause is somewhat verbose, but no sentence is wasted given the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 10-parameter mutation-adjacent tool with no annotations, the description covers purpose, alternatives, the IMAP threading constraint, permissions, failure semantics and a return summary. Even with an output schema present, the brief return description orients the caller correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 70%, and the description adds real meaning on top: recipient fields are arrays, attachments are optional, inReplyTo/references cause the draft to be threaded via IMAP rather than AppleScript, and account must name a configured IMAP account in that case. It does not cover every parameter (composition, attachment limits) in prose, but the schema documents those well.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('composing an email the user should review in Mail.app before sending') and frames it as the safe default for new messages. It is immediately distinguishable from send-email and reply-to-message, which are named as the alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'Use when' and 'Do not use when' clauses name both competing tools and the exact condition that selects each ('user has already confirmed they want it sent now' → send-email; 'replying to an existing message' → reply-to-message with send=false). It even explains why reply-to-message is preferable (derives recipients, threading and quote).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create-mailboxA
Use when: creating a new mailbox/folder in an account. Returns: a confirmation that the mailbox was created. Do not use when: renaming an existing one (use rename-mailbox) or deleting one (use delete-mailbox). Use list-mailboxes to see what already exists. Safety: creates a real folder in the mail account — confirm the name and target account first.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| account | No | Account to create the mailbox in |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description discloses that the tool 'creates a real folder' and includes a safety warning to confirm name and target account. It also states the return value is a confirmation. This covers the key side effect, though it omits details like permissions or duplicate handling.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the primary use case, followed by return info, exclusions, and a safety reminder. Every sentence is purposeful and no redundant text exists.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity and the presence of an output schema, the description covers creation, return value, alternatives, and safety. It could mention error cases or uniqueness requirements, but it is sufficiently complete for an agent to use it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema covers 'account' with a description but not 'name' (50% coverage). The description adds context by referencing 'name and target account' in the safety note and clarifying the purpose of the main parameter. It does not fully specify name constraints, but the schema's minLength provides some guidance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action: 'creating a new mailbox/folder in an account.' It distinguishes from sibling tools by explicitly naming rename-mailbox and delete-mailbox as alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when:' and 'Do not use when:' guidance, naming specific alternatives (rename-mailbox, delete-mailbox) and recommending list-mailboxes to check existing mailboxes. This gives the agent clear decision criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create-newsletter-smart-mailboxesA
Use when: auto-discovering newsletter/bulk senders in your INBOX(es) and (optionally) creating a dedicated smart mailbox per sender (named "NL: "). Defaults to a safe dry run that only proposes. Returns: the proposed or created smart mailboxes with their match scores. Do not use when: you already know the exact sender (use create-smart-mailbox) or want real folders (use create-mailbox). Safety: with dryRun=false it edits Apple Mail's SyncedSmartMailboxes.plist (backed up, atomic, existing entries preserved) and can create many smart mailboxes at once — review a dryRun first. It scans up to ~400 recent messages per inbox via AppleScript, which can be slow on large mailboxes.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | Look back this many days in INBOXes | |
| dryRun | No | If true (default), only propose; if false, actually create | |
| minCount | No | Minimum messages from sender in the period |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| dryRun | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully bears the behavioral disclosure burden. It reveals the default dry-run safety, the fact that dryRun=false edits Apple Mail's SyncedSmartMailboxes.plist with backup and atomicity, that existing entries are preserved, and that the scan can be slow on large mailboxes. This is exceptionally transparent for a mutation-capable tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with labeled sections (Use when, Returns, Do not use when, Safety), and every sentence provides valuable information. It is appropriately sized for a complex tool and front-loads the primary use case.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers purpose, usage alternatives, return values, safety, side effects, and performance. Given the tool's complexity (3 parameters, no annotations, output schema present), the description is complete and self-sufficient. Output schema exists so return format details are not required in the description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds some behavioral context for dryRun (safe proposal vs. actual creation) but does not significantly expand parameter semantics beyond what the schema already documents. The 'scans up to ~400 recent messages' detail adds performance context rather than clarifying parameter meaning.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: auto-discovering newsletter/bulk senders in inboxes and optionally creating a dedicated smart mailbox per sender. It uses specific verbs and names the resource (INBOX(es) and smart mailboxes), and it distinguishes itself from create-smart-mailbox and create-mailbox.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly provides 'Use when' and 'Do not use when' sections, naming alternative tools (create-smart-mailbox for known senders, create-mailbox for real folders). This gives clear contextual guidance for when to select this tool vs. siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create-ruleA
Use when: creating a new Mail rule with one or more conditions (field/operator/value) and at least one action (markRead, markFlagged, delete, or moveTo). Set matchAll to require all conditions vs. any. Returns: a confirmation naming the rule and its condition count. Do not use when: toggling an existing rule (use enable-rule / disable-rule) or removing one (use delete-rule). Use list-rules to avoid duplicating an existing rule. Safety: creates a rule that automatically acts on real mail (including delete/move actions) on an ongoing basis — confirm the conditions and actions with the user before calling.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| actions | Yes | ||
| enabled | No | Enable immediately; defaults to false so the rule must be reviewed first | |
| matchAll | No | ||
| conditions | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | No | |
| created | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite no annotations, the description discloses that creating a rule has ongoing safety implications, including automatic action on real mail and the need to confirm with the user. It also mentions the default enabled state from the schema, adding behavioral context beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized with 'Use when', 'Returns', 'Do not use when', and 'Safety' sections, each adding unique value. It is concise yet comprehensive, with minimal fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complex nested schema and no annotations, the description covers safety, usage boundaries, and return format, making it complete for an agent to use without additional external context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is low (20%) and the description explains key fields like matchAll, actions, and conditions, and references the enabled default. It could detail sub-fields (e.g., operators) but the core parameters are clarified.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states the tool creates a new Mail rule, with specifics on conditions and actions. It is clearly distinct from siblings like enable-rule and delete-rule. The purpose is explicit and actionable.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' scenarios, naming alternative tools (enable-rule, disable-rule, delete-rule, list-rules). This directly guides the agent on when to invoke this tool versus siblings, which is exemplary.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create-smart-mailboxA
Use when: creating an Apple Mail smart mailbox (a criteria-based virtual view) that matches a sender, subject, or body substring — works on German-localized macOS where AppleScript's smart-mailbox terms fail. Returns: confirmation of creation, or a note that a smart mailbox with that name already existed. Do not use when: creating a real folder (use create-mailbox). Safety: edits Apple Mail's SyncedSmartMailboxes.plist directly. It backs the file up (.bak) and writes atomically, and never rewrites your existing smart mailboxes. It does not quit Mail — quit Mail first for reliable results, since a running Mail may not show the new smart mailbox until relaunched and can overwrite plist edits it did not make.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| bodyContains | No | Match body (contains) | |
| fromContains | No | Match sender (From contains) | |
| subjectContains | No | Match subject (contains) |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| name | No | |
| alreadyExisted | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden, and it delivers: it discloses direct plist editing, backup creation (.bak), atomic writes, that existing smart mailboxes are not rewritten, and that Mail is not quit automatically. It also warns about Mail overwriting plist edits if running, which is valuable behavioral transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is structured with clear labeled sections: use when, returns, do not use, and safety. It is front-loaded with the core purpose, and every sentence adds distinct, useful information without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description fully covers invocation context, return behavior, exclusions, safety mechanisms, and operational prerequisites (quitting Mail). An output schema exists, so return-value detail is not required; remaining minor gaps like combined-criteria semantics do not undermine completeness.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema describes three of four params with 'contains' phrasing (75% coverage); the description adds the concept of 'substring' and groups them as sender/subject/body criteria. This provides some semantic clarification beyond the schema, though it does not give deep per-parameter details.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Use when: creating an Apple Mail smart mailbox (a criteria-based virtual view)' and names the specific action and resource. It clearly distinguishes from 'create-mailbox' for real folders, and specifies the matching criteria (sender, subject, body substring).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description includes explicit 'Use when' and 'Do not use when' instructions, citing 'create-mailbox' as the alternative for real folders. It also adds actionable context like German-localized macOS compatibility and the instruction to quit Mail first, giving the agent clear conditions for appropriate use.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete-mailboxA
Use when: deleting a mailbox/folder from an account. Returns: a confirmation that the mailbox was deleted. Do not use when: renaming it (use rename-mailbox) or deleting messages within it (use delete-message / batch-delete-messages). Safety: destructive — deleting a mailbox removes the folder and any messages it contains. Require explicit user confirmation and use list-mailboxes first to confirm the exact name.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | ||
| account | No | Account containing the mailbox |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses destructive consequences, removal of contained messages, requirement for explicit user confirmation, and prerequisite to list-mailboxes first. This is highly transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, front-loaded with use cases, and structured into clear sections (Use when, Returns, Do not use when, Safety). Every sentence adds value without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive tool with no annotations, the description covers purpose, usage boundaries, return value, safety, prerequisites, and needed confirmation. It is fully contextual.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 50% (account has description, name does not). The description adds some context (e.g., exact name confirmation via list-mailboxes) but does not deeply elaborate on parameters. Since coverage is moderate and some extra meaning is added, a 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool deletes a mailbox/folder, uses a specific verb+resource, and distinguishes from siblings by naming alternatives like rename-mailbox and delete-message/batch-delete-messages.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly provides when to use ('deleting a mailbox/folder'), what it returns, and when not to use (renaming, deleting messages), naming specific alternative tools. This is excellent guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete-messageA
Use when: deleting a single message by id (moves it to Trash). Returns: a confirmation that the message was deleted. Do not use when: deleting several at once (use batch-delete-messages) or just filing it away (use move-message). Safety: destructive — require explicit user confirmation, and search-messages/list-messages first to confirm you have the right id before deleting.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No | |
| countDelta | No | |
| verification | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it flags the destructive nature, requires explicit user confirmation, and advises confirmation of the id. It doesn't cover all edge cases (e.g., attachments), but it covers the critical safety aspects. Slightly more detail on reversibility would push it higher.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured using labeled sections (Use when, Returns, Do not use when, Safety). Every sentence adds value, with no fluff or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter destructive action with an output schema, the description covers all necessary context: when to use, what happens, and safety checks. It also differentiates from similar tools, making it complete for agent use.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is only one parameter, id. The description explicitly says 'by id' and clarifies it's a message id. The schema provides a pattern, which helps further. The description adds enough context for the agent to understand the parameter's role without redundancy.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it deletes a single message by id and moves it to Trash. It distinguishes from siblings by explicitly pointing to batch-delete-messages and move-message, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance with 'Use when' and 'Do not use when', naming alternatives (batch-delete-messages, move-message). It also recommends verifying the id first, which is actionable for agent decision-making.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete-ruleA
Use when: permanently removing a Mail rule by name. Returns: a confirmation that the rule was deleted. Do not use when: you only want to pause it (use disable-rule) or create one (use create-rule). Safety: destructive — the rule is removed permanently. Require explicit user confirmation and use list-rules first to confirm the exact name.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | No | |
| deleted | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses that the rule is removed permanently, warns it is destructive, and requires explicit user confirmation plus a preliminary list-rules call. This is strong transparency, though it does not mention error handling or behavior for non-existent names. The output schema likely covers return value format.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tightly formatted into four labeled segments (Use when, Returns, Do not use when, Safety), each earning its place. It is front-loaded with the primary use case and avoids redundant content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple (one parameter, no nested objects) and has an output schema to document return values. The description covers when, when-not, prerequisites, and safety completely. No gaps are apparent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides only a 'name' field with minLength. The description clarifies that name refers to the rule name and emphasizes using list-rules to confirm the exact name, adding practical meaning. It does not discuss case sensitivity, but for a single parameter it is sufficiently helpful.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Use when: permanently removing a Mail rule by name,' which clearly specifies the verb and resource. It further differentiates from sibling tools by explicitly mentioning disable-rule and create-rule, making the purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit use and non-use guidance, naming alternatives (disable-rule, create-rule) and recommending list-rules first to confirm the exact name. This fully addresses when to use the tool versus alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete-smart-mailboxA
Use when: deleting an Apple Mail smart mailbox (virtual view) by name. Returns: confirmation of deletion. Do not use when: deleting a real folder (use delete-mailbox) or messages (use delete-message / batch-delete-messages). Safety: destructive — removes the smart mailbox from Apple Mail's SyncedSmartMailboxes.plist. It backs the file up (.bak) and writes atomically, preserving every other smart mailbox, but the removal is not undoable in-app. Confirm the exact name with list-smart-mailboxes first, and quit Mail first for reliable results.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It clearly states the tool is destructive, modifies SyncedSmartMailboxes.plist, creates a backup, writes atomically, preserves other smart mailboxes, and that removal is not undoable in-app. This exceeds expectations for transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is structured with clear labels (Use when, Returns, Do not use when, Safety), making it easily scannable. Every sentence provides necessary information without redundancy, and the most critical usage guidance appears first.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This destructive tool has no annotations, but the description covers prerequisites, safety, file-level behavior, and exclusions. It also names the confirmation tool and the need to quit Mail, making it complete for safe invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema description coverage is 0%, there is only one parameter ('name') whose meaning is self-evident. The description adds value by emphasizing the name must be exact and recommending list-smart-mailboxes to confirm it, which indirectly clarifies the parameter's expected value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('deleting') and resource ('Apple Mail smart mailbox (virtual view) by name'), clearly distinguishing this tool from siblings. It also explicitly names alternatives for real folders and messages, leaving no ambiguity about its scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, naming alternative tools (delete-mailbox, delete-message, batch-delete-messages). It also instructs to confirm the exact name with list-smart-mailboxes and quit Mail first, giving concrete prerequisites.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete-templateA
Use when: permanently removing a saved email template by id. Returns: a confirmation that the template was deleted. Do not use when: you only want to view it (use get-template) or update it (use save-template with the existing id). Safety: destructive — removes the template from the on-disk store permanently. Require explicit user confirmation and use list-templates first to confirm the id.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and excels: it states the operation is destructive, removes permanently from the on-disk store, and requires explicit user confirmation. It also discloses the return type. This is comprehensive behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, uses clear logical sections (Use when, Returns, Do not use, Safety), and every sentence adds value. No fluff, well-structured, and easy to parse.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with one parameter, the description covers purpose, usage, alternatives, safety, parameter guidance, and return value. The output schema exists, so the description needn't detail return structure. All essential context is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and the only param is 'id' (string minLength 1). The description adds practical meaning by instructing to use list-templates first to confirm the id, giving context for how to obtain a valid value. It doesn't detail format, but the single simple parameter makes this adequate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action ('permanently removing a saved email template by id') and distinguishes from siblings by explicitly referencing get-template and save-template. The verb+resource combination is specific and unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, naming specific alternatives (get-template, save-template). It also advises using list-templates first to confirm the id, which is actionable and prevents misuse.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
disable-ruleA
Use when: turning off an existing Mail rule by name (without deleting it). Returns: a confirmation that the rule was disabled. Do not use when: turning a rule on (use enable-rule), creating one (use create-rule), or removing it permanently (use delete-rule). Use list-rules to confirm the exact rule name.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| name | No | |
| enabled | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It states the action (disable), clarifies it does not delete the rule, and mentions the return value (confirmation). However, it does not cover error cases (e.g., rule not found) or idempotency, which slightly limits transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise, front-loaded with the primary use case, and every sentence adds value. It avoids fluff and provides both guidance and return information in three short sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity tool with one parameter and an output schema, the description is largely complete. It covers purpose, usage, alternatives, and return value. The only gap is lack of error-handling details, but the presence of an output schema partially mitigates this.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides a simple 'name' string with no description, and schema coverage is 0%. The description compensates by explaining that the name refers to an existing rule and advising use of list-rules to confirm the exact name, giving practical meaning to the parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: turning off an existing Mail rule by name without deleting it. It explicitly distinguishes this from sibling tools like enable-rule and delete-rule, making the purpose unambiguous and easy to differentiate.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, listing alternatives for related operations (enable-rule, create-rule, delete-rule) and recommending list-rules to confirm the exact name. This is exemplary usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
doctorA
Use when: troubleshooting setup problems — diagnoses Mail.app automation permissions, account state, and the IMAP/SMTP backends with actionable remediation messages. Returns: a detailed diagnostic report (formatted text plus structured checks). Do not use when: you just want a quick up/down status (use health-check) or message counts (use get-mail-stats).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| checks | No | |
| healthy | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses the diagnostic (non-mutating) nature, the specific areas checked, and the return format (formatted text plus structured checks). It does not explicitly state 'read-only' or mention potential side effects, but the diagnostic framing strongly implies a safe analysis operation, which is sufficient for a zero-parameter tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences, front-loaded with 'Use when', and includes a return-value note and a clear 'Do not use when' with alternatives. Every sentence earns its place—no fluff or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's low complexity (zero parameters) and the presence of an output schema, the description adequately covers the purpose, target issues, return type, and exclusions. It provides enough context for an agent to select and invoke it confidently without further detail.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has 0 parameters, so the schema coverage is trivially 100%. Per the calibration rule, a 0-parameter tool receives a baseline of 4, and there are no additional parameter semantics needed since no inputs exist.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool diagnoses Mail.app automation permissions, account state, and IMAP/SMTP backends, which is a specific verb+resource+scope. It also distinguishes itself from siblings by naming alternatives like health-check and get-mail-stats, making its purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly provides both 'Use when' conditions (troubleshooting setup problems) and 'Do not use when' exclusions (quick up/down status, message counts), with named alternative tools (health-check, get-mail-stats). This gives clear decision guidance for when to invoke this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
enable-ruleA
Use when: turning on an existing Mail rule by name. Returns: a confirmation that the rule was enabled. Do not use when: turning a rule off (use disable-rule), creating one (use create-rule), or deleting one (use delete-rule). Use list-rules to confirm the exact rule name.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| name | No | |
| enabled | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Since no annotations are provided, the description must carry the transparency burden. It discloses that the tool turns on an existing rule and returns a confirmation. It stops short of covering idempotency (e.g., enabling an already-enabled rule) or error scenarios, but for this simple operation, the core behavior and outcome are clear.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact, using a clear 'Use when' / 'Do not use when' structure. Every sentence earns its place: purpose, return value, exclusions, and a useful hint about list-rules.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with a single simple parameter and an output schema, the description fully covers what the tool does, when to use it, what it returns, and how to verify the parameter. Sibling references further complete the contextual picture.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema has 0% description coverage, so the description must compensate. It explains that the 'name' parameter is the rule's exact name through 'turning on an existing Mail rule by name' and 'confirm the exact rule name.' This adds meaningful semantic detail beyond the raw schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: 'turning on an existing Mail rule by name.' This uses a specific verb (turning on) and resource (existing Mail rule), and distinguishes it from sibling tools like disable-rule, create-rule, and delete-rule.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' guidance, naming alternatives for opposite or related operations. Also advises using list-rules to verify the exact rule name, which is actionable context for correct invocation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch-attachmentA
Use when: retrieving an attachment's raw bytes inline as base64 (by message id and attachmentName), e.g. to process its contents without keeping a file. Returns: the attachment's bytes base64-encoded, with its size and (for IMAP) MIME type. Do not use when: you don't know the attachment name (use list-attachments first) or you just want it saved to disk (use save-attachment). Safety: leaves no file behind, but the AppleScript path is not disk-free — Mail writes the attachment into a private temp directory, which is read back and then deleted. Needs no Full Disk Access either way: Mail performs that write under the Automation grant, and this server never reads the mail store itself.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| attachmentName | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| bytes | No | |
| mimeType | No | |
| contentBase64 | No | |
| attachmentName | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries full responsibility for disclosing side effects and requirements. It covers: no file left behind (but temp file used and deleted), no Full Disk Access needed, and explains the underlying AppleScript temp-directory mechanism. It also states the return format (base64 bytes with size and MIME type), giving a transparent behavioral contract.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear sections: 'Use when', 'Returns', 'Do not use when', and 'Safety'. It is front-loaded with the primary usage, and each sentence contributes unique value — no fluff or repetition. The length is appropriate for the level of detail, covering purpose, exclusions, output, and safety in a compact manner.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity and the presence of an output schema (which handles return structure), the description goes beyond the minimum. It explains when to use, when not to use with alternatives, side effects (temp file handling), permission requirements, and return content. It effectively addresses all essential context for correct invocation, including a dependency on list-attachments for unknown names.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema coverage is 0%, but the description adds meaning by stating 'by message id and attachmentName' and clarifying that attachmentName must be known (with a suggestion to use list-attachments). It doesn't elaborate on the id pattern, but the schema already has a permissive regex. The description gives sufficient semantic context to understand what each parameter represents and how to obtain the correct value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states the tool's purpose: 'retrieving an attachment's raw bytes inline as base64 (by message id and attachmentName)'. It clearly differentiates from siblings by explicitly naming alternatives: 'use save-attachment' for saving to disk and 'use list-attachments first' when the attachment name is unknown. The verb+resource+scope is specific and unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance: it starts with 'Use when:' and includes a dedicated 'Do not use when:' section that names concrete alternatives for both exclusions. This directly addresses when to use this tool versus list-attachments and save-attachment, which are the most relevant siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
flag-messageA
Use when: flagging a single message (by id), optionally with a color (red/orange/yellow/green/blue/purple/gray). Returns: a confirmation that the message was flagged (and the color, when applied). Do not use when: flagging several at once (use batch-flag-messages) or removing a flag (use unflag-message). Get the id from search-messages or list-messages first. Note: the color is applied on both routes — AppleScript sets the flag index, IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads — and search-messages' flagColor filter finds colored messages again later, so a color works as a cross-mailbox label.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| color | No | Optional flag color (Apple Mail palette: red, orange, yellow, green, blue, purple, gray — 'grey' accepted). Omit for Mail's default flag. The color is applied on both routes: AppleScript sets the flag index, and IMAP writes the equivalent $MailFlagBit0/1/2 keywords Mail.app reads — so a smart mailbox keyed on flag color matches either way. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No | |
| color | No | |
| colorApplied | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden, and it does substantial work: it discloses the return value (a confirmation plus color), the dual delivery mechanism (AppleScript flag index vs IMAP $MailFlagBit keywords), and the downstream effect that search-messages' flagColor filter will find colored messages. It does not address permission requirements or failure modes, which keeps it short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the 'Use when' clause and tightly organized into Use/Returns/Do-not-use/Note blocks. However the color-on-both-routes explanation is repeated almost verbatim between the description body and the color parameter's schema description, which is mild redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter mutation tool with no annotations, the description covers triggers, alternatives, id sourcing, return shape, and the persistence semantics of the color. An output schema exists, so return values need only cursory mention, which it provides. Only auth/permission context is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 50%; the color parameter is richly documented in-schema, while id has only a pattern and no prose. The description compensates by listing the palette, pointing to id sources, and explaining what the color semantically enables (cross-mailbox labeling). This adds meaning beyond the raw enum but is partly duplicated from the schema's own color description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (flag) and resource (a single message, by id) with the optional color dimension called out. It explicitly names the siblings it is not — batch-flag-messages for bulk and unflag-message for removal — so an agent can distinguish it without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Uses an explicit 'Use when' / 'Do not use when' structure, names both alternative tools and the condition that selects each, and even supplies the prerequisite for obtaining the id (search-messages or list-messages). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
forward-messageA
Use when: forwarding an existing message (by id) to new recipients (to is an array), with an optional body to prepend and optional extra attachments (same rules as send-email). Set send=false to save as a draft. Returns: a confirmation that the message was forwarded or saved as a draft, with the attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy). Do not use when: replying to the sender/recipients (use reply-to-message) or composing a new message (use send-email / create-draft). Safety: with the default send=true this SENDS real email immediately and cannot be unsent — require explicit user confirmation of the recipients and any prepended body, or pass send=false to let the user review.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| to | Yes | ||
| body | No | Optional message to prepend (plain text) | |
| send | No | Send immediately (false = save as draft) | |
| account | No | For a numeric id, the account it was listed from; pair with mailbox. Omit for an imap: id. | |
| mailbox | No | For a numeric id, the exact source mailbox path; pair with account. A miss never searches elsewhere. | |
| transport | No | SMTP sends clean MIME and preserves reply threading; requires SMTP configuration. AppleScript uses Mail.app and may quote-wrap the new body on macOS 15+. Omit to prefer configured SMTP when sending; drafts use AppleScript. A selected SMTP transport never silently falls back. SMTP with send=false is rejected. | |
| attachments | No | Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each. | |
| composition | No | Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No | |
| sent | No | |
| sentCopy | No | SMTP only: whether a best-effort copy was filed to the account's Sent mailbox over IMAP (issue #220). Absent when no configured IMAP account matches the SMTP identity — not a failure, the feature simply wasn't engaged. |
| messageId | No | |
| transport | No | |
| recipients | No | |
| sentCopyError | No | Present only when sentCopy is false: why the Sent-folder copy failed. |
| attachmentCount | No | |
| sentCopyAlreadyFiled | No | Present (true) only when sentCopy is true because the Sent mailbox already held this Message-ID (e.g. Gmail's SMTP files its own copy), so none was appended. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does: it discloses that default send=true dispatches real, non-reversible email, requires explicit user confirmation of recipients and prepended body, and that send=false saves a reviewable draft. It also explains what the confirmation response contains (attachment count, SMTP sentCopy) and that failures do not silently fall back.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labelled sections (Use when / Returns / Do not use when / Safety) front-load the decision-relevant facts, with the dangerous default buried last as a deliberate emphasis. Sentences are dense with no filler, and the Returns line still adds information (sentCopy over SMTP) beyond the output schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter mutation tool with no annotations, the description covers purpose, alternatives, side effects, irreversibility, confirmation requirements, and the draft escape hatch; the remaining transport/composition intricacies are fully covered by schema descriptions and an output schema exists for return values.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 78% and the schema already documents transport, composition, account/mailbox and attachments in depth. The description adds useful signal the schema doesn't state plainly: 'to is an array', body is prepended plain text, attachments follow 'the same rules as send-email', and send=false means draft rather than send. It does not explain the numeric-vs-imap id pairing, which is left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('forwarding an existing message (by id) to new recipients') plus scope details (optional prepended body, optional extra attachments). It explicitly distinguishes itself from reply-to-message, send-email and create-draft, so an agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The 'Use when' / 'Do not use when' structure names the exact alternatives (reply-to-message for replies, send-email / create-draft for new mail) and the conditions that select them. The draft path (send=false) is also framed as a usage decision the agent must make.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-mail-statsA
Use when: you want aggregate mailbox statistics — total and unread message counts, recently-received counts (last 24h/7d/30d), and (for the all-accounts path) a per-account breakdown. Returns: totals, unread counts, recent-activity counts, and per-account figures. Do not use when: you only need a single unread number (use get-unread-count) or want to list the messages themselves (use list-messages / search-messages).
| Name | Required | Description | Default |
|---|---|---|---|
| account | No | Limit to one account; uses fast IMAP STATUS if that account is IMAP-configured |
Output Schema
| Name | Required | Description |
|---|---|---|
| recent | No | |
| account | No | |
| partial | No | |
| accounts | No | |
| perMailbox | No | |
| queueWaitMs | No | |
| totalUnread | No | |
| totalMessages | No | |
| failedAccounts | No | |
| recentlyReceived | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It discloses the fast IMAP STATUS optimization and the all-accounts path behavior. However, it does not explicitly state that it is read-only or has no side effects, though this is strongly implied by the purpose.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tightly structured with 'Use when', 'Returns', and 'Do not use when' sections. Every sentence earns its place, is concise, and key information is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one optional parameter, no annotations) and the presence of an output schema, the description covers the essentials: when to use, what it returns, and when not to use. No critical information is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already fully describes the account parameter (100% coverage). The description adds value by clarifying the 'all-accounts path' when the parameter is omitted, which is not explicit in the schema and helps the agent correctly choose whether to pass the parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: aggregate mailbox statistics including total/unread counts, recent activity counts, and per-account breakdown. It uses a specific verb and resource, and distinguishes itself from sibling tools like get-unread-count and list-messages.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'Use when' and 'Do not use when' sections provide clear context and explicitly name alternative tools (get-unread-count, list-messages, search-messages). This gives the agent precise decision criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-messageA
Use when: reading the full body of one message whose id you already have (numeric or imap:…); set preferHtml to get the HTML body instead of plain text. Returns: the message subject, body (plain text by default, HTML when preferHtml is true), its stable RFC Message-ID (rfcMessageId) for dedup/threading, and two dates: dateSent (the author's Date: header — survives a migration/re-import) and dateReceived (arrival in the mailbox; this is the one a migration resets). Tip: pass the mailbox+account you got the id from (e.g. from search-messages) to fetch it directly — required for reliable reads of large folders like "Sent Items", which otherwise time out. Do not use when: you don't yet have an id (use search-messages or list-messages first), you want the whole conversation (use get-thread), or you need the raw headers / Received: trace (use get-message-headers).
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| account | No | Account that holds the message. Pair with `mailbox` for a direct, scan-free fetch. | |
| mailbox | No | Mailbox that holds the message (e.g. "Sent Items"). Numeric ids are unique per mailbox; supplying this (with account) opens that mailbox directly instead of scanning every mailbox, which is required to read large folders like Sent Items without timing out. | |
| preferHtml | No | Return the HTML body (extracted from the message source) instead of plain text |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| body | No | |
| isHtml | No | |
| subject | No | |
| dateSent | No | ISO 8601 send time from the message's Date: header (Mail's `date sent`). Absent when the message carries no parseable Date: header, or when the value is more than 7 days later than dateReceived — a send time after arrival is not a real one. |
| dateReceived | No | ISO 8601 arrival time in the mailbox (IMAP INTERNALDATE / Mail's `date received`). A migration or re-import resets this; compare with dateSent. |
| rfcMessageId | No | Stable RFC 5322 Message-ID (angle brackets stripped); empty when the message has none |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral disclosure burden. It covers what the operation does (reads, does not mutate), the return fields (subject, body, rfcMessageId, dateSent, dateReceived), the meaning of the two dates, migration effects on dateReceived, and even a timeout behavior for large folders. This is rich, honest behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is structured into clear, purposeful sections: use-when, return contract, operational tip, and exclusions. Each sentence adds decision-relevant information, and the most important qualifier (id required) is front-loaded. Though a bit long, nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool of this complexity, the description is complete: it states prerequisites, tells the agent exactly what it will receive, informs about a non-obvious migration behavior, warns about a timeout risk, and routes to siblings for related but different needs. The presence of an output schema further reduces the burden, yet the description still goes beyond it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 75%, but the description substantially supplements it: it clarifies the two valid id forms (numeric or imap:...), explains when preferHtml changes the body format, and explains why pairing mailbox+account is valuable beyond the schema's descriptions. It even documents the otherwise undescribed 'id' parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb-resource pair ('reading the full body of one message') and clearly identifies the required prerequisite (an id you already have). It also explicitly contrasts itself with sibling tools like get-thread and get-message-headers, making the tool's purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' sections, naming concrete alternatives such as search-messages, list-messages, get-thread, and get-message-headers. It also gives a practical tip about passing mailbox and account for reliable reads of large folders, which is actionable routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-message-headersARead-only
Use when: you need a message's raw RFC 5322 headers — the author's Date: header (not the mailbox arrival time), Message-ID, In-Reply-To/References, the Received: hop trace, or any custom X- header — for a message whose id you already have (numeric or imap:…). Cheap: never downloads the body or attachments. Returns: the raw header block (text), every header as ordered {name, value} pairs with folding undone, and the decoded key fields: date (ISO 8601, from the Date: header), dateHeader (verbatim), dateReceived (mailbox arrival time — the value a migration or re-import resets, so compare it with date), messageId, subject, from, to, cc, replyTo, inReplyTo, references[], received[], backend (imap or applescript), and warnings[] when a malformed block was repaired. Tip: pass the mailbox+account you got the id from so a numeric id is fetched directly instead of scanning every mailbox. Do not use when: you want the body (use get-message), the conversation (use get-thread), or only the Message-ID (get-message already returns rfcMessageId).
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| account | No | Account that holds the message. Pair with `mailbox` for a direct fetch. | |
| mailbox | No | Mailbox that holds the message (numeric ids are unique per mailbox). With `account`, opens that mailbox directly instead of scanning every mailbox. |
Output Schema
| Name | Required | Description |
|---|---|---|
| cc | No | |
| id | No | |
| to | No | |
| raw | No | The raw header block, exactly as stored |
| date | No | ISO 8601 from the Date: header — the author's send time |
| from | No | |
| backend | No | Which backend read the headers: "imap" (the stored bytes over IMAP) or "applescript" (Mail's `all headers` property, which Mail renders itself and can alter) |
| headers | No | |
| replyTo | No | |
| subject | No | RFC 2047-decoded Subject: |
| received | No | Every Received: header, as written (first = last hop) |
| warnings | No | Repairs applied to a malformed header block, in plain words — e.g. a Date: value Mail.app dropped with the next header joined onto it. Absent when none fired; `raw` is never rewritten. |
| inReplyTo | No | |
| messageId | No | Bare RFC 5322 Message-ID |
| dateHeader | No | The Date: header verbatim |
| references | No | |
| headerCount | No | |
| dateReceived | No | ISO 8601 mailbox arrival time (INTERNALDATE / Mail's `date received`) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint, but the description adds critical behavioral context: it never downloads body/attachments ('Cheap'), it returns raw header text, ordered name/value pairs with folding undone, and decoded fields including dateReceived vs date (with explanation of mailbox arrival time vs Date header). It also discloses warnings[] for repaired malformed blocks. This is rich behavioral disclosure beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is longer than average but well-structured with clear sections (Use when, Returns, Tip, Do not use). Every sentence adds value; nothing is redundant. The purpose is front-loaded, and the 'Do not use' section is at the end. Minor deduction for length, but it's efficient given the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having an output schema (not shown), the description thoroughly covers what the tool returns, including decoded fields and warnings behavior. It also covers performance expectations, input requirements, and exclusions. An agent would have everything needed to invoke this tool correctly without additional research.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema covers 67% of parameters (account and mailbox have descriptions, id only has a pattern). The description explains the id formats (numeric or imap:...) and provides the tip that pairing mailbox+account enables direct fetch instead of scanning all mailboxes. This adds meaning beyond the schema's pattern, clarifying the relationship between parameters and their optimization effect.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise purpose: retrieving raw RFC 5322 headers, listing specific header types (Date, Message-ID, In-Reply-To, Received, X-). It clearly differentiates from siblings get-message (body) and get-thread (conversation), and even notes get-message returns rfcMessageId for the Message-ID case. This leaves no ambiguity about what the tool does.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states 'Use when' and 'Do not use when' with concrete alternatives (get-message, get-thread, get-message for rfcMessageId). It also gives a practical tip about passing mailbox+account for direct fetch. This is exemplary routing guidance that saves the agent from guessing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-message-rfc822A
Use when: you need a message's complete original RFC 822 bytes exactly as the IMAP server stores them — for archival, forensic review, evidence preservation, or a verifiable .eml — together with the IMAP identity that links back to the source (uid, uidValidity, internalDate, flags, RFC822.SIZE) and a SHA-256 of the acquired bytes. IMAP-only: needs an imap: id from list-messages/search-messages on an IMAP-configured account. Returns: the acquisition record (account, mailbox, uid, uidValidity, internalDate, flags, size, bytes, sha256, messageId, readMethod, backend, warnings) plus either contentBase64 — the bytes, base64-encoded, carried in structuredContent only — or savedPath when savePath was given. No decoding, charset conversion, line-ending change or MIME re-serialization is applied. Do not use when: you want readable content (use get-message), only the headers (use get-message-headers), or one attachment (use fetch-attachment / save-attachment); or when the id is numeric — Mail.app's AppleScript bridge exposes its own rendering, not the stored bytes, so there is no AppleScript path. Safety: strictly read-only against the mailbox — EXAMINE + BODY.PEEK[], so \Seen is not set and no STORE/COPY/MOVE/APPEND/EXPUNGE is issued; no Mail.app, AppleScript or osascript involved. Inline results are capped at 6 MiB of raw bytes (maxBytes) to stay under the MCP stdio 10 MB message limit; larger messages need savePath, which creates exactly one new file (never overwrites, mode 0600) inside the configured allowed roots, up to 25 MiB.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | An imap: message id (numeric Mail.app ids are refused) | |
| fileName | No | File name to use with savePath (no path separators or '..'). Default: <account>-<mailbox>-uidv<uidValidity>-uid<uid>.eml | |
| maxBytes | No | Refuse (never truncate) a message larger than this many raw bytes. Default and inline maximum 6291456 (6 MiB); with savePath up to 26214400 (25 MiB) | |
| savePath | No | Directory inside the allowed roots to write the .eml into instead of returning base64; raises the ceiling to 25 MiB |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| uid | No | |
| size | No | RFC822.SIZE as reported by the server |
| bytes | No | Number of bytes acquired (and hashed) |
| flags | No | |
| sha256 | No | Hex SHA-256 over exactly the acquired bytes |
| account | No | |
| backend | No | |
| mailbox | No | |
| warnings | No | |
| messageId | No | Bare RFC 5322 Message-ID from ENVELOPE, for convenience; the authoritative copy is in the bytes |
| savedPath | No | |
| readMethod | No | The IMAP commands used |
| uidValidity | No | Mailbox UIDVALIDITY as a decimal string; with uid, the durable identity of the source message |
| internalDate | No | ISO 8601 IMAP INTERNALDATE (arrival), not the Date: header |
| contentBase64 | No | The acquired bytes, base64-encoded (absent when savePath was used) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral burden and succeeds: it states the tool is strictly read-only via EXAMINE + BODY.PEEK[], that \Seen is not set, and that no STORE/COPY/MOVE/APPEND/EXPUNGE is issued. It also discloses byte-fidelity guarantees, size caps, file creation semantics (never overwrites, mode 0600), and that no AppleScript/osaScript is involved.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is deliberately sectioned with 'Use when', 'Returns', 'Do not use when', and 'Safety', and the most decision-relevant guidance is front-loaded. It is long, but every sentence conveys a distinct operational constraint or selection criterion; no filler or repetition of schema-only information is present.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, the absence of annotations, and the presence of an output schema, the description is remarkably complete. It covers selection criteria, prerequisites, return shape, byte-fidelity guarantees, safety behavior, size limits, file-writing behavior, and refusals. An agent has everything needed to decide whether and how to invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents all parameters. The description adds meaningful context beyond the schema: it explains the relationship between maxBytes and inline vs. savePath limits, that contentBase64 appears only in structuredContent, and that savePath returns savedPath instead of base64. This is more than baseline value, though not a full per-parameter specification.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: retrieving a message's 'complete original RFC 822 bytes exactly as the IMAP server stores them' along with IMAP identity metadata and SHA-256. It clearly distinguishes this tool from siblings such as get-message, get-message-headers, and fetch-attachment by naming what this tool is and is not for.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, naming concrete alternatives like get-message, get-message-headers, and fetch-attachment. It also states the prerequisite that an imap: id from list-messages/search-messages is required, and explains why numeric Mail.app ids are refused. This leaves no ambiguity about when to choose this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-sync-statusA
Use when: checking whether Mail.app is running and actively syncing, e.g. to explain why new mail hasn't appeared yet. Returns: whether Mail.app is running and whether sync activity was detected. Do not use when: you need message counts (use get-mail-stats) or a full setup diagnosis (use doctor).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| error | No | |
| syncDetected | No | |
| pendingUpload | No | |
| recentActivity | No | |
| secondsSinceLastChange | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden of disclosing behavioral traits. It states that the tool returns whether Mail.app is running and whether sync activity is detected, but it does not explicitly disclose whether the operation is read-only, has side effects, or could trigger a sync. This leaves safety and interference aspects unaddressed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is optimally concise with three labeled sentences: 'Use when', 'Returns', and 'Do not use when'. It is front-loaded, every sentence provides distinct value, and there is no redundant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
This is a simple parameterless status-check tool with an output schema. The description provides sufficient context to select and invoke the tool: it states the purpose, return values, and exclusions. The existence of an output schema means detailed return format explanations are unnecessary.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the schema trivially covers all parameters. The baseline for 0 params is 4, and the description does not need to add parameter details since there are none.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose with a specific verb and resource: checking whether Mail.app is running and actively syncing. It explicitly lists what the tool returns, which distinguishes it from siblings like get-mail-stats and doctor.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly includes 'Use when' and 'Do not use when' sections, naming the exact conditions and pointing to specific alternative tools (get-mail-stats, doctor). This provides clear, actionable guidance for when to use this tool versus others.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-templateA
Use when: reading the full contents of one saved template by id — its name, subject, default to/cc, and body. Returns: the template's name, subject, default recipients, and body text. Do not use when: you don't have the id (use list-templates first) or want to apply the template into a draft (use use-template).
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| cc | No | |
| id | No | |
| to | No | |
| body | No | |
| name | No | |
| subject | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Since no annotations are provided, the description carries the full burden. It explicitly states this is a read operation ('reading') and enumerates the exact return fields. It does not discuss errors, permissions, or side effects, but for a simple get-by-id read, this is sufficient and adds value beyond the tool name.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three short, labeled sentences (Use when, Returns, Do not use when). It is front-loaded with the purpose and avoids any filler, making every sentence earn its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one parameter), the presence of an output schema, and rich sibling context, the description covers the essential use cases, exclusions, and return data. It is complete for an agent to select and invoke the tool correctly among many email-related siblings.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The sole parameter id is explained as the template's identifier, and the description explicitly warns when you don't have the id. This adds meaningful context beyond the raw schema field, which only specifies a string with minLength 1.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool reads a saved template by id and lists the returned contents (name, subject, default to/cc, body). It distinguishes itself from list-templates and use-template by explicitly naming them, making its purpose unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, including alternatives (list-templates for missing id, use-template for applying a template to a draft). This fully covers when to use the tool vs alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-threadA
Use when: you have one message id and want the whole conversation it belongs to, oldest-first. With an imap: id it threads by References/Message-ID; otherwise it groups by normalized subject. Returns: the thread's normalized subject and its messages (id, date, subject, sender, read state). Do not use when: you only need the single message (use get-message) or are searching by arbitrary criteria (use search-messages).
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | A message ID in the conversation (numeric or imap:…) | |
| limit | No | Max messages in the thread (default 50) | |
| account | No | Account to search (omit to search all) | |
| mailbox | No | Mailbox to search (omit to search all) |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| partial | No | |
| subject | No | |
| messages | No | |
| failedMailboxes | No | |
| omittedMessages | No | |
| failedMailboxReasons | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does well: it discloses the two distinct threading algorithms (References/Message-ID for imap: ids, normalized-subject grouping otherwise) and the oldest-first ordering. It stops short of noting the false-positive risk of subject-based threading or any permissions/rate behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three labeled, front-loaded blocks (use when / returns / do not use when) with no padding. Every sentence carries information the agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
All four parameters are schema-documented, an output schema exists, and the description still summarizes return shape for quick orientation. An agent has everything required to call it correctly on the first attempt.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description earns credit by explaining that the id's 'imap:' prefix changes which threading strategy is used — semantics the schema only hints at with a pattern. It doesn't add anything for limit/account/mailbox beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('thread', 'whole conversation') and resource (message thread) and explicitly contrasts with get-message and search-messages by name. An agent can distinguish it from every relevant sibling without inspecting schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It provides an explicit 'Use when' condition (one message id, want the whole conversation) and a 'Do not use when' section naming the correct alternatives for the two common misfires. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get-unread-countA
Use when: you only need the number of unread messages — INBOX by default, or scoped to one mailbox and/or account — without listing the messages themselves.
Returns: the unread count for the requested scope (INBOX when no mailbox is given). If a source cannot be read the result carries partial: true + failedAccounts, and a total AppleScript failure returns an ERROR — a plain count is never a disguised transport failure.
Do not use when: you need the actual unread messages and their ids (use list-messages with unreadOnly, or search-messages with isRead=false) or broader totals across every mailbox (use get-mail-stats).
| Name | Required | Description | Default |
|---|---|---|---|
| account | No | Account to check | |
| mailbox | No | Mailbox to check (default: INBOX) |
Output Schema
| Name | Required | Description |
|---|---|---|
| unread | No | |
| account | No | |
| mailbox | No | |
| partial | No | |
| failedAccounts | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description fully discloses behavior: it returns a count, handles partial failures with `partial: true` and `failedAccounts`, and distinguishes a total AppleScript failure returning an ERROR from a disguised transport failure. This is rich behavioral context beyond simple tool purpose.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized into three labeled sections: 'Use when', 'Returns', and 'Do not use when'. Every sentence provides useful information with no fluff or repetition, and the structure makes it easy to scan.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's core purpose, edge cases (partial failures, errors), and clearly delineates when not to use it. Given that the output schema is present and the tool is simple, this description fully equips an agent to decide when and how to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% (both account and mailbox have descriptions), so the baseline is 3. The description adds meaning by clarifying that the parameters can be used individually or together ('one mailbox and/or account'), which is not explicit in the schema. It also reiterates the INBOX default, reinforcing schema info but adding slight combination semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a very specific purpose: returning the number of unread messages, with INBOX as default or scoped to one mailbox and/or account. It explicitly notes what the tool does NOT do ('without listing the messages themselves'), which sharply distinguishes it from sibling tools like list-messages.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' sections. It names concrete alternatives (list-messages with unreadOnly, search-messages with isRead=false, get-mail-stats) and clarifies the exact scenarios for which this tool is appropriate.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
health-checkA
Use when: doing a quick check that Mail.app is reachable and the server's basic checks pass. Returns: an overall healthy/unhealthy status with a pass/fail line per check. Do not use when: you need detailed permission/account/IMAP/SMTP diagnostics with remediation steps (use doctor).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| checks | No | |
| healthy | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden of behavioral disclosure. It does describe the return format ('overall healthy/unhealthy status with a pass/fail line per check') and implies it is non-invasive, but it does not explicitly state whether it has side effects, requires permissions, or if checks are read-only. For a health-check tool, this is adequate but leaves some ambiguity about potential network or system impacts.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three short, targeted sentences: 'Use when: ...', 'Returns: ...', and 'Do not use when: ...'. Every sentence provides essential guidance with no fluff or repetition, making it highly concise and well-structured.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no parameters and a simple health-check function, the description covers the essential context: when to use it, what it returns, and when to avoid it. The output schema likely details the checks further, so the description is complete enough for this low-complexity tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, and the schema is empty. The baseline for 0 parameters is 4, and the description adds context by explaining what the tool checks (Mail.app reachability and basic server checks). This is sufficient since there is no parameter detail to compensate for.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'a quick check that Mail.app is reachable and the server's basic checks pass.' It uses a specific verb ('check') and resource (Mail.app/server), and distinguishes itself from the more detailed 'doctor' tool by emphasizing 'quick check' versus 'detailed diagnostics.'
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use and when-not-to-use guidance is given: 'Use when: doing a quick check...' and 'Do not use when: you need detailed permission/account/IMAP/SMTP diagnostics with remediation steps (use doctor).' This clearly directs the agent to an alternative tool, satisfying the criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-accountsA
Use when: discovering the configured Mail accounts (e.g. iCloud, Gmail) so you can pass an exact account name to other tools. Returns: the account names and a count. If the AppleScript transport fails (timeout / wedged Mail / missing Automation grant) this returns an ERROR rather than an empty list — an empty list always means Mail really has no accounts. Do not use when: you want the folders within an account (use list-mailboxes) or messages (use list-messages / search-messages).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| error | No | |
| partial | No | |
| accounts | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description fully carries the burden. It discloses an important error behavior: if AppleScript transport fails, it returns an ERROR rather than an empty list, and an empty list always means truly no accounts. This goes beyond basic listing semantics and adds valuable transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four concise sentences with clear structure: Use when, Returns, error caveat, Do not use when. Every sentence contributes useful information with no fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple zero-parameter tool, the description fully covers purpose, usage, error behavior, and exclusions. The output schema likely handles return structure, and the description adds the critical distinction between an error and an empty list, making it complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4 per the rubric. The description adds context about the output (account names and count) and emphasizes passing an 'exact account name' to other tools, but there is no parameter detail to add. It appropriately does not invent unnecessary info.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Clearly states it lists configured Mail accounts (like iCloud, Gmail) and the purpose is to get an exact account name for other tools. It explicitly distinguishes from list-mailboxes (folders) and list-messages/search-messages (messages).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' sections, including named alternatives (list-mailboxes, list-messages, search-messages). This gives clear context for when to invoke this tool vs siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-attachmentsA
Use when: enumerating a message's attachments (by id) to discover their names, MIME types, and sizes — typically before saving or fetching one. Returns: each attachment's name, MIME type, and decoded size in bytes (what fetch-attachment returns), plus a count; sizeApproximate: true marks the rare IMAP size that is an estimate. Do not use when: you want the bytes (use fetch-attachment for inline base64, or save-attachment to write to disk). Get the message id from search-messages or list-messages first.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| attachments | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it documents the return payload (name, MIME type, decoded byte size, count) and the sizeApproximate edge case for IMAP estimates. It doesn't cover auth/rate-limit or pagination, but as a read-only enumeration those are minor.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Organized into front-loaded 'Use when', 'Returns', and 'Do not use when' blocks with no wasted sentences. Every clause carries decision-relevant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, yet the description still usefully summarizes the return shape and the sizeApproximate flag. Combined with the routing guidance, an agent has everything needed to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% and the single id parameter has a pattern but no description. The description compensates by clarifying the id refers to a message and telling the agent to obtain it from search-messages or list-messages, adding genuine meaning beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (enumerating) and resource (a message's attachments by id), and explicitly scopes the result to names, MIME types, and sizes. It clearly distinguishes itself from fetch-attachment and save-attachment by contrast.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit 'Use when' and 'Do not use when' clauses, naming the two alternative tools (fetch-attachment, save-attachment) and the condition that selects each. It also states the prerequisite for obtaining the id.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-mailboxesA
Use when: discovering the mailbox/folder paths (and unread/message counts) available in an account, e.g. before moving messages or searching a specific mailbox.
Returns: each mailbox's canonical account-relative path in name, unread/message counts, and a total count. Use the full path for nested mailboxes (for example Archive/Inbox); a top-level Inbox remains Inbox. A source that could not be read is NAMED — the result carries partial: true + failedAccounts and the list is a floor, not the complete set — and a listing Mail refused outright (e.g. an account that does not exist) returns an ERROR naming the accounts that do exist, never an empty list.
Do not use when: you want the messages inside a mailbox (use list-messages or search-messages) or the list of accounts (use list-accounts).
Note: Mail's local "On My Mac" mailboxes are not part of any account, so they are reported under the synthetic account label "On My Mac" — an unscoped call includes them, and account: "On My Mac" lists only them. They will not appear in list-accounts, which reports real accounts only.
| Name | Required | Description | Default |
|---|---|---|---|
| account | No | Account to list mailboxes from |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| partial | No | |
| mailboxes | No | |
| failedAccounts | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of behavioral disclosure. It thoroughly covers edge cases: partial results with `partial: true` and `failedAccounts`, errors for nonexistent accounts (naming existing ones, never empty), and the synthetic 'On My Mac' account. It also explains the return format (canonical paths, counts, total count). This is exemplary transparency.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized with clear sections ('Use when', 'Returns', 'Do not use when', a note). It is front-loaded with the primary purpose, each sentence provides distinct and useful information, and there is no redundancy. Although longer than typical, every part earns its place given the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description is complete for the tool's complexity. It explains return values (counts, paths), edge cases (partial results, errors), the synthetic account handling, and usage context. An output schema exists, so detailed return format isn't needed here. An agent has everything required to call this tool correctly and interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema describes `account` as 'Account to list mailboxes from' (100% coverage). The description adds meaningful nuance beyond the schema: it specifies that `account: "On My Mac"` lists only On My Mac mailboxes, and an unscoped call includes them. This is valuable parameter-specific behavior not present in the schema, bringing the score above the baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: discovering mailbox/folder paths and unread/message counts in an account. It names the specific resource (mailboxes) and the operation (list), and distinguishes it from siblings like list-messages, search-messages, and list-accounts by explicitly stating what it is not for. The context 'before moving messages or searching a specific mailbox' further clarifies its role.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' sections, naming alternative tools (list-messages, search-messages, list-accounts) and the conditions that select them. It also explains the 'On My Mac' special case and when an unscoped call vs. a scoped call is appropriate. This leaves nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-messagesA
Use when: browsing a mailbox's recent messages (optionally filtered by sender or unread-only) with pagination via limit/offset, and you need their ids. Returns: messages with id, date, subject, sender, and flagColor when flagged with an explicit color (plus partial-coverage diagnostics when some mailboxes were skipped). Do not use when: you have specific search criteria like subject/date/flags/flag color (use search-messages, which also supports filtering by flagColor) or already have an id and want the body (use get-message). Like search-messages, use this to obtain the ids that read/mark/delete/move and batch tools require.
| Name | Required | Description | Default |
|---|---|---|---|
| from | No | Filter by sender email address or name | |
| limit | No | Maximum number of messages (default: 50, max: 500) | |
| offset | No | Number of messages to skip (for pagination) | |
| account | No | Account to list messages from | |
| mailbox | No | Mailbox to list messages from. Omit to list from all mailboxes. | |
| unreadOnly | No | Only show unread messages |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| backend | No | |
| partial | No | |
| messages | No | |
| stoppedBy | No | |
| failedMailboxes | No | |
| omittedMessages | No | |
| backendByAccount | No | |
| timedOutAccounts | No | |
| timedOutMailboxes | No | |
| failedMailboxReasons | No | |
| notSearchedMailboxes | No | |
| skippedLargeMailboxes | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the burden, and it does disclose real behavioral traits: cross-mailbox aggregation is default, skipping some mailboxes yields 'partial-coverage diagnostics' rather than a clean success, and the response shape is enumerated. It stops short of stating that the operation is read-only/non-mutating or what permissions the account argument requires — the main remaining gap for an un-annotated tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the use condition, then returns, then exclusions — a structure an agent can scan quickly, with no filler sentences. Slightly long because the returns paragraph overlaps with the existing output schema, but nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema exists, the description needn't enumerate return values (though it does), and it correctly covers what structured data cannot: sibling routing, the id-first workflow, and degraded results from skipped mailboxes. Missing only read-only/side-effect confirmation and any account permission requirement for a zero-annotation tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including defaults (limit 50, max 500) and the 'omit to list from all mailboxes' behavior on mailbox, so the schema already does the heavy lifting. The description reinforces the filtering and pagination semantics in context but adds no format or edge-case detail beyond what the schema says. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource — browsing a mailbox's recent messages — and names the outcome the agent needs (message ids), which immediately separates it from get-message (body by id) and search-messages (criteria-driven lookup). The optional sender/unread filters and pagination scope are stated up front, so the tool is identifiable without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Uses explicit 'Use when' / 'Do not use when' framing with two named alternatives and the exact condition that selects each: specific subject/date/flag criteria → search-messages, already have an id and want the body → get-message. It closes by tying the tool to the downstream batch/mutation tools that consume the returned ids, which tells the agent where this fits in a workflow.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-rulesA
Use when: discovering the Mail rules that exist and whether each is enabled or disabled, e.g. before enabling/disabling/deleting one. Returns: each rule's name and enabled/disabled state. Do not use when: you want to change a rule (use enable-rule / disable-rule / create-rule / delete-rule).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| rules | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It clearly states the return content ('each rule's name and enabled/disabled state') and implies a read-only operation through the 'Use when' vs 'Do not use when' distinction. However, it does not explicitly declare that the operation has no side effects, nor mention auth or error behavior, which is a minor gap for a tool with no annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three short, clearly structured sentences with helpful labels ('Use when', 'Returns', 'Do not use when'). Every sentence adds value and there is no redundant information, making it highly concise and well front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, no-annotation, simple listing tool, the description fully covers when to use it, what it returns, and when not to use it. The existence of an output schema further reduces the burden, so the description is complete for its context.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the schema documents 100% of them vacuously. Per guidelines, 0 params gives a baseline of 4. The description adds useful context about the output, but there are no parameter semantics to explain, so the baseline is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('discovering' / 'list') with a clear resource ('Mail rules') and scope ('whether each is enabled or disabled'). It distinguishes from sibling tools by explicitly naming enable-rule, disable-rule, create-rule, and delete-rule as alternatives for changes, so the purpose is unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly states 'Use when: discovering the Mail rules...' and 'Do not use when: you want to change a rule (use enable-rule / disable-rule / create-rule / delete-rule)', giving clear context and naming alternative tools. This is exactly the level of guidance expected.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-smart-mailboxesA
Use when: listing Apple Mail smart mailboxes (criteria-based virtual views), including on German-localized macOS where AppleScript's smart-mailbox terms do not compile. Returns: each smart mailbox's name and a short criteria summary. Do not use when: listing real folders/mailboxes (use list-mailboxes).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| smartMailboxes | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the behavioral transparency burden. It discloses a notable edge case (German-localized macOS where AppleScript terms don't compile) and mentions the return content ('name and a short criteria summary'). While it doesn't explicitly state non-destructive intent, 'listing' implies read-only, and the edge case adds valuable behavioral context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise, with two clear sections ('Use when' and 'Returns') and a 'Do not use when' clause. Every sentence serves a purpose, and the most critical usage direction is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter list tool with an output schema, the description covers purpose, usage boundary, an important localization nuance, and return value summary. It is fully sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline for this dimension is 4 as per the rubric. The description doesn't need to add parameter semantics, and it correctly stays silent on parameters. The mention of 'criteria summary' is about the return value, not an input.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description specifies the exact action ('listing Apple Mail smart mailboxes') and clearly distinguishes it from the sibling tool by stating 'Do not use when: listing real folders/mailboxes (use list-mailboxes)'. This makes the purpose unambiguous and differentiates it from closely related tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' instructions, naming the alternative tool (list-mailboxes). This meets the highest standard for usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list-templatesA
Use when: discovering the saved email templates and their ids, e.g. before using or editing one. Returns: each template's id, name, and subject. Do not use when: you want a single template's full body (use get-template) or want to apply one (use use-template).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| templates | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden of behavioral disclosure. It discloses that the tool returns only id, name, and subject (not full body), and implies a read-only, non-mutating operation. It could add more context about potential errors or permission requirements, but for a simple list operation with zero parameters, the disclosure is reasonably complete.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is extremely concise, using three short sentences that are front-loaded with the primary use case, followed by return details and exclusions. Every sentence earns its place with no filler or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool is simple (0 parameters) and has an output schema, so the description need not explain return values in depth. It fully covers when to use, what it returns, and when not to use, making it contextually complete for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so the baseline is 4. The description appropriately focuses on output and usage rather than parameter details, adding no unnecessary parameter-related text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool 'lists' saved email templates and their ids, with a specific verb and resource. It further distinguishes itself from siblings by explicitly stating it returns 'each template's id, name, and subject' and contrasts with get-template and use-template.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, naming alternative tools (get-template, use-template) for different use cases. This makes it very clear when to select this tool versus its siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark-as-readA
Use when: marking a single message (by id) as read. Returns: a confirmation that the message was marked read. Do not use when: marking several at once (use batch-mark-as-read) or marking unread (use mark-as-unread). Get the id from search-messages or list-messages first.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It states the action (mark as read) and the return behavior (confirmation). This is adequate for a simple mutation tool, though it doesn't disclose behavior for invalid ids or other edge cases.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with 'Use when' and 'Returns'. Every sentence adds value with no redundancy or fluff.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with an output schema, the description covers purpose, usage, alternatives, and id sourcing, making it complete for an agent to select and invoke correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, but the description adds meaning by explaining the id is a message id and where to obtain it ('from search-messages or list-messages first'). The schema's pattern provides format constraints, but the description provides practical usage semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: marking a single message by id as read. It distinguishes from siblings by explicitly naming batch-mark-as-read and mark-as-unread.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use ('Use when: marking a single message') and when-not-to-use ('Do not use when: marking several at once... or marking unread'), with named alternatives. It also instructs to get the id from search-messages or list-messages first.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark-as-unreadA
Use when: marking a single message (by id) as unread. Returns: a confirmation that the message was marked unread. Do not use when: marking several at once (use batch-mark-as-unread) or marking read (use mark-as-read). Get the id from search-messages or list-messages first.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the burden. It discloses the return value (a confirmation) and implies the mutation by stating the tool marks a message unread. However, it does not explicitly state permission requirements or reversibility, though these are less critical for this action.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-organized: starts with use case, then returns, then exclusions and prerequisites. Every sentence adds value, and it front-loads the most critical information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with an output schema, the description covers the core action, exclusions, return value, and id source. It is complete for the tool's complexity and leaves no significant gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate. It explains the id parameter's role ('by id') and tells the user where to obtain it, adding context beyond the bare schema pattern.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses specific verb+resource: 'marking a single message (by id) as unread'. It clearly distinguishes from sibling tools like mark-as-read and batch-mark-as-unread by explicitly naming them in the 'Do not use when' clause.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use, do-not-use scenarios with named alternative tools (batch-mark-as-unread, mark-as-read), and even notes the prerequisite to get the id from search-messages or list-messages first.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
move-messageA
Use when: moving a single message (by id) into another mailbox/folder, e.g. archiving or filing. Returns: a confirmation naming the destination mailbox. Do not use when: moving several at once (use batch-move-messages) or deleting (use delete-message). Use list-mailboxes to confirm the destination name exists. Safety: moves a real message between folders — confirm the destination mailbox, and search-messages/list-messages first to confirm the id.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| account | No | Account containing the destination mailbox | |
| mailbox | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No | |
| mailbox | No | |
| countDelta | No | |
| verification | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description carries the burden. It clearly discloses that the tool 'moves a real message between folders' and includes a safety warning. It also states the return value (a confirmation naming the destination mailbox). While it doesn't detail potential failure modes or permission requirements, it provides adequate transparency for a simple mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Every sentence earns its place. The description uses a clear 'Use when'/'Do not use when' structure, includes a return note, and ends with a safety caveat. It is concise (about 3 sentences) and front-loaded with the most critical information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the simplicity of the tool (3 params, no nested objects) and the presence of an output schema, the description covers the essential aspects: when to use, alternatives, safety, and return value. It could mention error conditions or edge cases (e.g., what happens if the mailbox doesn't exist), but it preemptively advises to confirm the mailbox. This is complete enough for typical use, though not exhaustive.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is only 33% (only 'account' has a description). The tool description clarifies that 'id' refers to the message id and 'mailbox' to the destination folder, adding meaning beyond the schema. However, 'account' is left to the schema's description, and no additional context is given for the id pattern or mailbox naming conventions. The description partially compensates for the low coverage but does not fully explain all parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the action: 'moving a single message (by id) into another mailbox/folder' with examples (archiving, filing). It distinguishes itself from batch-move-messages and delete-message, making the tool's unique scope explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Extremely explicit usage guidance: includes 'Use when' and 'Do not use when' with specific alternatives (batch-move-messages, delete-message). Also advises using list-mailboxes to verify destination and search-messages/list-messages to confirm id, providing a clear decision framework.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
rename-mailboxA
Use when: renaming an existing mailbox/folder from oldName to newName within an account. Returns: a confirmation naming the old and new mailbox names. Do not use when: creating a new folder (use create-mailbox) or deleting one (use delete-mailbox). Use list-mailboxes to confirm the current name. Safety: renames a real folder in the mail account — confirm oldName matches exactly (case-sensitive) before calling.
| Name | Required | Description | Default |
|---|---|---|---|
| account | No | Account containing the mailbox | |
| newName | Yes | ||
| oldName | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| newName | No | |
| oldName | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full transparency burden. It warns that the tool 'renames a real folder in the mail account' and stresses case-sensitivity and confirming oldName matches exactly. It also mentions the return value (a confirmation). However, it doesn't disclose potential side effects like whether existing links break or if the operation is reversible, which prevents a higher score.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and well-structured with labeled sections: 'Use when', 'Returns', 'Do not use when', and 'Safety'. Each sentence serves a purpose without redundancy, front-loading the primary action.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple 3-parameter tool, the description covers purpose, usage boundaries, safety, and return value. It lacks details on error handling or edge cases (e.g., if the mailbox doesn't exist), but given the output schema exists and the operation is straightforward, it is fairly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 33%, so the description must compensate. It explains oldName and newName contextually (renaming from old to new) and notes case-sensitivity, adding value beyond the schema. However, it doesn't clarify the 'account' parameter's role or whether it's required, and it doesn't elaborate on format or constraints. Partial compensation but not thorough.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: 'renaming an existing mailbox/folder from oldName to newName within an account.' It uses a specific verb (rename) and resource (mailbox/folder), and distinguishes from create-mailbox and delete-mailbox by explicitly naming those alternatives.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit when-to-use and when-not-to-use guidance: 'Use when: renaming...' and 'Do not use when: creating a new folder (use create-mailbox) or deleting one (use delete-mailbox).' It also recommends verifying the current name with list-mailboxes, which is practical context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
reply-to-messageA
Use when: replying to an existing message by id, preserving its threading headers and quoting the original, optionally with attachments (same rules as send-email). Set replyAll for all recipients; set send=false to save as a draft instead of sending. Returns: a confirmation that the reply was sent or saved as a draft, with the attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy). Do not use when: composing a brand-new message (use send-email / create-draft) or forwarding to new recipients (use forward-message). Safety: with the default send=true this SENDS real email immediately and cannot be unsent — require explicit user confirmation of the recipients and body, or pass send=false to let the user review.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| body | Yes | Reply body (plain text; HTML tags such as <br> are not rendered) | |
| send | No | Send immediately (false = save as draft) | |
| account | No | For a numeric id, the account it was listed from; pair with mailbox. Omit for an imap: id. | |
| mailbox | No | For a numeric id, the exact source mailbox path; pair with account. A miss never searches elsewhere. | |
| replyAll | No | Reply to all recipients | |
| transport | No | SMTP sends clean MIME and preserves reply threading; requires SMTP configuration. AppleScript uses Mail.app and may quote-wrap the new body on macOS 15+. Omit to prefer configured SMTP when sending; drafts use AppleScript. A selected SMTP transport never silently falls back. SMTP with send=false is rejected. | |
| attachments | No | Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each. | |
| composition | No | Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message. |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No | |
| sent | No | |
| sentCopy | No | SMTP only: whether a best-effort copy was filed to the account's Sent mailbox over IMAP (issue #220). Absent when no configured IMAP account matches the SMTP identity — not a failure, the feature simply wasn't engaged. |
| messageId | No | |
| transport | No | |
| sentCopyError | No | Present only when sentCopy is false: why the Sent-folder copy failed. |
| attachmentCount | No | |
| sentCopyAlreadyFiled | No | Present (true) only when sentCopy is true because the Sent mailbox already held this Message-ID (e.g. Gmail's SMTP files its own copy), so none was appended. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. It clearly warns that default send=true sends real email immediately and cannot be unsent, requires explicit user confirmation or send=false, and explains drafting, threading preservation, attachment behavior, and SMTP versus AppleScript effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with a clear Use when section, followed by Returns, Do not use when, and Safety. Each section is compact and carries actionable information without redundant filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 9-parameter mutation tool with no annotations, the description covers purpose, alternatives, safety, drafting behavior, transport implications, and return-side confirmation details. The output schema exists, and the remaining parameter details are well covered by the schema itself.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is already 89%, so the schema documents most parameter meaning. The description echoes replyAll and send=false and notes attachments follow send-email rules, but it adds little parameter-level detail beyond the schema, making 3 the appropriate baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: replying to an existing message by id while preserving threading headers and quoting the original. It also names the sibling tools for adjacent actions, so an agent can distinguish this from send-email, create-draft, and forward-message.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly gives the use case, lists when not to use it, and routes the agent to the correct alternatives: send-email/create-draft for new messages and forward-message for forwarding. It also explains the replyAll and send=false options as usage choices.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
resolve-message-idA
Use when: you have imap: message id(s) and genuinely need the numeric Mail.app id(s) — e.g. for reply-to-message/forward-message, which are numeric-id only. NOTE: as of 2.10.0 you no longer need this to apply a flag COLOR — flag-message/batch-flag-messages write the color over IMAP directly via Mail.app's $MailFlagBit0/1/2 keywords, so a smart mailbox keyed on flag color matches an IMAP-flagged message. Each imap: id is resolved via its RFC822 Message-ID.
Returns: for each input id, its numericId (the AppleScript id) or null when it can't be resolved, plus the messageId used; and a resolvedCount.
Do not use when: your ids are already numeric (they pass straight through), or you don't need a color — flag/move/mark tools operate on imap: ids directly.
| Name | Required | Description | Default |
|---|---|---|---|
| ids | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| resolved | No | |
| resolvedCount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full transparency burden. It explains how resolution works (via RFC822 Message-ID), what each input yields (numericId or null, plus messageId and resolvedCount), and includes a version note about flag coloring behavior. It stops short of explicitly stating non-destructiveness or edge-case behavior, but the disclosed behavior is substantive and above average.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with 'Use when', 'Returns', and 'Do not use when' sections, and it leads with the main use case. It is slightly verbose due to the version note, but every sentence adds information and the structure akes it easy to scan. A more compact phrasing might be possible, but the length is justified.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given this is a simple one-parameter tool with an output schema, the description fully covers when to use it, how it behaves, what it returns, and how it differs from alternative tools. The version note adds important contextual information. No critical gaps remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must explain the parameter. It clarifies the `ids` format (either numeric or `imap:` prefixed) and what happens to each form (numeric pass through, `imap:` resolved). This goes well beyond the raw JSON schema pattern and provides essential semantic context.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that this tool resolves `imap:` message IDs into numeric Mail.app IDs, specifically needed for reply-to-message/forward-message. It distinguishes itself from sibling tools by noting that flag/move/mark tools work directly with `imap:` IDs, making this tool's unique purpose explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, including concrete examples (reply-to-message/forward-message) and exclusions (numeric IDs already pass through, flag/move/mark operate directly). It also notes a version-specific change (2.10.0) that affects when this tool is needed for flag colors, showing strong awareness of alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
save-attachmentA
Use when: writing one of a message's attachments to disk, by message id and attachmentName, into the savePath directory (saved as savePath/attachmentName). Returns: a confirmation of the saved file path. Do not use when: you don't know the attachment name (use list-attachments first) or want the bytes inline rather than on disk (use fetch-attachment). Safety: writes a file to disk — savePath must be a directory inside the configured allowed roots, and attachmentName may not contain path separators or '..'; calls outside those constraints are rejected.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | ||
| savePath | Yes | ||
| attachmentName | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| savedPath | No | |
| attachmentName | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations present, the description transparently discloses that the tool writes a file to disk (destructive side effect), enforces path safety constraints on savePath and attachmentName, and returns a confirmation of the saved path. It does not mention overwrite behavior or permissions, but the disclosed constraints substantially cover the behavioral profile.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is tightly organized into labeled sections (Use when, Returns, Do not use when, Safety). Each sentence delivers distinct information without redundancy, and the structure front-loads the primary use case.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity and the presence of an output schema, the description covers purpose, usage guidelines, return behavior, and safety constraints. Minor gaps such as overwrite semantics prevent a perfect score, but the description is highly usable for an agent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema provides no property descriptions (0% coverage). The description compensates by defining all three parameters: id as message id, attachmentName as the attachment name, savePath as the destination directory, and the resulting file path as savePath/attachmentName. It also adds validation rules for attachmentName.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description explicitly states the tool's action: writing a message attachment to disk using message id and attachmentName into savePath. It names the exact output file path format and distinguishes from sibling tools by mentioning fetch-attachment for inline bytes and list-attachments for unknown names.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit 'Use when' and 'Do not use when' guidance, naming alternatives (list-attachments, fetch-attachment). This clearly instructs the agent on appropriate conditions and exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
save-templateA
Use when: creating a reusable email template (name, subject, body, optional default to/cc), or updating one by passing its existing id. Subject/body may contain placeholders for later use. Returns: the saved template's name and id (reuse the id with use-template / get-template / delete-template). Do not use when: composing a one-off message (use create-draft / send-email) or filling in a template to send (use use-template). Safety: writes the template to the on-disk templates store (APPLE_MAIL_MCP_TEMPLATES_FILE) and persists across restarts; passing an existing id overwrites that template.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | Default CC recipients | |
| id | No | Template ID (for updating existing template) | |
| to | No | Default recipients | |
| body | Yes | ||
| name | Yes | ||
| subject | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No | |
| name | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are present, so the description carries the burden. It discloses that templates are written to an on-disk store, persist across restarts, and that passing an existing id overwrites. It also mentions the return value (name and id). This is strong coverage, though it doesn't address permissions or error conditions.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a structured paragraph with labeled sections (Use when, Returns, Do not use when, Safety). Every sentence adds value, though it is slightly verbose. It remains efficient and scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers creation, update, return values, persistence/overwrite behavior, and exclusions. With an output schema noted, it fully equips an agent to call the tool correctly. No critical gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 50%, but the description compensates by explaining the role of core parameters: name, subject, body, optional to/cc, and id for updating. It clarifies that id distinguishes update from creation, which the schema alone does not convey.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool creates or updates reusable email templates, naming the key fields (name, subject, body, to/cc). It also distinguishes itself from siblings by mentioning updating via id and explicitly contrasting with use-template, create-draft, and send-email.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' sections, naming alternative tools (create-draft, send-email, use-template) for other scenarios. This leaves no ambiguity about when to select this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search-contactsA
Use when: looking up a person in Contacts by name, organization, nickname, or email to find their email address(es)/phone(s) before composing or sending mail. Reads the macOS Contacts database directly (needs Full Disk Access; does NOT require Contacts.app to be running or an Automation / Apple-Events grant). Returns: matching contacts with their names, email addresses, and phone numbers. Do not use when: searching email messages (use search-messages) — this queries Contacts, not the mailbox.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| contacts | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full burden and does well by disclosing that it reads the macOS Contacts database directly, requires Full Disk Access, and does not require Contacts.app or Apple-Events grant. It also states return fields. Minor gaps: doesn't mention whether it is read-only explicitly or handle errors, but overall clearly describes behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-organized with clear 'Use when', 'Reads...', 'Returns...', and 'Do not use when' segments. Every sentence adds valuable information, no fluff, and it is appropriately sized for the tool's simplicity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple search tool with one parameter and an output schema, the description covers all essential aspects: use case, when not to use, access method, prerequisites, and return values. There is no ambiguity about the tool's function or limitations.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The only parameter 'query' is given meaning by the description: it can be a name, organization, nickname, or email. This goes beyond the schema's generic 'string' definition and fully compensates for the 0% schema coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States clearly what it does: looking up a person in Contacts by name, organization, nickname, or email to find email addresses and phone numbers. It uses a specific verb ('search') and resource ('Contacts'), and explicitly distinguishes from sibling tool search-messages by saying 'Do not use when: searching email messages...'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'Use when' and 'Do not use when' sections provide clear guidance on when to choose this tool over alternatives, including naming the alternative (search-messages). It also gives prerequisites (Full Disk Access, no need for Contacts.app running), which helps agent decide when this tool is viable.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search-messagesA
Use when: finding messages by query/sender/subject/date/read/flag/flag-color filters and you need their ids for follow-up operations. Returns: matching messages with id, date, subject, sender, read state, and flagColor when flagged with an explicit color (plus partial-coverage diagnostics when some mailboxes were skipped). An IMAP search stops at its deadline (APPLE_MAIL_MCP_SEARCH_DEADLINE_MS, default 45s) or when the request is cancelled, returning the matches found so far with partial: true and timedOutMailboxes — narrow with mailbox/dateFrom rather than repeating it. Do not use when: you want a plain mailbox listing without filters (use list-messages), already have an id and want the body (use get-message), or want a whole conversation (use get-thread). Prefer this first to obtain the message ids that get-message/mark-as-read/delete-message/move-message and the batch tools require. Tip: use flagColor to find messages you tagged earlier (e.g. all 'orange' across mailboxes) — colored flags work as cross-mailbox labels this way, via flag-message/batch-flag-messages to set them and this filter to find them again, without moving anything into a dedicated folder.
| Name | Required | Description | Default |
|---|---|---|---|
| body | No | Text to search for in the message body (server-side IMAP BODY search). Requires the IMAP backend for the account searched, or the opt-in local body index (APPLE_MAIL_MCP_BODY_INDEX); otherwise AppleScript-only accounts cannot search bodies and are reported as not searched. | |
| from | No | Filter by sender (substring match against the full sender string, i.e. display name + address — not an exact address match) | |
| limit | No | Maximum number of results (default: 50, max: 500) | |
| query | No | Text to search for in subject or sender | |
| dateTo | No | End date filter (e.g., 'March 1, 2026') | |
| isRead | No | Filter by read status | |
| offset | No | Number of newest matches to skip (for pagination; IMAP accounts only). Without a mailbox, offset + limit may not exceed 5,000 — name a mailbox to page deeper. | |
| account | No | Account to search in (omit to search all accounts) | |
| mailbox | No | Mailbox to search in (e.g., 'INBOX'). Omit to search all mailboxes. | |
| subject | No | Filter by subject line (substring match) | |
| dateFrom | No | Start date filter (e.g., 'January 1, 2026') | |
| flagColor | No | Filter to messages flagged with exactly this color (same palette as flag-message: red/orange/yellow/green/blue/purple/gray — 'grey' accepted). A message flagged with Mail's plain default (no explicit color) never matches. Implies isFlagged: true — pairing this with isFlagged: false is rejected as contradictory. Caveat on IMAP accounts only: 'red' is Mail's all-bits-clear encoding, wire-identical to a plain (colorless) flag, so red: true in results and flagColor: 'red' in this filter both also match a plain flag there — this is inherent to Apple's $MailFlagBit0/1/2 scheme, not specific to this server. AppleScript-only accounts distinguish red correctly (Mail's own `flag index` 0 vs -1). | |
| isFlagged | No | Filter by flagged status |
Output Schema
| Name | Required | Description |
|---|---|---|
| count | No | |
| backend | No | |
| partial | No | |
| messages | No | |
| stoppedBy | No | |
| failedMailboxes | No | |
| omittedMessages | No | |
| backendByAccount | No | |
| timedOutAccounts | No | |
| timedOutMailboxes | No | |
| failedMailboxReasons | No | |
| notSearchedMailboxes | No | |
| skippedLargeMailboxes | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so: it discloses the search deadline and its env var (APPLE_MAIL_MCP_SEARCH_DEADLINE_MS, 45s default), cancellation behavior, the partial: true / timedOutMailboxes return shape when mailboxes are skipped, and the remediation (narrow with mailbox/dateFrom instead of retrying). These are non-obvious operational traits an agent could not infer from the name or schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded and labeled (Use when / Returns / Do not use when / Tip), so an agent scans rather than reads. The flagColor tip is longer than strictly necessary for the retrieval decision it supports, but each section still carries distinct information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 13-parameter, zero-required search tool this covers the decision space fully: what it returns, how it degrades, how to recover from degradation, and how it chains into id-consuming siblings. An output schema exists, yet the description still outlines the payload, which is a bonus rather than a gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description exceeds it by giving flagColor an operational meaning beyond the enum — a cross-mailbox label set via flag-message/batch-flag-messages and recovered here, and the advice to narrow with mailbox/dateFrom under timeout. It does not re-explain the other 12 params, but the schema already does that thoroughly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource ('finding messages') and enumerates the exact filter dimensions (query/sender/subject/date/read/flag/flag-color). It explicitly names the sibling tools it is not (list-messages, get-message, get-thread), so an agent can disambiguate without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Has an explicit 'Use when' / 'Do not use when' split with a named alternative for each excluded case, plus a routing rule ('Prefer this first to obtain the message ids that get-message/mark-as-read/delete-message/move-message require'). Nothing about selection is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send-emailA
Use when: the user has explicitly confirmed they want to send a single email now to the given recipients (to/cc/bcc are arrays), optionally with attachments, a chosen transport, and caller-built threading headers (inReplyTo/references, SMTP only). Returns: a confirmation naming the recipients and attachment count; over SMTP, also whether a Sent-folder copy was filed (sentCopy). Do not use when: the user wants to review first (use create-draft), is replying to or forwarding an existing message (use reply-to-message / forward-message — they derive recipients, threading and the quote, and also take attachments), or wants per-recipient personalized copies (use send-serial-email). Safety: this SENDS real email immediately and it cannot be unsent — require explicit user confirmation of the exact recipients, subject, and body before calling. Prefer create-draft when there is any doubt.
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | CC recipients | |
| to | Yes | ||
| bcc | No | BCC recipients | |
| body | Yes | ||
| account | No | Account to send from. AppleScript transport: a Mail account name or an exact sender address, resolved to one address and verified before sending; if it matches no enabled account, or Mail would use a different sender, nothing is sent and an error is returned. Omitted → Mail's default sender (reported back in `from`). SMTP transport: an email-form From override (see README). | |
| replyTo | No | SMTP only (issue #220): sets the Reply-To header when replies should go somewhere other than the From/login address, e.g. a domain-alias setup where APPLE_MAIL_MCP_SMTP_FROM differs from APPLE_MAIL_MCP_SMTP_USER. Ignored on the AppleScript transport. | |
| subject | Yes | ||
| inReplyTo | No | Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you. | |
| transport | No | Send transport. 'smtp' submits clean MIME directly via SMTP, avoiding the macOS 15+ Mail.app <blockquote> wrapping (issue #12); requires APPLE_MAIL_MCP_SMTP_* env config. 'applescript' sends through Mail.app. If omitted, configured SMTP is used unless account is a Mail.app account label (for example 'Work'), which selects AppleScript. Explicit SMTP uses the configured SMTP identity; it does not resolve Mail.app account labels. Without SMTP configuration, use AppleScript. | |
| references | No | Thread chain, oldest first: the parent's own References followed by the parent's Message-ID (each an angle-bracketed token). Emitted verbatim, never RFC 2047-encoded. | |
| attachments | No | Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each. | |
| composition | No | Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message. |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| from | No | AppleScript transport: the sender Mail.app used |
| sentCopy | No | SMTP only: whether a best-effort copy was filed to the account's Sent mailbox over IMAP (issue #220). Absent when no configured IMAP account matches the SMTP identity — not a failure, the feature simply wasn't engaged. |
| messageId | No | |
| transport | No | |
| recipients | No | |
| sentCopyError | No | Present only when sentCopy is false: why the Sent-folder copy failed. |
| attachmentCount | No | |
| sentCopyAlreadyFiled | No | Present (true) only when sentCopy is true because the Sent mailbox already held this Message-ID (e.g. Gmail's SMTP files its own copy), so none was appended. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are supplied, so the description carries the full burden, and it does: it warns the email is sent immediately and cannot be unsent, requires explicit confirmation of exact recipients/subject/body, describes the Sent-folder copy outcome over SMTP, explains transport selection trade-offs (SMTP avoids macOS 15+ blockquote wrapping), and documents composition caveats (unlocked desktop, Accessibility/Automation permissions, no fallback on failure). This is unusually rich behavioral disclosure for a mutation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Labeled sections (Use when / Returns / Do not use when / Safety) make it strongly front-loaded and scannable. Minor waste: the 'Returns' sentence partially duplicates the output schema, and the closing 'Prefer create-draft when there is any doubt' restates the Safety clause.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 12-parameter destructive mutation with no annotations, the description covers the safety profile, transport/account resolution caveats, threading constraints and composition preconditions. With an output schema present, it need not detail return values further, and nothing an agent needs in order to call it safely is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 75%, so the schema already documents most fields in depth. The description still adds orientation the schema does not frame: that to/cc/bcc are arrays, that attachments are optional, that transport is a chosen option with consequences, and that inReplyTo/references are caller-built and SMTP-only — a useful cross-cutting note even if it restates schema details.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (send a single email now) plus the scope of what can be included: recipients, attachments, transport, caller-built threading headers. It is immediately distinguishable from reply-to-message, forward-message, create-draft and send-serial-email, all of which are named.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'Use when' and 'Do not use when' clauses, each routing to a named alternative with the condition that selects it (review first → create-draft; replying/forwarding → reply-to-message / forward-message; per-recipient copies → send-serial-email). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send-serial-emailA
Use when: the user has confirmed a mail-merge — sending individually personalized copies to many recipients (max 100), with {{Key}} placeholders in subject/body replaced per-recipient from each recipient's variables. Recipients do not see each other. Returns: a per-recipient sent/failed report with counts. Do not use when: sending one message to a shared recipient list (use send-email) or saving for review (use create-draft). Safety: this SENDS many real emails immediately and they cannot be unsent — require explicit user confirmation of the recipient list, the subject/body template, and the placeholder substitutions before calling.
| Name | Required | Description | Default |
|---|---|---|---|
| body | Yes | Email body — use {{Key}} for placeholders | |
| account | No | Account to send from. AppleScript transport: a Mail account name or an exact sender address, resolved to one address and verified before sending; if it matches no enabled account, or Mail would use a different sender, nothing is sent and an error is returned. Omitted → Mail's default sender (reported back in `from`). SMTP transport: an email-form From override (see README). | |
| delayMs | No | Delay between sends in ms (default: 500, max: 10000) | |
| subject | Yes | Subject line — use {{Key}} for placeholders | |
| transport | No | Send transport for the whole batch. 'smtp' sends clean MIME without Mail.app quote wrapping and requires matching SMTP configuration; errors never fall back to AppleScript. SMTP accepts an allowed sender email address in account, not a Mail.app account label. 'applescript' explicitly uses Mail.app. If omitted, configured SMTP is used unless account is a Mail.app account label. | |
| recipients | Yes | List of recipients with personalization variables (max 100) |
Output Schema
| Name | Required | Description |
|---|---|---|
| ok | No | |
| sent | No | |
| failed | No | |
| results | No | |
| transport | No | Transport used for this batch |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden and does so thoroughly: it warns that emails are sent immediately and cannot be unsent, requires explicit user confirmation of recipients, template, and substitutions, notes recipients do not see each other, and states the return is a per-recipient sent/failed report with counts.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with 'Use when', followed by returns, exclusions, and safety. Every section is purposeful and directly useful, with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity (6 parameters, batch sending, safety risk) and the presence of an output schema, the description provides all critical context: recipient limit, placeholder mechanics, privacy behavior, return format, and explicit confirmation requirements.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so parameters like account, transport, and delayMs are already documented in the schema. The description mentions placeholder substitution and the 100-recipient cap, but these are also present in the schema, so it adds little beyond the baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action (send-serial-email) with clear scope: mail-merge personalized copies to many recipients, max 100, using {{Key}} placeholders. It explicitly distinguishes itself from send-email and create-draft, so an agent can select it without opening sibling schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' conditions, naming the alternatives send-email and create-draft. The selection criteria are unambiguous and leave nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
unflag-messageA
Use when: removing the flag from a single message (by id). Returns: a confirmation that the message was unflagged. Do not use when: unflagging several at once (use batch-unflag-messages) or adding a flag (use flag-message). Get the id from search-messages or list-messages first.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description mentions that the tool returns a confirmation, which adds some behavioral context. However, with no annotations present, it does not disclose what happens when the message is already unflagged, whether the operation is idempotent, or any error conditions. This leaves some ambiguity for a mutation tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise (three sentences) and front-loaded with the primary usage condition. Every sentence adds value: use case, return value, exclusions, and id sourcing. No redundant or filler content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with an output schema, the description covers all essential guidance: when to use, when not to use, how to obtain the parameter, and what to expect as a return value. It is fully sufficient for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must compensate. It explains the single parameter 'id' by saying 'by id' and instructs the user to get the id from search-messages or list-messages first. This provides practical guidance beyond the raw schema pattern and helps the agent understand the param's provenance.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb ('removing'), the resource ('flag from a single message'), and the scope ('by id'). It also differentiates from sibling tools by specifying 'single' versus batch operations and removal versus adding a flag.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit 'Use when' and 'Do not use when' sections directly state the appropriate usage and name alternatives (batch-unflag-messages, flag-message). Additionally, it tells the user to obtain the id via search-messages or list-messages first, providing clear operational context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
use-templateA
Use when: composing a new draft from a saved template (by id), optionally overriding the recipients, subject, or body. Creates a draft in Mail.app for the user to review and send. Returns: a confirmation that a draft was created from the template. Do not use when: you want to inspect the template without composing (use get-template) or send immediately without a draft (use send-email).
| Name | Required | Description | Default |
|---|---|---|---|
| cc | No | Override CC recipients | |
| id | Yes | ||
| to | No | Override recipients | |
| body | No | Override body | |
| subject | No | Override subject |
Output Schema
| Name | Required | Description |
|---|---|---|
| id | No | |
| ok | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the burden of disclosing behavior. It states that the tool creates a draft in Mail.app for review/send, not an immediate send, and returns a confirmation. It does not mention potential side effects on the template, but the core behavior is well disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is concise and front-loaded with a direct 'Use when' statement. Every sentence adds value, clearly stating purpose, return value, and exclusions without unnecessary detail.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 5 params and an output schema, the description covers the essential use case, return value, and alternatives. It is sufficiently complete given the simplicity of the operation and the presence of an output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 80%, so baseline is 3. The description adds meaning by explaining the id is a template id and that recipients/subject/body are optional overrides. This clarifies the workflow beyond the schema, especially for the undocumented id parameter.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states that the tool composes a new draft from a saved template by id, with optional overrides for recipients, subject, or body. It explicitly distinguishes itself from get-template and send-email, naming these sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit 'Use when' and 'Do not use when' guidance with named alternatives (get-template, send-email). This makes the appropriate context and exclusions unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v2.20.47- Changed
search-messages1 field changed- changed
Input schema / properties / body / descriptionPrevious value: -"Text to search for in the message body (server-side IMAP BODY search). Requires the IMAP backend for the account searched; AppleScript-only accounts cannot search bodies and are reported as not searched."New value: +"Text to search for in the message body (server-side IMAP BODY search). Requires the IMAP backend for the account searched, or the opt-in local body index (APPLE_MAIL_MCP_BODY_INDEX); otherwise AppleScript-only accounts cannot search bodies and are reported as not searched."
4 tool updates
v2.20.45- Changed
forward-message2 fields changed- added
Input schema / properties / accountAdded value: +{ + "description": "For a numeric id, the account it was listed from; pair with mailbox. Omit for an imap: id.", + "type": "string" +} - added
Input schema / properties / mailboxAdded value: +{ + "description": "For a numeric id, the exact source mailbox path; pair with account. A miss never searches elsewhere.", + "type": "string" +}
- Changed
list-messages2 fields changed- added
Output schema / properties / backendAdded value: +{ + "enum": [ + "envelope-index", + "applescript", + "mixed" + ], + "type": "string" +} - added
Output schema / properties / backendByAccountAdded value: +{ + "additionalProperties": { + "enum": [ + "envelope-index", + "applescript" + ], + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +}
- Changed
reply-to-message2 fields changed- added
Input schema / properties / accountAdded value: +{ + "description": "For a numeric id, the account it was listed from; pair with mailbox. Omit for an imap: id.", + "type": "string" +} - added
Input schema / properties / mailboxAdded value: +{ + "description": "For a numeric id, the exact source mailbox path; pair with account. A miss never searches elsewhere.", + "type": "string" +}
- Changed
search-messages2 fields changed- added
Output schema / properties / backendAdded value: +{ + "enum": [ + "envelope-index", + "applescript", + "mixed" + ], + "type": "string" +} - added
Output schema / properties / backendByAccountAdded value: +{ + "additionalProperties": { + "enum": [ + "envelope-index", + "applescript" + ], + "type": "string" + }, + "propertyNames": { + "type": "string" + }, + "type": "object" +}
4 tool updates
v2.20.39- Changed
create-draft1 field changed- added
Input schema / properties / compositionAdded value: +{ + "const": "native-editor", + "description": "Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message.", + "type": "string" +}
- Changed
forward-message1 field changed- added
Input schema / properties / compositionAdded value: +{ + "const": "native-editor", + "description": "Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message.", + "type": "string" +}
- Changed
reply-to-message1 field changed- added
Input schema / properties / compositionAdded value: +{ + "const": "native-editor", + "description": "Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message.", + "type": "string" +}
- Changed
send-email1 field changed- added
Input schema / properties / compositionAdded value: +{ + "const": "native-editor", + "description": "Opt in to Mail's visible native editor: inserts only new prose, preserving its signature, HTML quotation and reply ancestry without the AppleScript content setter. Requires an unlocked desktop and existing Accessibility and Automation permissions. Uses Mail.app even when SMTP is configured; cannot be combined with transport=smtp or caller-built threading headers. New messages require an explicit account. Do not interact with Mail or the clipboard while it runs. Failure never falls back to another composition method or sends an unverified message.", + "type": "string" +}
2 tool updates
v2.20.31- Changed
send-email1 field changed- changed
Input schema / properties / transport / descriptionPrevious value: -"Send transport. 'smtp' submits clean MIME directly via SMTP, avoiding the macOS 15+ Mail.app <blockquote> wrapping (issue #12); requires APPLE_MAIL_MCP_SMTP_* env config. 'applescript' sends through Mail.app. If omitted, SMTP is used automatically when APPLE_MAIL_MCP_SMTP_* is configured, otherwise AppleScript."New value: +"Send transport. 'smtp' submits clean MIME directly via SMTP, avoiding the macOS 15+ Mail.app <blockquote> wrapping (issue #12); requires APPLE_MAIL_MCP_SMTP_* env config. 'applescript' sends through Mail.app. If omitted, configured SMTP is used unless account is a Mail.app account label (for example 'Work'), which selects AppleScript. Explicit SMTP uses the configured SMTP identity; it does not resolve Mail.app account labels. Without SMTP configuration, use AppleScript."
- Changed
send-serial-email2 fields changed- added
Input schema / properties / transportAdded value: +{ + "description": "Send transport for the whole batch. 'smtp' sends clean MIME without Mail.app quote wrapping and requires matching SMTP configuration; errors never fall back to AppleScript. SMTP accepts an allowed sender email address in account, not a Mail.app account label. 'applescript' explicitly uses Mail.app. If omitted, configured SMTP is used unless account is a Mail.app account label.", + "enum": [ + "applescript", + "smtp" + ], + "type": "string" +} - added
Output schema / properties / transportAdded value: +{ + "description": "Transport used for this batch", + "enum": [ + "smtp", + "applescript" + ], + "type": "string" +}
8 tool updates
v2.20.29- Changed
create-draft5 fields changed- removed
Input schema / properties / references / items / $refRemoved value: -"#/properties/inReplyTo" - added
Input schema / properties / references / items / descriptionAdded value: +"Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you." - added
Input schema / properties / references / items / maxLengthAdded value: +998 - added
Input schema / properties / references / items / patternAdded value: +"^<[^<>\\s@]+@[^<>\\s@]+>$" - added
Input schema / properties / references / items / typeAdded value: +"string"
- Changed
forward-message1 field changed- added
Output schema / properties / sentCopyAlreadyFiledAdded value: +{ + "description": "Present (true) only when sentCopy is true because the Sent mailbox already held this Message-ID (e.g. Gmail's SMTP files its own copy), so none was appended.", + "type": "boolean" +}
- Changed
get-thread1 field changed- added
Output schema / properties / failedMailboxReasons / propertyNamesAdded value: +{ + "type": "string" +}
- Changed
list-messages1 field changed- added
Output schema / properties / failedMailboxReasons / propertyNamesAdded value: +{ + "type": "string" +}
- Changed
reply-to-message1 field changed- added
Output schema / properties / sentCopyAlreadyFiledAdded value: +{ + "description": "Present (true) only when sentCopy is true because the Sent mailbox already held this Message-ID (e.g. Gmail's SMTP files its own copy), so none was appended.", + "type": "boolean" +}
- Changed
search-messages4 fields changed- removed
Input schema / properties / dateTo / $refRemoved value: -"#/properties/dateFrom" - added
Input schema / properties / dateTo / patternAdded value: +"^[a-zA-Z0-9 ,/\\-:]+$" - added
Input schema / properties / dateTo / typeAdded value: +"string" - added
Output schema / properties / failedMailboxReasons / propertyNamesAdded value: +{ + "type": "string" +}
- Changed
send-email6 fields changed- removed
Input schema / properties / references / items / $refRemoved value: -"#/properties/inReplyTo" - added
Input schema / properties / references / items / descriptionAdded value: +"Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you." - added
Input schema / properties / references / items / maxLengthAdded value: +998 - added
Input schema / properties / references / items / patternAdded value: +"^<[^<>\\s@]+@[^<>\\s@]+>$" - added
Input schema / properties / references / items / typeAdded value: +"string" - added
Output schema / properties / sentCopyAlreadyFiledAdded value: +{ + "description": "Present (true) only when sentCopy is true because the Sent mailbox already held this Message-ID (e.g. Gmail's SMTP files its own copy), so none was appended.", + "type": "boolean" +}
- Changed
send-serial-email1 field changed- added
Input schema / properties / recipients / items / properties / variables / propertyNamesAdded value: +{ + "type": "string" +}
3 tool updates
v2.20.16- Changed
create-draft2 fields changed- changed
Input schema / properties / account / descriptionPrevious value: -"Account to create draft in. With inReplyTo/references this must name a configured IMAP account (label or login); when omitted, the IMAP account matching the SMTP identity, else the only IMAP account, is used."New value: +"Account to create draft in: a Mail account name or an exact sender address, resolved and verified before the draft is saved (no match or a different sender → error, no draft). With inReplyTo/references this must name a configured IMAP account (label or login); when omitted, the IMAP account matching the SMTP identity, else the only IMAP account, is used." - added
Output schema / properties / fromAdded value: +{ + "description": "AppleScript draft: the sender Mail.app used", + "type": "string" +}
- Changed
send-email2 fields changed- changed
Input schema / properties / account / descriptionPrevious value: -"Account to send from"New value: +"Account to send from. AppleScript transport: a Mail account name or an exact sender address, resolved to one address and verified before sending; if it matches no enabled account, or Mail would use a different sender, nothing is sent and an error is returned. Omitted → Mail's default sender (reported back in `from`). SMTP transport: an email-form From override (see README)." - added
Output schema / properties / fromAdded value: +{ + "description": "AppleScript transport: the sender Mail.app used", + "type": "string" +}
- Changed
send-serial-email1 field changed- changed
Input schema / properties / account / descriptionPrevious value: -"Account to send from"New value: +"Account to send from. AppleScript transport: a Mail account name or an exact sender address, resolved to one address and verified before sending; if it matches no enabled account, or Mail would use a different sender, nothing is sent and an error is returned. Omitted → Mail's default sender (reported back in `from`). SMTP transport: an email-form From override (see README)."
6 tool updates
v2.20.11- Changed
create-draft9 fields changed- changed
Input schema / properties / account / descriptionPrevious value: -"Account to create draft in"New value: +"Account to create draft in. With inReplyTo/references this must name a configured IMAP account (label or login); when omitted, the IMAP account matching the SMTP identity, else the only IMAP account, is used." - added
Input schema / properties / inReplyToAdded value: +{ + "description": "Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you.", + "maxLength": 998, + "pattern": "^<[^<>\\s@]+@[^<>\\s@]+>$", + "type": "string" +} - added
Input schema / properties / referencesAdded value: +{ + "description": "Thread chain, oldest first: the parent's own References followed by the parent's Message-ID (each an angle-bracketed token). Emitted verbatim, never RFC 2047-encoded.", + "items": { + "$ref": "#/properties/inReplyTo" + }, + "maxItems": 100, + "minItems": 1, + "type": "array" +} - added
Output schema / properties / accountAdded value: +{ + "description": "Threaded draft only: IMAP account it was filed in", + "type": "string" +} - added
Output schema / properties / inReplyToAdded value: +{ + "type": "string" +} - added
Output schema / properties / mailboxAdded value: +{ + "description": "Threaded draft only: Drafts mailbox path", + "type": "string" +} - added
Output schema / properties / messageIdAdded value: +{ + "description": "Threaded draft only: the draft's Message-ID", + "type": "string" +} - added
Output schema / properties / referencesAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / transportAdded value: +{ + "enum": [ + "applescript", + "imap" + ], + "type": "string" +}
- Changed
forward-message2 fields changed- added
Input schema / properties / attachmentsAdded value: +{ + "description": "Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each.", + "items": { + "anyOf": [ + { + "description": "Absolute path to an existing file in an allowed read root", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "contentBase64": { + "description": "Base64-encoded file content (maximum 25 MiB decoded)", + "maxLength": 69905072, + "minLength": 1, + "type": "string" + }, + "filename": { + "description": "Filename to give the attachment", + "maxLength": 255, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "filename", + "contentBase64" + ], + "type": "object" + } + ] + }, + "maxItems": 20, + "type": "array" +} - added
Output schema / properties / attachmentCountAdded value: +{ + "type": "number" +}
- Changed
list-messages2 fields changed- added
Output schema / properties / stoppedByAdded value: +{ + "enum": [ + "deadline", + "cancelled" + ], + "type": "string" +} - added
Output schema / properties / timedOutMailboxesAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +}
- Changed
reply-to-message2 fields changed- added
Input schema / properties / attachmentsAdded value: +{ + "description": "Files to attach: absolute paths in the configured attachment read roots (e.g. '/Users/me/Documents/report.pdf') and/or inline {filename, contentBase64} objects up to 25 MiB decoded each.", + "items": { + "anyOf": [ + { + "description": "Absolute path to an existing file in an allowed read root", + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "contentBase64": { + "description": "Base64-encoded file content (maximum 25 MiB decoded)", + "maxLength": 69905072, + "minLength": 1, + "type": "string" + }, + "filename": { + "description": "Filename to give the attachment", + "maxLength": 255, + "minLength": 1, + "type": "string" + } + }, + "required": [ + "filename", + "contentBase64" + ], + "type": "object" + } + ] + }, + "maxItems": 20, + "type": "array" +} - added
Output schema / properties / attachmentCountAdded value: +{ + "type": "number" +}
- Changed
search-messages3 fields changed- added
Input schema / properties / flagColorAdded value: +{ + "description": "Filter to messages flagged with exactly this color (same palette as flag-message: red/orange/yellow/green/blue/purple/gray — 'grey' accepted). A message flagged with Mail's plain default (no explicit color) never matches. Implies isFlagged: true — pairing this with isFlagged: false is rejected as contradictory. Caveat on IMAP accounts only: 'red' is Mail's all-bits-clear encoding, wire-identical to a plain (colorless) flag, so red: true in results and flagColor: 'red' in this filter both also match a plain flag there — this is inherent to Apple's $MailFlagBit0/1/2 scheme, not specific to this server. AppleScript-only accounts distinguish red correctly (Mail's own `flag index` 0 vs -1).", + "enum": [ + "red", + "orange", + "yellow", + "green", + "blue", + "purple", + "gray", + "grey" + ], + "type": "string" +} - added
Output schema / properties / stoppedByAdded value: +{ + "enum": [ + "deadline", + "cancelled" + ], + "type": "string" +} - added
Output schema / properties / timedOutMailboxesAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +}
- Changed
send-email3 fields changed- added
Input schema / properties / inReplyToAdded value: +{ + "description": "Thread this message as a reply: the parent's Message-ID token, angle brackets included (e.g. '<abc123@mail.example.com>'). Emitted verbatim as In-Reply-To, never RFC 2047-encoded. When `references` is omitted, References defaults to [inReplyTo]. Prefer reply-to-message, which derives recipients, threading and the quote from the original for you.", + "maxLength": 998, + "pattern": "^<[^<>\\s@]+@[^<>\\s@]+>$", + "type": "string" +} - added
Input schema / properties / referencesAdded value: +{ + "description": "Thread chain, oldest first: the parent's own References followed by the parent's Message-ID (each an angle-bracketed token). Emitted verbatim, never RFC 2047-encoded.", + "items": { + "$ref": "#/properties/inReplyTo" + }, + "maxItems": 100, + "minItems": 1, + "type": "array" +} - added
Output schema / properties / messageIdAdded value: +{ + "type": "string" +}
3 tool updates
v2.19.19- Changed
get-thread2 fields changed- added
Output schema / properties / failedMailboxReasonsAdded value: +{ + "additionalProperties": { + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / omittedMessagesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "mailbox": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "uid": { + "type": "number" + } + }, + "required": [ + "id", + "mailbox", + "uid", + "reason" + ], + "type": "object" + }, + "type": "array" +}
- Changed
list-messages1 field changed- added
Output schema / properties / omittedMessagesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "mailbox": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "uid": { + "type": "number" + } + }, + "required": [ + "id", + "mailbox", + "uid", + "reason" + ], + "type": "object" + }, + "type": "array" +}
- Changed
search-messages2 fields changed- added
Input schema / properties / offsetAdded value: +{ + "description": "Number of newest matches to skip (for pagination; IMAP accounts only). Without a mailbox, offset + limit may not exceed 5,000 — name a mailbox to page deeper.", + "minimum": 0, + "type": "integer" +} - added
Output schema / properties / omittedMessagesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "mailbox": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "uid": { + "type": "number" + } + }, + "required": [ + "id", + "mailbox", + "uid", + "reason" + ], + "type": "object" + }, + "type": "array" +}
2 tool updates
v2.19.14- Changed
list-messages1 field changed- added
Output schema / properties / failedMailboxReasonsAdded value: +{ + "additionalProperties": { + "type": "string" + }, + "type": "object" +}
- Changed
search-messages1 field changed- added
Output schema / properties / failedMailboxReasonsAdded value: +{ + "additionalProperties": { + "type": "string" + }, + "type": "object" +}
4 tool updates
v2.19.12- Changed
forward-message1 field changed- changed
Input schema / properties / body / descriptionPrevious value: -"Optional message to prepend"New value: +"Optional message to prepend (plain text)"
- Added
get-message-rfc822 - Changed
reply-to-message1 field changed- added
Input schema / properties / body / descriptionAdded value: +"Reply body (plain text; HTML tags such as <br> are not rendered)"
- Changed
search-messages2 fields changed- added
Input schema / properties / bodyAdded value: +{ + "description": "Text to search for in the message body (server-side IMAP BODY search). Requires the IMAP backend for the account searched; AppleScript-only accounts cannot search bodies and are reported as not searched.", + "type": "string" +} - changed
Input schema / properties / query / descriptionPrevious value: -"Text to search for in subject, sender, or content"New value: +"Text to search for in subject or sender"
2 tool updates
v2.19.7- Changed
get-message1 field changed- changed
Output schema / properties / dateSent / descriptionPrevious value: -"ISO 8601 send time from the message's Date: header (Mail's `date sent`). Absent when the message carries no parseable Date: header."New value: +"ISO 8601 send time from the message's Date: header (Mail's `date sent`). Absent when the message carries no parseable Date: header, or when the value is more than 7 days later than dateReceived — a send time after arrival is not a real one."
- Changed
get-message-headers2 fields changed- added
Output schema / properties / backendAdded value: +{ + "description": "Which backend read the headers: \"imap\" (the stored bytes over IMAP) or \"applescript\" (Mail's `all headers` property, which Mail renders itself and can alter)", + "type": "string" +} - added
Output schema / properties / warningsAdded value: +{ + "description": "Repairs applied to a malformed header block, in plain words — e.g. a Date: value Mail.app dropped with the next header joined onto it. Absent when none fired; `raw` is never rewritten.", + "items": { + "type": "string" + }, + "type": "array" +}
2 tool updates
v2.19.2- Changed
get-message2 fields changed- added
Output schema / properties / dateReceivedAdded value: +{ + "description": "ISO 8601 arrival time in the mailbox (IMAP INTERNALDATE / Mail's `date received`). A migration or re-import resets this; compare with dateSent.", + "type": "string" +} - added
Output schema / properties / dateSentAdded value: +{ + "description": "ISO 8601 send time from the message's Date: header (Mail's `date sent`). Absent when the message carries no parseable Date: header.", + "type": "string" +}
- Added
get-message-headers
3 tool updates
v2.18.0- Changed
forward-message2 fields changed- added
Output schema / properties / sentCopyAdded value: +{ + "description": "SMTP only: whether a best-effort copy was filed to the account's Sent mailbox over IMAP (issue #220). Absent when no configured IMAP account matches the SMTP identity — not a failure, the feature simply wasn't engaged.", + "type": "boolean" +} - added
Output schema / properties / sentCopyErrorAdded value: +{ + "description": "Present only when sentCopy is false: why the Sent-folder copy failed.", + "type": "string" +}
- Changed
reply-to-message2 fields changed- added
Output schema / properties / sentCopyAdded value: +{ + "description": "SMTP only: whether a best-effort copy was filed to the account's Sent mailbox over IMAP (issue #220). Absent when no configured IMAP account matches the SMTP identity — not a failure, the feature simply wasn't engaged.", + "type": "boolean" +} - added
Output schema / properties / sentCopyErrorAdded value: +{ + "description": "Present only when sentCopy is false: why the Sent-folder copy failed.", + "type": "string" +}
- Changed
send-email3 fields changed- added
Input schema / properties / replyToAdded value: +{ + "description": "SMTP only (issue #220): sets the Reply-To header when replies should go somewhere other than the From/login address, e.g. a domain-alias setup where APPLE_MAIL_MCP_SMTP_FROM differs from APPLE_MAIL_MCP_SMTP_USER. Ignored on the AppleScript transport.", + "type": "string" +} - added
Output schema / properties / sentCopyAdded value: +{ + "description": "SMTP only: whether a best-effort copy was filed to the account's Sent mailbox over IMAP (issue #220). Absent when no configured IMAP account matches the SMTP identity — not a failure, the feature simply wasn't engaged.", + "type": "boolean" +} - added
Output schema / properties / sentCopyErrorAdded value: +{ + "description": "Present only when sentCopy is false: why the Sent-folder copy failed.", + "type": "string" +}
TDQS
Scored across 52 tools
Descriptions are unusually rigorous, with explicit 'Use when' / 'Do not use when' cross-references that separate near-neighbors like search-messages vs list-messages, get-unread-count vs get-mail-stats, and health-check vs doctor vs get-sync-status. The main residual ambiguity is the five single-vs-batch pairs (flag/batch-flag, delete/batch-delete, etc.), which a hurried agent could pick between wrongly, though each description disambiguates them explicitly.
Nearly everything follows a kebab-case verb_noun pattern (search-messages, create-mailbox, delete-rule, batch-move-messages), which is highly predictable and even encodes the single/batch split. A few outliers break the verb-first rule — health-check, doctor, use-template, get-sync-status — but they are readable and few relative to 52 tools.
At 52 tools this surface is far past the comfortable range and much of the bulk comes from mechanical duplication: five operations each spawned as both a single and a batch variant, plus three separate diagnostic tools. The domain genuinely has breadth (messages, mailboxes, smart mailboxes, rules, templates, contacts, sending, diagnostics), but several tools could be collapsed or parameterized rather than exposed individually.
Coverage is strong and lifecycle-driven: full CRUD for messages, mailboxes, smart mailboxes, rules (except editing), templates, attachments, plus send/reply/forward/draft and diagnostics. Gaps are modest — rules can be created, enabled, disabled and deleted but not edited in place, and there is no draft-editing or junk/spam classification tool — but none is a hard blocker.
Maintenance
Related MCP Connectors
Email infrastructure for AI agents — send, receive, search, and reply to email over MCP.
Your IMAP mailbox as an MCP server: read, search and (if you allow it) organize mail. Open source.
Email inboxes for AI agents: send, receive, reply, search, and manage threaded email over MCP.
A MCP server for Gmail that lets you search, read, and draft emails and replies.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that enables AI models to read, search, and send emails via IMAP and SMTP protocols. It supports various providers like Gmail and Outlook, allowing for tasks such as retrieving unread messages, searching by sender, and managing mailbox folders.-
- AlicenseAqualityAmaintenanceAn MCP server for Apple Mail that enables Claude to read, search, manage, and compose emails via AppleScript.2021 npm1MIT
- AlicenseNot gradedqualityCmaintenanceAn MCP server that gives AI assistants comprehensive access to Apple Mail accounts, enabling email discovery, reading, flag management, and server-side message retrieval.MIT
- AlicenseAqualityBmaintenanceAn MCP server that provides programmatic access to Apple Mail, enabling AI assistants like Claude to read, send, search, and manage emails on macOS.25MIT