apple-mail-mcp 2.20.20 → 2.20.22

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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/build/index.js +79 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1075,6 +1075,12 @@ List attachments on a message.
1075
1075
  **Returns:** List of attachments with name, MIME type, and `size` — the **decoded** byte count,
1076
1076
  i.e. what `fetch-attachment` returns.
1077
1077
 
1078
+ **A numeric `id` resolves the same way the read tools resolve one** (2.20.21): the mailbox the id
1079
+ was listed from when that is known, else a bounded cross-mailbox scan that skips very large
1080
+ mailboxes and stops at its time budget. An id that several mailboxes hold is **refused**, with the
1081
+ candidates named, rather than answered for whichever copy came first — so list or search the
1082
+ mailbox you mean immediately before calling this. `imap:…` ids are never ambiguous.
1083
+
1078
1084
  **Sizes over IMAP.** `BODYSTRUCTURE` reports a part's size in its transfer encoding, so a
1079
1085
  base64 attachment's raw figure is ~4/3 of the file. `list-attachments` converts it: a
1080
1086
  `Content-Disposition` `size=` parameter wins when present; 7bit/8bit/binary parts are exact
@@ -1096,6 +1102,11 @@ of overwriting an existing file. AppleScript and MIME fallback paths stage the
1096
1102
  bytes privately, commit with an exclusive create, and leave the saved file
1097
1103
  owner-readable/writable (`0600`).
1098
1104
 
1105
+ **A numeric `id` resolves the same way the read tools resolve one** (2.20.21): the mailbox the id
1106
+ was listed from when that is known, else the bounded cross-mailbox scan. An id that several
1107
+ mailboxes hold **saves nothing** rather than writing some other message's attachment under the
1108
+ name you asked for.
1109
+
1099
1110
  | Parameter | Type | Required | Description |
1100
1111
  | ---------------- | ------ | -------- | -------------------------- |
1101
1112
  | `id` | string | Yes | Message ID |
package/build/index.js CHANGED
@@ -83314,6 +83314,7 @@ var DATES_MARKER = "DATES";
83314
83314
  var HTML_MARKER = "HTML";
83315
83315
  var LOOKUP_ERROR_MARKER = "ERR";
83316
83316
  var BATCH_FATAL = "FATAL";
83317
+ var ATTACHMENTS_MARKER = "ATT";
83317
83318
  var RECON_TAG = "RECON";
83318
83319
  var SNAP_TAG = "SNAP";
83319
83320
  var SNAP_PAIR = "P";
