apple-mail-mcp 2.20.19 → 2.20.21

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
@@ -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/cli.js CHANGED
@@ -58581,10 +58581,13 @@ async function searchUids(client, path3, criteria, size) {
58581
58581
  async function filterUidsByColor(client, uids, colorIndex) {
58582
58582
  if (uids.length === 0) return uids;
58583
58583
  const keep = /* @__PURE__ */ new Set();
58584
- for await (const msg of client.fetch(uids.join(","), { uid: true, flags: true }, { uid: true })) {
58585
- if (typeof msg?.uid !== "number") continue;
58586
- const idx = mailFlagColorIndex(msg.flags);
58587
- if (colorIndex === 0 ? idx === void 0 : idx === colorIndex) keep.add(msg.uid);
58584
+ for (let i = 0; i < uids.length; i += COLOR_FETCH_CHUNK) {
58585
+ const slice = uids.slice(i, i + COLOR_FETCH_CHUNK).join(",");
58586
+ for await (const msg of client.fetch(slice, { uid: true, flags: true }, { uid: true })) {
58587
+ if (typeof msg?.uid !== "number") continue;
58588
+ const idx = mailFlagColorIndex(msg.flags);
58589
+ if (colorIndex === 0 ? idx === void 0 : idx === colorIndex) keep.add(msg.uid);
58590
+ }
58588
58591
  }
58589
58592
  return uids.filter((uid) => keep.has(uid));
58590
58593
  }
@@ -60075,7 +60078,7 @@ async function imapThread(id, deps = {}, limit = 50) {
60075
60078
  true
60076
60079
  );
60077
60080
  }
60078
- var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, FLAG_COLOR_NAMES, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
60081
+ var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, COLOR_FETCH_CHUNK, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, FLAG_COLOR_NAMES, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
60079
60082
  var init_imapClient = __esm({
60080
60083
  "src/services/imapClient.ts"() {
60081
60084
  "use strict";
@@ -60151,6 +60154,7 @@ var init_imapClient = __esm({
60151
60154
  FIRST_SEARCH_WINDOW = 5e3;
60152
60155
  MAX_WINDOW = 5e4;
60153
60156
  MAX_UNSCOPED_SEARCH_DEPTH = 5e3;
60157
+ COLOR_FETCH_CHUNK = 500;
60154
60158
  ROW_QUERY = {
60155
60159
  envelope: true,
60156
60160
  flags: true,
package/build/index.js CHANGED
@@ -66334,10 +66334,13 @@ async function searchUids(client, path3, criteria, size) {
66334
66334
  async function filterUidsByColor(client, uids, colorIndex) {
66335
66335
  if (uids.length === 0) return uids;
66336
66336
  const keep = /* @__PURE__ */ new Set();
66337
- for await (const msg of client.fetch(uids.join(","), { uid: true, flags: true }, { uid: true })) {
66338
- if (typeof msg?.uid !== "number") continue;
66339
- const idx = mailFlagColorIndex(msg.flags);
66340
- if (colorIndex === 0 ? idx === void 0 : idx === colorIndex) keep.add(msg.uid);
66337
+ for (let i = 0; i < uids.length; i += COLOR_FETCH_CHUNK) {
66338
+ const slice = uids.slice(i, i + COLOR_FETCH_CHUNK).join(",");
66339
+ for await (const msg of client.fetch(slice, { uid: true, flags: true }, { uid: true })) {
66340
+ if (typeof msg?.uid !== "number") continue;
66341
+ const idx = mailFlagColorIndex(msg.flags);
66342
+ if (colorIndex === 0 ? idx === void 0 : idx === colorIndex) keep.add(msg.uid);
66343
+ }
66341
66344
  }
66342
66345
  return uids.filter((uid) => keep.has(uid));
66343
66346
  }
@@ -67828,7 +67831,7 @@ async function imapThread(id, deps = {}, limit = 50) {
67828
67831
  true
67829
67832
  );
67830
67833
  }
67831
- var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, FLAG_COLOR_NAMES, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
67834
+ var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, MAX_UNSCOPED_SEARCH_DEPTH, COLOR_FETCH_CHUNK, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, DELETED_MARGIN, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, FLAG_COLOR_NAMES, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, BASE64_TAIL_WINDOW, QP_EXACT_MAX_BYTES, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
67832
67835
  var init_imapClient = __esm({
67833
67836
  "src/services/imapClient.ts"() {
67834
67837
  "use strict";
@@ -67904,6 +67907,7 @@ var init_imapClient = __esm({
67904
67907
  FIRST_SEARCH_WINDOW = 5e3;
67905
67908
  MAX_WINDOW = 5e4;
67906
67909
  MAX_UNSCOPED_SEARCH_DEPTH = 5e3;
67910
+ COLOR_FETCH_CHUNK = 500;
67907
67911
  ROW_QUERY = {
67908
67912
  envelope: true,
67909
67913
  flags: true,
@@ -83310,6 +83314,7 @@ var DATES_MARKER = "DATES";
83310
83314
  var HTML_MARKER = "HTML";
83311
83315
  var LOOKUP_ERROR_MARKER = "ERR";
83312
83316
  var BATCH_FATAL = "FATAL";
83317
+ var ATTACHMENTS_MARKER = "ATT";
83313
83318
  var RECON_TAG = "RECON";
83314
83319
  var SNAP_TAG = "SNAP";
83315
83320
  var SNAP_PAIR = "P";
@@ -83560,10 +83565,17 @@ function buildMessageRowLoop(opts) {
83560
83565
  set _dates to date received of _msgs
83561
83566
  set _reads to read status of _msgs
83562
83567
  set _flags to flagged status of _msgs
83563
- set _flagColors to flag index of _msgs
83564
83568
  on error
83565
83569
  set _bulkOK to false
83566
83570
  end try
83571
+ -- Its own try, like the sent dates: a getter that fails on one account type must
83572
+ -- not send every other property down the slow per-message path.
83573
+ set _flagBulkOK to true
83574
+ try
83575
+ set _flagColors to flag index of _msgs
83576
+ on error
83577
+ set _flagBulkOK to false
83578
+ end try
83567
83579
  set _sentBulkOK to true
83568
83580
  try
83569
83581
  set _sentDates to date sent of _msgs
@@ -83591,15 +83603,21 @@ function buildMessageRowLoop(opts) {
83591
83603
  set msgSender to item _i of _sndrs
83592
83604
  set msgRead to (item _i of _reads) as string
83593
83605
  set msgFlagged to (item _i of _flags) as string
83594
- set msgFlagColorRaw to (item _i of _flagColors) as string
83595
83606
  else
83596
83607
  set _m to item _i of _msgs
83597
83608
  set msgSubject to subject of _m
83598
83609
  set msgSender to sender of _m
83599
83610
  set msgRead to read status of _m as string
83600
83611
  set msgFlagged to flagged status of _m as string
83601
- set msgFlagColorRaw to flag index of _m as string
83602
- end if${attRow}
83612
+ end if
83613
+ set msgFlagColorRaw to "-1"
83614
+ try
83615
+ if _bulkOK and _flagBulkOK then
83616
+ set msgFlagColorRaw to (item _i of _flagColors) as string
83617
+ else
83618
+ set msgFlagColorRaw to (flag index of (item _i of _msgs)) as string
83619
+ end if
83620
+ end try${attRow}
83603
83621
  set msgDateStr to ${AS_DATE_TO_STRING}
83604
83622
  set msgDateSentStr to ""
83605
83623
  try
@@ -86886,14 +86904,7 @@ ${this.errorEmit(" ")}
86886
86904
  * when AppleScript returns empty (known issue across all account types).
86887
86905
  */
86888
86906
  listAttachments(id) {
86889
- const script = buildAppLevelScript(`
86890
- try
86891
- repeat with acct in accounts
86892
- repeat with mb in mailboxes of acct
86893
- try
86894
- set matchingMsgs to (messages of mb whose id is ${Number(id)})
86895
- if (count of matchingMsgs) > 0 then
86896
- set msg to item 1 of matchingMsgs
86907
+ const attachmentLoop = `
86897
86908
  set outputText to ""
86898
86909
  set attCount to 0
86899
86910
  -- Each property is read inside its own try: \`MIME type of att\` throws
@@ -86919,20 +86930,20 @@ ${this.errorEmit(" ")}
86919
86930
  set outputText to outputText & attName & "${FIELD_SEP}" & attType & "${FIELD_SEP}" & attSize
86920
86931
  set attCount to attCount + 1
86921
86932
  end if
86922
- end repeat
86923
- return outputText
86924
- end if
86925
- end try
86926
- end repeat
86927
- end repeat
86928
- return ""
86929
- on error errMsg
86930
- return ""
86931
- end try
86932
- `);
86933
- const result = executeAppleScript(script, { timeoutMs: 6e4 });
86934
- if (result.success && result.output.trim()) {
86935
- const items = result.output.split(RECORD_SEP);
86933
+ end repeat`;
86934
+ this.lastMessageLookupError = void 0;
86935
+ const output = this.resolveById(
86936
+ id,
86937
+ `${attachmentLoop}
86938
+ return "${ATTACHMENTS_MARKER}" & outputText`,
86939
+ void 0
86940
+ );
86941
+ if (output === null) {
86942
+ const refusal = this.consumeLastMessageLookupError();
86943
+ if (refusal) throw new Error(refusal);
86944
+ }
86945
+ if (output?.startsWith(ATTACHMENTS_MARKER) && output.length > ATTACHMENTS_MARKER.length) {
86946
+ const items = output.slice(ATTACHMENTS_MARKER.length).split(RECORD_SEP);
86936
86947
  const attachments = [];
86937
86948
  for (const item of items) {
86938
86949
  const parts = item.split(FIELD_SEP);
@@ -86990,14 +87001,28 @@ ${this.errorEmit(" ")}
86990
87001
  }
86991
87002
  };
86992
87003
  const numericId = Number(id);
86993
- const script = buildAppLevelScript(`
86994
- try
86995
- repeat with acct in accounts
86996
- repeat with mb in mailboxes of acct
86997
- try
86998
- set matchingMsgs to (messages of mb whose id is ${numericId})
86999
- if (count of matchingMsgs) > 0 then
87000
- set msg to item 1 of matchingMsgs
87004
+ this.lastMessageLookupError = void 0;
87005
+ const located = this.resolveById(
87006
+ id,
87007
+ `set _where to ""
87008
+ try
87009
+ ${mailboxPathFragment("_hitMb", "_where")}
87010
+ end try
87011
+ return _hitAcct & "${FIELD_SEP}" & _where`,
87012
+ void 0
87013
+ );
87014
+ if (located === null) {
87015
+ const why = this.consumeLastMessageLookupError();
87016
+ if (why) {
87017
+ cleanupTemporaryDirectory();
87018
+ console.error(`Failed to save attachment: ${why}`);
87019
+ return false;
87020
+ }
87021
+ }
87022
+ const [holderAccount, holderMailbox] = (located ?? "").split(FIELD_SEP);
87023
+ if (holderAccount && holderMailbox)
87024
+ this.rememberLocation(String(id), holderAccount, holderMailbox);
87025
+ const saveAction = `
87001
87026
  repeat with att in mail attachments of msg
87002
87027
  if name of att is "${safeName}" then
87003
87028
  set savePath to POSIX file "${safeTemporaryPath}"
@@ -87005,7 +87030,16 @@ ${this.errorEmit(" ")}
87005
87030
  return "ok"
87006
87031
  end if
87007
87032
  end repeat
87008
- return "error:Attachment not found"
87033
+ return "error:Attachment not found"`;
87034
+ const script = holderAccount && holderMailbox ? this.scopedByIdScript(holderAccount, holderMailbox, id, saveAction) : buildAppLevelScript(`
87035
+ try
87036
+ repeat with acct in accounts
87037
+ repeat with mb in mailboxes of acct
87038
+ try
87039
+ set matchingMsgs to (messages of mb whose id is ${numericId})
87040
+ if (count of matchingMsgs) > 0 then
87041
+ set msg to item 1 of matchingMsgs
87042
+ ${saveAction}
87009
87043
  end if
87010
87044
  end try
87011
87045
  end repeat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.20.19",
3
+ "version": "2.20.21",
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",