apple-mail-mcp 2.10.2 → 2.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -147,6 +147,7 @@ tool, and troubleshooting. Verify any time by running the **`doctor`** tool.
147
147
 
148
148
  - **macOS** - Apple Mail and AppleScript are macOS-only
149
149
  - **Node.js 20+** - Required for the MCP server
150
+ - **Node.js 22.5+ and Full Disk Access** - Required by `search-contacts` only. It reads the Contacts database directly through Node's built-in `node: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](https://github.com/sweetrb/apple-mail-mcp/blob/main/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md).
150
151
  - **Apple Mail** - Must have at least one account configured (iCloud, Gmail, Exchange, etc.)
151
152
 
152
153
  ## Features
@@ -264,6 +265,8 @@ Get the full content of a message.
264
265
  |-----------|------|----------|-------------|
265
266
  | `id` | string | Yes | Message ID |
266
267
  | `preferHtml` | boolean | No | Return HTML source instead of plain text |
268
+ | `mailbox` | string | No | Mailbox holding the message (e.g. `"Sent Items"`). With `account`, opens that mailbox directly instead of scanning every mailbox — this is the fix for timeouts on large folders |
269
+ | `account` | string | No | Account holding the message. Pair with `mailbox` to skip the cross-mailbox scan |
267
270
 
268
271
  **Returns:** Subject line and message body (plain text by default, HTML if `preferHtml` is true and HTML content is available).
269
272
 
@@ -668,7 +671,7 @@ Return an attachment's bytes as base64 (the read counterpart to inline-base64 se
668
671
 
669
672
  | Parameter | Type | Required | Description |
670
673
  |-----------|------|----------|-------------|
671
- | `id` | string | Yes | Numeric message ID |
674
+ | `id` | string | Yes | Message ID (numeric or `imap:…`) |
672
675
  | `attachmentName` | string | Yes | Attachment filename (from `list-attachments`) |
673
676
 
674
677
  **Returns:** The attachment bytes, base64-encoded (also in `structuredContent.contentBase64`).
