apple-mail-mcp 2.15.1 → 2.16.0

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
@@ -953,6 +953,11 @@ accounts only: the local store is a store, not an account. Nothing selects it
953
953
  implicitly — omitting `account` still resolves to a real account for every other
954
954
  tool.
955
955
 
956
+ The mail inside them is reachable too: `list-messages` and `search-messages`
957
+ accept `account="On My Mac"`, and `get-message` resolves an id that lives only in
958
+ a local mailbox. An id present **both** in an account mailbox and locally is
959
+ reported as ambiguous rather than silently resolving to the account copy.
960
+
956
961
  ---
957
962
 
958
963
  #### `get-unread-count`
package/build/index.js CHANGED
@@ -78980,7 +78980,7 @@ function describeMailboxOpError(op, raw) {
78980
78980
  const trimmed = (raw || "").trim();
78981
78981
  if (UNSUPPORTED_APPLESCRIPT_OP.test(trimmed)) {
78982
78982
  const verb = op.charAt(0).toUpperCase() + op.slice(1);
78983
- return `Mail.app cannot ${op} server-side (IMAP / Gmail / Workspace / iCloud / Exchange) mailboxes via AppleScript \u2014 only local "On My Mac" mailboxes support this. ${verb} it in Mail.app directly. (Mail.app error: ${trimmed})`;
78983
+ return `Mail.app's scripting bridge will not ${op} this mailbox. That covers server-side (IMAP / Gmail / Workspace / iCloud / Exchange) mailboxes, and on current macOS it covers local "On My Mac" mailboxes too \u2014 measured 2026-08-16, see #193. ${verb} it in Mail.app directly; for an IMAP-configured account the IMAP path can do it instead. (Mail.app error: ${trimmed})`;
78984
78984
  }
78985
78985
  return trimmed || `Failed to ${op} mailbox`;
78986
78986
  }
@@ -80227,7 +80227,9 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80227
80227
  }
80228
80228
  return { messages: allMessages.slice(0, limit), diagnostics };
80229
80229
  }
80230
- const targetAccount = this.resolveAccount(account);
80230
+ const local = isLocalStoreLabel(account);
80231
+ const targetAccount = local ? LOCAL_STORE_LABEL : this.resolveAccount(account);
80232
+ const mbIter = local ? "_mbs" : "mailboxes";
80231
80233
  const searchCondition = buildSearchCondition({ query, from, subject, isRead, isFlagged });
80232
80234
  let dateSetup = "";
80233
80235
  let dateFilter = "";
@@ -80259,7 +80261,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80259
80261
  set _wantNames to ${nameList}
80260
80262
  set msgCount to 0
80261
80263
  set seenIds to {}
80262
- repeat with mb in mailboxes
80264
+ repeat with mb in ${mbIter}
80263
80265
  if msgCount >= ${limit} then exit repeat
80264
80266
  set mbName to ""
80265
80267
  try
@@ -80304,7 +80306,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80304
80306
  set _skipped to ""
80305
80307
  set _notSearched to ""
80306
80308
  set _startedAt to current date
80307
- repeat with mb in mailboxes
80309
+ repeat with mb in ${mbIter}
80308
80310
  if msgCount >= ${limit} then exit repeat
80309
80311
  set mbName to ""
80310
80312
  try
@@ -80334,7 +80336,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80334
80336
  return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "${DIAG_FIELD_SEP}skipped=" & _skipped & "${DIAG_FIELD_SEP}notSearched=" & _notSearched