@@ -83780,19 +83781,32 @@ function mailboxPathFragment(mailboxVar, outputVar) {
83780
83781
  set _pathParent to _pathNext
83781
83782
  end repeat`;
83782
83783
  }
83783
- function mailboxLookupFragment(collExpr, path3, outputVar) {
83784
- return `
83785
- set ${outputVar} to missing value
83784
+ function mailboxLookupFragment(collExpr, path3, outputVar, hintIndex) {
83785
+ const safePath = escapeForAppleScript(path3);
83786
+ const hint = hintIndex === void 0 ? "" : `
83787
+ try
83788
+ set _mbc to item ${hintIndex} of (${collExpr})
83789
+ set _mbcPath to ""
83790
+ ${mailboxPathFragment("_mbc", "_mbcPath")}
83791
+ ignoring case
83792
+ if _mbcPath is "${safePath}" then set ${outputVar} to _mbc
83793
+ end ignoring
83794
+ end try`;
83795
+ const walk = `
83786
83796
  repeat with _mbc in (${collExpr})
83787
83797
  set _mbcPath to ""
83788
83798
  ${mailboxPathFragment("_mbc", "_mbcPath")}
83789
83799
  ignoring case
83790
- if _mbcPath is "${escapeForAppleScript(path3)}" then
83800
+ if _mbcPath is "${safePath}" then
83791
83801
  set ${outputVar} to _mbc
83792
83802
  exit repeat
83793
83803
  end if
83794
83804
  end ignoring
83795
83805
  end repeat`;
83806
+ return `
83807
+ set ${outputVar} to missing value${hint}${hintIndex === void 0 ? walk : `
83808
+ if ${outputVar} is missing value then${walk}
83809
+ end if`}`;
83796
83810
  }
83797
83811
  var MAILBOX_ALIASES = {
83798
83812
  inbox: ["INBOX", "Inbox", "inbox"],
@@ -84788,6 +84802,17 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
84788
84802
  resolveMailbox(mailbox, account) {
84789
84803
  return resolveAppleMailboxPath(mailbox, this.getCachedMailboxNames(account));
84790
84804
  }
84805
+ /**
84806
+ * The 1-based position of `path` in the account's cached mailbox list, which is
84807
+ * built by walking the account's `mailboxes` in order. `mailboxLookupFragment`
84808
+ * checks that one mailbox first instead of working out the path of every one.
84809
+ * Undefined when the path is not in the list; a stale position is harmless, the
84810
+ * fragment verifies the path and falls back to the full walk.
84811
+ */
84812
+ mailboxIndexHint(account, path3) {
84813
+ const index = this.getCachedMailboxNames(account).indexOf(path3);
84814
+ return index >= 0 ? index + 1 : void 0;
84815
+ }
84791
84816
  /**
84792
84817
  * Non-throwing `resolveMailbox`, for callers comparing mailbox names AFTER
84793
84818
  * a destructive op already ran (the forensics `sameMailbox` closures). An
@@ -84954,7 +84979,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
84954
84979
  ${dateSetup}set outputText to ""
84955
84980
  set _timedOut to false
84956
84981
  set _notSearched to ""
84957
- ${mailboxLookupFragment(mbIter, targetMailbox, "theMailbox")}
84982
+ ${mailboxLookupFragment(mbIter, targetMailbox, "theMailbox", this.mailboxIndexHint(targetAccount, targetMailbox))}
84958
84983
  set msgCount to 0
84959
84984
  if theMailbox is missing value then
84960
84985
  set _timedOut to true
@@ -85686,7 +85711,7 @@ ${indent}end try${this.sanitizeFragment("_uacct", indent)}${this.sanitizeFragmen
85686
85711
  set outputText to ""
85687
85712
  set _timedOut to false
85688
85713
  set _notSearched to ""
85689
- ${mailboxLookupFragment(mbIter, targetMailbox, "theMailbox")}
85714
+ ${mailboxLookupFragment(mbIter, targetMailbox, "theMailbox", this.mailboxIndexHint(targetAccount, targetMailbox))}
85690
85715
  set msgCount to 0
85691
85716
  set skipped to 0
85692
85717
  if theMailbox is missing value then
@@ -86903,14 +86928,7 @@ ${this.errorEmit(" ")}
86903
86928
  * when AppleScript returns empty (known issue across all account types).
86904
86929
  */
86905
86930
  listAttachments(id) {
86906
- const script = buildAppLevelScript(`
86907
- try
86908
- repeat with acct in accounts
86909
- repeat with mb in mailboxes of acct
86910
- try
86911
- set matchingMsgs to (messages of mb whose id is ${Number(id)})
86912
- if (count of matchingMsgs) > 0 then
86913
- set msg to item 1 of matchingMsgs
86931
+ const attachmentLoop = `
86914
86932
  set outputText to ""
86915
86933
  set attCount to 0
86916
86934
  -- Each property is read inside its own try: \`MIME type of att\` throws
@@ -86936,20 +86954,20 @@ ${this.errorEmit(" ")}
86936
86954
  set outputText to outputText & attName & "${FIELD_SEP}" & attType & "${FIELD_SEP}" & attSize
86937
86955
  set attCount to attCount + 1
86938
86956
  end if
86939
- end repeat
86940
- return outputText
86941
- end if
86942
- end try
86943
- end repeat
86944
- end repeat
86945
- return ""
86946
- on error errMsg
86947
- return ""
86948
- end try
86949
- `);
86950
- const result = executeAppleScript(script, { timeoutMs: 6e4 });
86951
- if (result.success && result.output.trim()) {
86952
- const items = result.output.split(RECORD_SEP);
86957
+ end repeat`;
86958
+ this.lastMessageLookupError = void 0;
86959
+ const output = this.resolveById(
86960
+ id,
86961
+ `${attachmentLoop}
86962
+ return "${ATTACHMENTS_MARKER}" & outputText`,
86963
+ void 0
86964
+ );
86965
+ if (output === null) {
86966
+ const refusal = this.consumeLastMessageLookupError();
86967
+ if (refusal) throw new Error(refusal);
86968
+ }
86969
+ if (output?.startsWith(ATTACHMENTS_MARKER) && output.length > ATTACHMENTS_MARKER.length) {
86970
+ const items = output.slice(ATTACHMENTS_MARKER.length).split(RECORD_SEP);
86953
86971
  const attachments = [];
86954
86972
  for (const item of items) {
86955
86973
  const parts = item.split(FIELD_SEP);
@@ -87007,14 +87025,28 @@ ${this.errorEmit(" ")}
87007
87025
  }
87008
87026
  };
87009
87027
  const numericId = Number(id);
87010
- const script = buildAppLevelScript(`
87011
- try
87012
- repeat with acct in accounts
87013
- repeat with mb in mailboxes of acct
87014
- try
87015
- set matchingMsgs to (messages of mb whose id is ${numericId})
87016
- if (count of matchingMsgs) > 0 then
87017
- set msg to item 1 of matchingMsgs
87028
+ this.lastMessageLookupError = void 0;
87029
+ const located = this.resolveById(
87030
+ id,
87031
+ `set _where to ""
87032
+ try
87033
+ ${mailboxPathFragment("_hitMb", "_where")}
87034
+ end try
87035
+ return _hitAcct & "${FIELD_SEP}" & _where`,
87036
+ void 0
87037
+ );
87038
+ if (located === null) {
87039
+ const why = this.consumeLastMessageLookupError();
87040
+ if (why) {
87041
+ cleanupTemporaryDirectory();
87042
+ console.error(`Failed to save attachment: ${why}`);
87043
+ return false;
87044
+ }
87045
+ }
87046
+ const [holderAccount, holderMailbox] = (located ?? "").split(FIELD_SEP);
87047
+ if (holderAccount && holderMailbox)
87048
+ this.rememberLocation(String(id), holderAccount, holderMailbox);
87049
+ const saveAction = `
87018
87050
  repeat with att in mail attachments of msg
87019
87051
  if name of att is "${safeName}" then
87020
87052
  set savePath to POSIX file "${safeTemporaryPath}"
@@ -87022,7 +87054,16 @@ ${this.errorEmit(" ")}
87022
87054
  return "ok"
87023
87055
  end if
87024
87056
  end repeat
87025
- return "error:Attachment not found"
87057
+ return "error:Attachment not found"`;
87058
+ const script = holderAccount && holderMailbox ? this.scopedByIdScript(holderAccount, holderMailbox, id, saveAction) : buildAppLevelScript(`
87059
+ try
87060
+ repeat with acct in accounts
87061
+ repeat with mb in mailboxes of acct
87062
+ try
87063
+ set matchingMsgs to (messages of mb whose id is ${numericId})
87064
+ if (count of matchingMsgs) > 0 then
87065
+ set msg to item 1 of matchingMsgs
87066
+ ${saveAction}
87026
87067
  end if
87027
87068
  end try
87028
87069
  end repeat
@@ -87166,7 +87207,7 @@ ${this.errorEmit(" ")}
87166
87207
  const targetAccount = this.resolveAccount(account);
87167
87208
  const targetMailbox = this.resolveMailbox(mailbox || "INBOX", targetAccount);
87168
87209
  const command = `
87169
- ${mailboxLookupFragment("mailboxes", targetMailbox, "theMailbox")}
87210
+ ${mailboxLookupFragment("mailboxes", targetMailbox, "theMailbox", this.mailboxIndexHint(targetAccount, targetMailbox))}
87170
87211
  if theMailbox is missing value then error "Mailbox \\"${escapeForAppleScript(targetMailbox)}\\" not found"
87171
87212
  return unread count of theMailbox`;
87172
87213
  const script = buildAccountScopedScript(targetAccount, command);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.20.20",
3
+ "version": "2.20.22",
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",