@@ -748,6 +751,8 @@ Flag or unflag a message. `flag-message` optionally takes a flag **color**; `unf
748
751
 
749
752
  **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.
750
753
 
754
+ To **read** a color, the IMAP read path returns `flagColorIndex` in `structuredContent` — the same 0-6 palette index, omitted when the message carries no color bits. The AppleScript read path does not populate it.
755
+
751
756
  ---
752
757
 
753
758
  #### `delete-message`
@@ -853,8 +858,12 @@ Get unread message count.
853
858
 
854
859
  | Parameter | Type | Required | Description |
855
860
  |-----------|------|----------|-------------|
856
- | `mailbox` | string | No | Mailbox to check (omit for total) |
857
- | `account` | string | No | Account to check |
861
+ | `mailbox` | string | No | Mailbox to check (omit for **INBOX**) |
862
+ | `account` | string | No | Account to check (omit to sum each account's INBOX) |
863
+
864
+ **Returns:** The unread count for the requested scope.
865
+
866
+ > Omitting `mailbox` counts **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 use [`get-mail-stats`](#get-mail-stats).
858
867
 
859
868
  ---
860
869
 
@@ -896,6 +905,8 @@ Rename a mailbox (creates new, moves messages, deletes old).
896
905
 
897
906
  ### Smart Mailbox Operations (intelligente Postfächer)
898
907
 
908
+ > **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](https://github.com/sweetrb/apple-mail-mcp/blob/main/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md).
909
+
899
910
  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.
900
911
 
901
912
  **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.
@@ -1030,14 +1041,15 @@ Delete a mail rule by name.
1030
1041
 
1031
1042
  #### `search-contacts`
1032
1043
 
1033
- Search contacts in Contacts.app.
1044
+ Search the macOS Contacts database by name, organization, nickname, or email substring.
1045
+
1046
+ 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](#requirements)). Without either, the tool returns an empty list rather than an error.
1034
1047
 
1035
1048
  | Parameter | Type | Required | Description |
1036
1049
  |-----------|------|----------|-------------|
1037
- | `query` | string | Yes | Name to search for |
1038
- | `limit` | number | No | Max results (default: 10) |
1050
+ | `query` | string | Yes | Substring matched against full name, organization, nickname, or any email address |
1039
1051
 
1040
- **Returns:** List of contacts with name, email addresses, and phone numbers.
1052
+ **Returns:** List of contacts with name, email addresses, and phone numbers. Results are **not** truncated — a broad query returns every match.
1041
1053
 
1042
1054
  ---
1043
1055
 
@@ -1130,7 +1142,9 @@ Run a full setup diagnostic: Mail.app automation permission, account state (flag
1130
1142
 
1131
1143
  Get mail statistics.
1132
1144
 
1133
- **Parameters:** None
1145
+ | Parameter | Type | Required | Description |
1146
+ |-----------|------|----------|-------------|
1147
+ | `account` | string | No | Limit to one account (uses fast IMAP `STATUS` when that account is IMAP-configured). Omit to merge across all accounts. |
1134
1148
 
1135
1149
  **Returns:** Total and per-account message/unread counts, plus recently received stats (24h, 7d, 30d).
1136
1150
 
@@ -1271,7 +1285,7 @@ The entrypoint is written as:
1271
1285
 
1272
1286
  ## Security and Privacy
1273
1287
 
1274
- - **Local only** - All operations happen locally via AppleScript. No data is sent to external servers.
1288
+ - **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.
1275
1289
  - **Permission required** - macOS will prompt for automation permission on first use.
1276
1290
  - **No credential storage** - The server doesn't store any passwords or authentication tokens.
1277
1291
  - **Email safety** - Use `create-draft` to review emails before sending.
@@ -1285,7 +1299,7 @@ The entrypoint is written as:
1285
1299
  | macOS only | Apple Mail and AppleScript are macOS-specific |
1286
1300
  | MCP `send-email` is plain-text | The `send-email` tool sends plain text (reading HTML content is supported). To send HTML, use the bundled `apple-mail-send` CLI with `--html-body-file` (sends `multipart/alternative` via SMTP) |
1287
1301
  | Attachments require absolute paths | File attachments must use full absolute paths (e.g., `/Users/me/file.pdf`) |
1288
- | No smart mailboxes | Cannot access Smart Mailboxes via AppleScript |
1302
+ | Smart mailboxes need Mail quit | Smart mailboxes are supported (see [Smart Mailbox Operations](#smart-mailbox-operations-intelligente-postfächer)), but `create-`/`delete-smart-mailbox` edit `SyncedSmartMailboxes.plist` directly — a running Mail may not show a new one until relaunched, and can overwrite plist edits it didn't make. Quit Mail first. Reading them needs Full Disk Access for the Node runtime |
1289
1303
  | Very large mailboxes not searchable *via AppleScript* | Apple Mail's AppleScript bridge times out on mailboxes with tens of thousands of messages, so unscoped `search-messages` skips mailboxes above `APPLE_MAIL_MAX_SEARCH_MAILBOX` (default 5000) and reports them as a partial result. Scope with `mailbox` + a date window — or configure the [IMAP backend](#imap-backend--opt-in), which searches these server-side in well under a second. ([#24](https://github.com/sweetrb/apple-mail-mcp/issues/24)) |
1290
1304
  | Can't delete/rename server-side mailboxes or mutate drafts *via AppleScript* | Mail.app's AppleScript bridge can only `delete`/`rename` **local "On My Mac"** mailboxes and cannot delete/move drafts — it throws `AppleEvent handler failed` for IMAP/Gmail/Workspace/iCloud/Exchange mailboxes (the GUI can do it). Without IMAP configured, `delete-mailbox`/`rename-mailbox`/`delete-message`/`move-message` return a clear "do it in Mail.app directly" error instead of a generic failure. With the [IMAP backend](#imap-backend--opt-in) configured for the account, these operations run via IMAP and succeed. ([#42](https://github.com/sweetrb/apple-mail-mcp/issues/42)) |
1291
1305
  | Message ID format | Message IDs must be numeric (AppleScript ids) or `imap:…` tokens from the IMAP read path (validated by schema) |
@@ -1385,14 +1399,16 @@ The `\\\\` in JSON becomes `\\` in the actual string, which represents a single
1385
1399
 
1386
1400
  ## Development
1387
1401
 
1402
+ 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.
1403
+
1388
1404
  ```bash