80335
80337
  `;
80336
80338
  }
80337
- const script = buildAccountScopedScript(targetAccount, searchCommand);
80339
+ const script = local ? buildAppLevelScript(`${localMailboxBindingFragment()}${searchCommand}`) : buildAccountScopedScript(targetAccount, searchCommand);
80338
80340
  const result = executeAppleScript(script, { timeoutMs: SEARCH_ACCOUNT_TIMEOUT_MS });
80339
80341
  if (!result.success) {
80340
80342
  console.error(`Failed to search messages in "${targetAccount}": ${result.error}`);
@@ -80445,9 +80447,16 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80445
80447
  */
80446
80448
  scopedByIdScript(account, mailbox, id, innerAction) {
80447
80449
  const resolved = this.resolveMailbox(mailbox, account);
80448
- return buildAppLevelScript(`
80449
- try
80450
- set acct to (first account whose name is "${escapeForAppleScript(account)}")
80450
+ const bind = isLocalStoreLabel(account) ? `${localMailboxBindingFragment()}
80451
+ set targetMb to missing value
80452
+ ignoring case
80453
+ repeat with mb in _mbs
80454
+ if (name of mb) is "${escapeForAppleScript(resolved)}" then
80455
+ set targetMb to mb
80456
+ exit repeat
80457
+ end if
80458
+ end repeat
80459
+ end ignoring` : `set acct to (first account whose name is "${escapeForAppleScript(account)}")
80451
80460
  set targetMb to missing value
80452
80461
  ignoring case
80453
80462
  repeat with mb in mailboxes of acct
@@ -80456,7 +80465,10 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80456
80465
  exit repeat
80457
80466
  end if
80458
80467
  end repeat
80459
- end ignoring
80468
+ end ignoring`;
80469
+ return buildAppLevelScript(`
80470
+ try
80471
+ ${bind}
80460
80472
  if targetMb is not missing value then
80461
80473
  set matchingMsgs to (messages of targetMb whose id is ${Number(id)})
80462
80474
  if (count of matchingMsgs) > 0 then
@@ -80520,6 +80532,19 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80520
80532
  end try
80521
80533
  end repeat
80522
80534
  end repeat
80535
+ -- #183: local mailboxes belong to no account, so the walk above cannot
80536
+ -- reach them. Collect into the SAME _hits/_names, which means an id
80537
+ -- present both in an account and locally is now correctly reported as
80538
+ -- ambiguous rather than silently resolving to the account copy.${localMailboxBindingFragment()}
80539
+ repeat with mb in _mbs
80540
+ try
80541
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
80542
+ if (count of matchingMsgs) > 0 then
80543
+ set end of _hits to item 1 of matchingMsgs
80544
+ set _names to _names & "${LOCAL_STORE_LABEL}/" & (name of mb) & ", "
80545
+ end if
80546
+ end try
80547
+ end repeat
80523
80548
  if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
80524
80549
  if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
80525
80550
  if (count of _hits) is 1 then
@@ -80610,6 +80635,19 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80610
80635
  end try
80611
80636
  end repeat
80612
80637
  end repeat
80638
+ -- #183: local mailboxes belong to no account, so the walk above cannot
80639
+ -- reach them. Collect into the SAME _hits/_names, which means an id
80640
+ -- present both in an account and locally is now correctly reported as
80641
+ -- ambiguous rather than silently resolving to the account copy.${localMailboxBindingFragment()}
80642
+ repeat with mb in _mbs
80643
+ try
80644
+ set matchingMsgs to (messages of mb whose id is ${Number(id)})
80645
+ if (count of matchingMsgs) > 0 then
80646
+ set end of _hits to item 1 of matchingMsgs
80647
+ set _names to _names & "${LOCAL_STORE_LABEL}/" & (name of mb) & ", "
80648
+ end if
80649
+ end try
80650
+ end repeat
80613
80651
  if (count of _hits) is 0 then return "${LOOKUP_ERROR_MARKER}Message not found"
80614
80652
  if (count of _hits) > 1 then return "${LOOKUP_ERROR_MARKER}${AMBIGUOUS_ID_PREFIX}${Number(id)} is present in more than one mailbox (" & _names & "); list or search that mailbox first so the read targets the right copy"
80615
80653
  if (count of _hits) is 1 then
@@ -80673,10 +80711,12 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80673
80711
  }
80674
80712
  return { messages: allMessages.slice(0, limit), diagnostics };
80675
80713
  }
80676
- const targetAccount = this.resolveAccount(account);
80714
+ const local = isLocalStoreLabel(account);
80715
+ const targetAccount = local ? LOCAL_STORE_LABEL : this.resolveAccount(account);
80677
80716
  const safeFrom = from ? escapeForAppleScript(from) : "";
80678
80717
  const fromFilter = from ? `whose sender contains "${safeFrom}"` : "";
80679
80718
  const scanThreshold = getMailboxScanThreshold();
80719
+ const mbIter = local ? "_mbs" : "mailboxes";
80680
80720
  let listCommand;
80681
80721
  if (mailbox) {
80682
80722
  const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
@@ -80691,7 +80731,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80691
80731
  set msgCount to 0
80692
80732
  set skipped to 0
80693
80733
  set seenIds to {}
80694
- repeat with mb in mailboxes
80734
+ repeat with mb in ${mbIter}
80695
80735
  if msgCount >= ${limit} then exit repeat
80696
80736
  set mbName to ""
80697
80737
  try
@@ -80738,7 +80778,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80738
80778
  set _skipped to ""
80739
80779
  set _notSearched to ""
80740
80780
  set _startedAt to current date
80741
- repeat with mb in mailboxes
80781
+ repeat with mb in ${mbIter}
80742
80782
  if msgCount >= ${limit} then exit repeat
80743
80783
  set mbName to ""
80744
80784
  try
@@ -80768,7 +80808,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
80768
80808
  return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "${DIAG_FIELD_SEP}skipped=" & _skipped & "${DIAG_FIELD_SEP}notSearched=" & _notSearched
80769
80809
  `;
80770
80810
  }
80771
- const script = buildAccountScopedScript(targetAccount, listCommand);
80811
+ const script = local ? buildAppLevelScript(`${localMailboxBindingFragment()}${listCommand}`) : buildAccountScopedScript(targetAccount, listCommand);
80772
80812
  const result = executeAppleScript(script, { timeoutMs: SEARCH_ACCOUNT_TIMEOUT_MS });
80773
80813
  if (!result.success) {
80774
80814
  console.error(`Failed to list messages in "${targetAccount}": ${result.error}`);
@@ -82736,16 +82776,14 @@ end tell`;
82736
82776
  * Used internally by the cache; prefer getCachedMailboxNames().
82737
82777
  */
82738
82778
  fetchMailboxNames(account) {
82739
- const script = buildAccountScopedScript(
82740
- account,
82741
- `
82779
+ const body = `
82742
82780
  set mbNames to {}
82743
- repeat with mb in mailboxes
82781
+ repeat with mb in ${isLocalStoreLabel(account) ? "_mbs" : "mailboxes"}
82744
82782
  set end of mbNames to name of mb
82745
82783
  end repeat
82746
82784
  return mbNames
82747
- `
82748
- );
82785
+ `;
82786
+ const script = isLocalStoreLabel(account) ? buildAppLevelScript(`${localMailboxBindingFragment()}${body}`) : buildAccountScopedScript(account, body);
82749
82787
  const result = executeAppleScript(script);
82750
82788
  if (!result.success || !result.output) {
82751
82789
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.15.1",
3
+ "version": "2.16.0",
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",