1389
- npm install # Install dependencies
1390
- npm run build # Typecheck, then bundle src/index.ts + src/cli.ts into build/ (esbuild)
1391
- npm test # Run unit tests
1392
- npm run test:integration # Run integration tests (requires Mail.app)
1393
- npm run test:all # Run all tests (unit + integration)
1394
- npm run lint # Check code style
1395
- npm run format # Format code
1405
+ corepack enable && pnpm install --frozen-lockfile # Install dependencies
1406
+ pnpm run build # Typecheck, then bundle src/index.ts + src/cli.ts into build/ (esbuild)
1407
+ pnpm test # Run unit tests
1408
+ pnpm run test:integration # Run integration tests (requires Mail.app)
1409
+ pnpm run test:all # Run all tests (unit + integration)
1410
+ pnpm run lint # Check code style
1411
+ pnpm run format # Format code
1396
1412
  ```
1397
1413
 
1398
1414
  ---
package/build/index.js CHANGED
@@ -76079,6 +76079,10 @@ function executeAppleScript(script, options = {}) {
76079
76079
  return lastError;
76080
76080
  }
76081
76081
 
76082
+ // src/utils/docsUrls.ts
76083
+ var SETUP_GUIDE_URL = "https://github.com/sweetrb/apple-mail-mcp/blob/main/docs/IMAP-SETUP.md";
76084
+ var SETUP_HINT = `Setup guide: ${SETUP_GUIDE_URL} \u2014 run the "doctor" tool to check your setup.`;
76085
+
76082
76086
  // src/utils/mimeParse.ts
76083
76087
  function extractBoundary(source) {
76084
76088
  const match = source.match(/boundary="?([^";\s\r\n]+)"?/i);
@@ -76974,7 +76978,7 @@ var AppleMailManager = class {
76974
76978
  serverSideCreateGuard(account, op) {
76975
76979
  if (this.isServerSideAccount(account) === true) {
76976
76980
  const verb = op === "rename" ? "rename" : "create";
76977
- return `Account "${account}" stores its mailboxes on the server (IMAP / iCloud / Exchange), and Mail.app cannot ${verb} server-side mailboxes via AppleScript \u2014 a ${verb} would ${op === "rename" ? "leave a half-created orphan" : "orphan a mailbox that can never be removed"}. Configure IMAP for this account (APPLE_MAIL_MCP_IMAP_*) so mailbox create/delete/rename route through the server, or manage the folder in Mail.app directly.`;
76981
+ return `Account "${account}" stores its mailboxes on the server (IMAP / iCloud / Exchange), and Mail.app cannot ${verb} server-side mailboxes via AppleScript \u2014 a ${verb} would ${op === "rename" ? "leave a half-created orphan" : "orphan a mailbox that can never be removed"}. Configure IMAP for this account (APPLE_MAIL_MCP_IMAP_*) so mailbox create/delete/rename route through the server, or manage the folder in Mail.app directly. ${SETUP_HINT}`;
76978
76982
  }
76979
76983
  return null;
76980
76984
  }
@@ -79739,12 +79743,6 @@ var import_nodemailer = __toESM(require_nodemailer(), 1);
79739
79743
  import { execFileSync } from "child_process";
79740
79744
  import { isAbsolute as isAbsolute2 } from "path";
79741
79745
  import { existsSync as existsSync4 } from "fs";
79742
-
79743
- // src/utils/docsUrls.ts
79744
- var SETUP_GUIDE_URL = "https://github.com/sweetrb/apple-mail-mcp/blob/main/docs/IMAP-SETUP.md";
79745
- var SETUP_HINT = `Setup guide: ${SETUP_GUIDE_URL} \u2014 run the "doctor" tool to check your setup.`;
79746
-
79747
- // src/services/smtpMailer.ts
79748
79746
  var SMTP_ENV = {
79749
79747
  host: "APPLE_MAIL_MCP_SMTP_HOST",
79750
79748
  port: "APPLE_MAIL_MCP_SMTP_PORT",
@@ -80144,7 +80142,7 @@ function specToConfig(spec) {
80144
80142
  }
80145
80143
  if (!pass) {
80146
80144
  throw new Error(
80147
- `No IMAP password for account "${spec.accountLabel}". Set a password or a Keychain service/account.`
80145
+ `No IMAP password for account "${spec.accountLabel}". Set a password or a Keychain service/account. ${SETUP_HINT}`
80148
80146
  );
80149
80147
  }
80150
80148
  return {
@@ -83252,7 +83250,7 @@ server.registerTool(
83252
83250
  withErrorHandling(({ query }) => {
83253
83251
  const contacts = mailManager.searchContacts(query);
83254
83252
  const structured = {
83255
- contacts: contacts.map((c) => ({ name: c.name, emails: c.emails })),
83253
+ contacts: contacts.map((c) => ({ name: c.name, emails: c.emails, phones: c.phones })),
83256
83254
  count: contacts.length
83257
83255
  };
83258
83256
  if (contacts.length === 0) {
@@ -83260,7 +83258,8 @@ server.registerTool(
83260
83258
  }
83261
83259
  const contactList = contacts.map((c) => {
83262
83260
  const emails = c.emails.length > 0 ? c.emails.join(", ") : "no email";
83263
- return ` - ${c.name} (${emails})`;
83261
+ const phones = c.phones.length > 0 ? `; ${c.phones.join(", ")}` : "";
83262
+ return ` - ${c.name} (${emails}${phones})`;
83264
83263
  }).join("\n");
83265
83264
  return successResponse(`Found ${contacts.length} contact(s):
83266
83265
  ${contactList}`, structured);
@@ -160,9 +160,22 @@ non-secret config goes here — passwords stay in the Keychain.**
160
160
  ### `APPLE_MAIL_MCP_IMAP_ACCOUNT` and routing
161
161
 
162
162
  `APPLE_MAIL_MCP_IMAP_ACCOUNT` should equal the **Mail.app account name** (what
163
- `list-accounts` shows). A tool call routes to IMAP only when its `account`
164
- argument matches a configured account's name or login — so this value is how the
165
- server knows "calls for this account go to IMAP."
163
+ `list-accounts` shows), because that is how an **explicitly named** `account`
164
+ argument is matched to an IMAP config.
165
+
166
+ **Since v2.6.0 you do not need to pass `account` at all for reads.** When any
167
+ IMAP account is configured, the read tools (`search-messages`, `get-thread`,
168
+ `list-messages`, `list-mailboxes`, `get-unread-count`, `get-mail-stats`) prefer
169
+ IMAP automatically:
170
+
171
+ - explicit IMAP `account` → that account over IMAP;
172
+ - explicit non-IMAP `account` → AppleScript;
173
+ - **no `account` → fan out over every configured IMAP account**, with AppleScript
174
+ used only for the accounts no IMAP config covers.
175
+
176
+ The name still matters for targeting a specific account and for the
177
+ mailbox-write ops (`create`/`delete`/`rename-mailbox`), which route to IMAP only
178
+ for an explicitly named IMAP account.
166
179
 
167
180
  ### Multiple accounts
168
181
 
@@ -285,9 +298,17 @@ and a server started there can't read passwords either. Options:
285
298
  restrict the config file's permissions to the service account.
286
299
 
287
300
  **Calls aren't routing to IMAP even though `doctor` shows connected.**
288
- The tool's `account` argument must match the configured account's name/login.
289
- Set `APPLE_MAIL_MCP_IMAP_ACCOUNT` to the exact Mail.app account name from
290
- `list-accounts`, and pass that same `account` to tools.
301
+ For the six **read** tools this should not happen since v2.6.0 — they prefer IMAP
302
+ as soon as any account is configured, with no `account` argument needed. If reads
303
+ still look like AppleScript (slow, or partial-coverage warnings), the usual cause
304
+ is that the server never saw your settings at all: a host that strips the `env`
305
+ block (use the `config.json` method), or a Keychain service/account mismatch so
306
+ the password lookup fails. Run `doctor` — it reports each account separately.
307
+
308
+ For **targeting one specific account**, and for the mailbox-write ops
309
+ (`create`/`delete`/`rename-mailbox`), the `account` argument must match the
310
+ configured account's name/login: set `APPLE_MAIL_MCP_IMAP_ACCOUNT` to the exact
311
+ Mail.app account name from `list-accounts` and pass that same `account`.
291
312
 
292
313
  **A disabled Mail account.** IMAP connects to the server directly, so it works
293
314
  even if the account is disabled in Mail.app — useful for reaching an account the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.10.2",
3
+ "version": "2.10.3",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",