apple-mail-mcp 1.6.2 → 1.6.4

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/build/index.js CHANGED
@@ -85,6 +85,28 @@ function errorResponse(message) {
85
85
  isError: true,
86
86
  };
87
87
  }
88
+ /**
89
+ * Render a partial-coverage warning from search/list diagnostics, so a caller
90
+ * never mistakes an incomplete scan for a confirmed "no matches" (#24/#29).
91
+ * Returns "" when coverage was complete.
92
+ */
93
+ function partialCoverageBlock(diagnostics) {
94
+ const notes = [];
95
+ if (diagnostics.timedOutAccounts.length > 0) {
96
+ notes.push(`timed out (no results) for account(s): ${diagnostics.timedOutAccounts.join(", ")}`);
97
+ }
98
+ if (diagnostics.skippedLargeMailboxes.length > 0) {
99
+ notes.push(`skipped mailbox(es) too large to scan via AppleScript: ${diagnostics.skippedLargeMailboxes.join(", ")} — scope with \`mailbox\` (+ a \`dateFrom\`/\`dateTo\` window for search) to reach them`);
100
+ }
101
+ if (diagnostics.notSearchedMailboxes.length > 0) {
102
+ notes.push(`could not finish scanning mailbox(es): ${diagnostics.notSearchedMailboxes.join(", ")}`);
103
+ }
104
+ if (notes.length === 0)
105
+ return "";
106
+ return `\n\n⚠️ Partial results — this is NOT a confirmed "no such mail":\n${notes
107
+ .map((n) => ` - ${n}`)
108
+ .join("\n")}`;
109
+ }
88
110
  /**
89
111
  * Serial execution gate for AppleScript-backed tool calls (issue #11).
90
112
  *
@@ -137,24 +159,7 @@ server.tool("search-messages", {
137
159
  limit: z.number().optional().describe("Maximum number of results (default: 50)"),
138
160
  }, withErrorHandling(({ query, mailbox, account, limit = 50, dateFrom, dateTo, from, subject, isRead, isFlagged, }) => {
139
161
  const { messages, diagnostics } = mailManager.searchMessagesWithDiagnostics(query, mailbox, account, limit, dateFrom, dateTo, from, subject, isRead, isFlagged);
140
- // Build a coverage note when the search couldn't reach everything, so a
141
- // caller never mistakes an incomplete search for a confirmed "no matches"
142
- // (issue #24).
143
- const coverageNotes = [];
144
- if (diagnostics.timedOutAccounts.length > 0) {
145
- coverageNotes.push(`timed out (no results) for account(s): ${diagnostics.timedOutAccounts.join(", ")}`);
146
- }
147
- if (diagnostics.skippedLargeMailboxes.length > 0) {
148
- coverageNotes.push(`skipped mailbox(es) too large to search via AppleScript: ${diagnostics.skippedLargeMailboxes.join(", ")} — scope the search with \`mailbox\` + a \`dateFrom\`/\`dateTo\` window to target them`);
149
- }
150
- if (diagnostics.notSearchedMailboxes.length > 0) {
151
- coverageNotes.push(`could not finish searching mailbox(es): ${diagnostics.notSearchedMailboxes.join(", ")}`);
152
- }
153
- const coverageBlock = coverageNotes.length > 0
154
- ? `\n\n⚠️ Partial results — this is NOT a confirmed "no such mail":\n${coverageNotes
155
- .map((n) => ` - ${n}`)
156
- .join("\n")}`
157
- : "";
162
+ const coverageBlock = partialCoverageBlock(diagnostics);
158
163
  if (messages.length === 0) {
159
164
  const base = diagnostics.partial
160
165
  ? "No messages found in the portions that were searched."
@@ -192,14 +197,18 @@ server.tool("list-messages", {
192
197
  from: z.string().optional().describe("Filter by sender email address or name"),
193
198
  unreadOnly: z.boolean().optional().describe("Only show unread messages"),
194
199
  }, withErrorHandling(({ mailbox, account, limit = 50, offset = 0, from }) => {
195
- const messages = mailManager.listMessages(mailbox, account, limit, from, offset);
200
+ const { messages, diagnostics } = mailManager.listMessagesWithDiagnostics(mailbox, account, limit, from, offset);
201
+ const coverageBlock = partialCoverageBlock(diagnostics);
196
202
  if (messages.length === 0) {
197
- return successResponse("No messages found");
203
+ const base = diagnostics.partial
204
+ ? "No messages found in the portions that were listed."
205
+ : "No messages found";
206
+ return successResponse(`${base}${coverageBlock}`);
198
207
  }
199
208
  const messageList = messages
200
209
  .map((m) => ` - ID: ${m.id} | ${m.dateReceived.toLocaleDateString()} | ${m.subject} (from: ${m.sender})`)
201
210
  .join("\n");
202
- return successResponse(`Found ${messages.length} message(s):\n${messageList}`);
211
+ return successResponse(`Found ${messages.length} message(s):\n${messageList}${coverageBlock}`);
203
212
  }, "Error listing messages"));
204
213
  // --- send-email ---
205
214
  server.tool("send-email", {
@@ -203,6 +203,19 @@ export declare class AppleMailManager {
203
203
  * @returns Array of messages
204
204
  */
205
205
  listMessages(mailbox?: string, account?: string, limit?: number, from?: string, offset?: number): Message[];
206
+ /**
207
+ * List messages, returning matches plus coverage diagnostics.
208
+ *
209
+ * Like `searchMessages`, the unscoped (all-mailboxes) path used to iterate
210
+ * `messages of mb` over every mailbox with a swallowing per-mailbox `try`,
211
+ * so a large IMAP/Gmail mailbox timed out and the method returned `[]` — a
212
+ * false "No messages found." This applies the same #24 discipline: skip
213
+ * mailboxes above the scan threshold (reported), enforce a per-account
214
+ * wall-clock budget, capture per-mailbox timeouts, and surface all of it as a
215
+ * partial result. (by-id lookups don't need this — `whose id is` is indexed
216
+ * and returns instantly even on a 44k-message mailbox.)
217
+ */
218
+ listMessagesWithDiagnostics(mailbox?: string, account?: string, limit?: number, from?: string, offset?: number): SearchResult;
206
219
  /**
207
220
  * Parse message list output from AppleScript.
208
221
  *
@@ -1 +1 @@
1
- {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"appleMailManager.d.ts","sourceRoot":"","sources":["../../src/services/appleMailManager.ts"],"names":[],"mappings":"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"}
@@ -790,107 +790,172 @@ export class AppleMailManager {
790
790
  * @returns Array of messages
791
791
  */
792
792
  listMessages(mailbox, account, limit = 50, from, offset = 0) {
793
- // If no account specified, list across all accounts
793
+ return this.listMessagesWithDiagnostics(mailbox, account, limit, from, offset).messages;
794
+ }
795
+ /**
796
+ * List messages, returning matches plus coverage diagnostics.
797
+ *
798
+ * Like `searchMessages`, the unscoped (all-mailboxes) path used to iterate
799
+ * `messages of mb` over every mailbox with a swallowing per-mailbox `try`,
800
+ * so a large IMAP/Gmail mailbox timed out and the method returned `[]` — a
801
+ * false "No messages found." This applies the same #24 discipline: skip
802
+ * mailboxes above the scan threshold (reported), enforce a per-account
803
+ * wall-clock budget, capture per-mailbox timeouts, and surface all of it as a
804
+ * partial result. (by-id lookups don't need this — `whose id is` is indexed
805
+ * and returns instantly even on a 44k-message mailbox.)
806
+ */
807
+ listMessagesWithDiagnostics(mailbox, account, limit = 50, from, offset = 0) {
808
+ // If no account specified, list across all accounts and merge diagnostics.
794
809
  if (!account) {
795
810
  const accounts = this.listAccounts();
796
811
  const allMessages = [];
812
+ const diagnostics = {
813
+ partial: false,
814
+ timedOutAccounts: [],
815
+ skippedLargeMailboxes: [],
816
+ notSearchedMailboxes: [],
817
+ };
797
818
  for (const acct of accounts) {
798
819
  if (allMessages.length >= limit)
799
820
  break;
800
821
  const remaining = limit - allMessages.length;
801
- const msgs = this.listMessages(mailbox, acct.name, remaining, from, offset);
802
- allMessages.push(...msgs);
822
+ const res = this.listMessagesWithDiagnostics(mailbox, acct.name, remaining, from, offset);
823
+ allMessages.push(...res.messages);
824
+ mergeSearchDiagnostics(diagnostics, res.diagnostics);
803
825
  }
804
- return allMessages.slice(0, limit);
826
+ return { messages: allMessages.slice(0, limit), diagnostics };
805
827
  }
806
828
  const targetAccount = this.resolveAccount(account);
807
829
  const safeFrom = from ? escapeForAppleScript(from) : "";
808
830
  const fromFilter = from ? `whose sender contains "${safeFrom}"` : "";
831
+ const scanThreshold = getMailboxScanThreshold();
809
832
  let listCommand;
810
833
  if (mailbox) {
811
- // List from a specific mailbox
834
+ // List from a specific mailbox. Caller-scoped, so no count-guard skip, but
835
+ // wrap the scan so a timeout is reported as partial, not a false empty.
812
836
  const targetMailbox = this.resolveMailbox(mailbox, targetAccount);
813
837
  listCommand = `
814
838
  set outputText to ""
839
+ set _timedOut to false
840
+ set _notSearched to ""
815
841
  set theMailbox to mailbox "${escapeForAppleScript(targetMailbox)}"
816
842
  set msgCount to 0
817
843
  set skipped to 0
818
- repeat with msg in messages of theMailbox ${fromFilter}
819
- if msgCount >= ${limit} then exit repeat
820
- try
821
- if skipped < ${offset} then
822
- set skipped to skipped + 1
823
- else
824
- set msgId to id of msg as string
825
- set msgSubject to subject of msg
826
- set msgSender to sender of msg
827
- set d to date received of msg
828
- set msgDate to ${AS_DATE_TO_STRING}
829
- set msgRead to read status of msg as string
830
- set msgFlagged to flagged status of msg as string
831
- set msgHasAtt to "false"
832
- try
833
- if (count of mail attachments of msg) > 0 then set msgHasAtt to "true"
834
- end try
835
- if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
836
- set outputText to outputText & msgId & "|||" & msgSubject & "|||" & msgSender & "|||" & msgDate & "|||" & msgRead & "|||" & msgFlagged & "|||" & msgHasAtt
837
- set msgCount to msgCount + 1
838
- end if
839
- end try
840
- end repeat
841
- return outputText
844
+ try
845
+ repeat with msg in messages of theMailbox ${fromFilter}
846
+ if msgCount >= ${limit} then exit repeat
847
+ try
848
+ if skipped < ${offset} then
849
+ set skipped to skipped + 1
850
+ else
851
+ set msgId to id of msg as string
852
+ set msgSubject to subject of msg
853
+ set msgSender to sender of msg
854
+ set d to date received of msg
855
+ set msgDate to ${AS_DATE_TO_STRING}
856
+ set msgRead to read status of msg as string
857
+ set msgFlagged to flagged status of msg as string
858
+ set msgHasAtt to "false"
859
+ try
860
+ if (count of mail attachments of msg) > 0 then set msgHasAtt to "true"
861
+ end try
862
+ if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
863
+ set outputText to outputText & msgId & "|||" & msgSubject & "|||" & msgSender & "|||" & msgDate & "|||" & msgRead & "|||" & msgFlagged & "|||" & msgHasAtt
864
+ set msgCount to msgCount + 1
865
+ end if
866
+ end try
867
+ end repeat
868
+ on error _errMsg number _errNum
869
+ set _timedOut to true
870
+ set _notSearched to "${escapeForAppleScript(targetMailbox)}|||M|||"
871
+ end try
872
+ return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "|||F|||skipped=|||F|||notSearched=" & _notSearched
842
873
  `;
843
874
  }
844
875
  else {
845
- // List from ALL mailboxes — iterate every mailbox in the account, dedup by message ID
876
+ // List from ALL mailboxes — skip mailboxes over the scan threshold, enforce
877
+ // the per-account budget, capture per-mailbox timeouts; dedup by message ID.
878
+ const scanGuard = scanThreshold > 0 ? `mbCount > ${scanThreshold}` : "false";
846
879
  listCommand = `
847
880
  set outputText to ""
848
881
  set msgCount to 0
849
882
  set skipped to 0
850
883
  set seenIds to {}
884
+ set _timedOut to false
885
+ set _skipped to ""
886
+ set _notSearched to ""
887
+ set _startedAt to current date
851
888
  repeat with mb in mailboxes
852
889
  if msgCount >= ${limit} then exit repeat
890
+ set mbName to ""
853
891
  try
854
- repeat with msg in messages of mb ${fromFilter}
855
- if msgCount >= ${limit} then exit repeat
892
+ set mbName to name of mb
893
+ end try
894
+ if ((current date) - _startedAt) > ${SEARCH_ACCOUNT_BUDGET_SECONDS} then
895
+ set _timedOut to true
896
+ set _notSearched to _notSearched & mbName & "|||M|||"
897
+ else
898
+ set mbCount to 0
899
+ try
900
+ set mbCount to count of messages of mb
901
+ end try
902
+ if (${scanGuard}) then
903
+ set _timedOut to true
904
+ set _skipped to _skipped & mbName & " (" & (mbCount as string) & ")|||M|||"
905
+ else
856
906
  try
857
- set msgId to id of msg as string
858
- if seenIds does not contain msgId then
859
- set end of seenIds to msgId
860
- if skipped < ${offset} then
861
- set skipped to skipped + 1
862
- else
863
- set msgSubject to subject of msg
864
- set msgSender to sender of msg
865
- set d to date received of msg
866
- set msgDate to ${AS_DATE_TO_STRING}
867
- set msgRead to read status of msg as string
868
- set msgFlagged to flagged status of msg as string
869
- set msgHasAtt to "false"
870
- try
871
- if (count of mail attachments of msg) > 0 then set msgHasAtt to "true"
872
- end try
873
- if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
874
- set outputText to outputText & msgId & "|||" & msgSubject & "|||" & msgSender & "|||" & msgDate & "|||" & msgRead & "|||" & msgFlagged & "|||" & name of mb & "|||" & msgHasAtt
875
- set msgCount to msgCount + 1
876
- end if
877
- end if
907
+ repeat with msg in messages of mb ${fromFilter}
908
+ if msgCount >= ${limit} then exit repeat
909
+ try
910
+ set msgId to id of msg as string
911
+ if seenIds does not contain msgId then
912
+ set end of seenIds to msgId
913
+ if skipped < ${offset} then
914
+ set skipped to skipped + 1
915
+ else
916
+ set msgSubject to subject of msg
917
+ set msgSender to sender of msg
918
+ set d to date received of msg
919
+ set msgDate to ${AS_DATE_TO_STRING}
920
+ set msgRead to read status of msg as string
921
+ set msgFlagged to flagged status of msg as string
922
+ set msgHasAtt to "false"
923
+ try
924
+ if (count of mail attachments of msg) > 0 then set msgHasAtt to "true"
925
+ end try
926
+ if msgCount > 0 then set outputText to outputText & "|||ITEM|||"
927
+ set outputText to outputText & msgId & "|||" & msgSubject & "|||" & msgSender & "|||" & msgDate & "|||" & msgRead & "|||" & msgFlagged & "|||" & mbName & "|||" & msgHasAtt
928
+ set msgCount to msgCount + 1
929
+ end if
930
+ end if
931
+ end try
932
+ end repeat
933
+ on error _errMsg number _errNum
934
+ set _timedOut to true
935
+ set _notSearched to _notSearched & mbName & "|||M|||"
878
936
  end try
879
- end repeat
880
- end try
937
+ end if
938
+ end if
881
939
  end repeat
882
- return outputText
940
+ return outputText & "${DIAG_MARKER}timedOut=" & (_timedOut as string) & "|||F|||skipped=" & _skipped & "|||F|||notSearched=" & _notSearched
883
941
  `;
884
942
  }
885
943
  const script = buildAccountScopedScript(targetAccount, listCommand);
886
- const result = executeAppleScript(script, { timeoutMs: 60000 });
944
+ const result = executeAppleScript(script, { timeoutMs: SEARCH_ACCOUNT_TIMEOUT_MS });
887
945
  if (!result.success) {
888
- console.error(`Failed to list messages: ${result.error}`);
889
- return [];
946
+ // Whole-account failure surface as a timeout, not a false empty (#24/#29).
947
+ console.error(`Failed to list messages in "${targetAccount}": ${result.error}`);
948
+ return {
949
+ messages: [],
950
+ diagnostics: {
951
+ partial: true,
952
+ timedOutAccounts: [targetAccount],
953
+ skippedLargeMailboxes: [],
954
+ notSearchedMailboxes: [],
955
+ },
956
+ };
890
957
  }
891
- if (!result.output.trim())
892
- return [];
893
- return this.parseMessageList(result.output, mailbox || "INBOX", targetAccount);
958
+ return this.parseSearchResult(result.output, mailbox || "INBOX", targetAccount);
894
959
  }
895
960
  /**
896
961
  * Parse message list output from AppleScript.
@@ -2129,32 +2194,20 @@ export class AppleMailManager {
2129
2194
  const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
2130
2195
  const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
2131
2196
  const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
2132
- // Format dates for AppleScript comparison
2133
- const formatDate = (d) => {
2134
- const months = [
2135
- "January",
2136
- "February",
2137
- "March",
2138
- "April",
2139
- "May",
2140
- "June",
2141
- "July",
2142
- "August",
2143
- "September",
2144
- "October",
2145
- "November",
2146
- "December",
2147
- ];
2148
- return `date "${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}"`;
2149
- };
2197
+ // Thresholds are built from numeric components via buildAppleScriptDate
2198
+ // rather than `date "January 5, 2026"` string coercion. The English-month
2199
+ // literal throws "Invalid date and time (-30720)" on non-English system
2200
+ // locales; that throw was swallowed by the per-inbox `try` below, so this
2201
+ // method silently returned 0/0/0 on those Macs — the same locale regression
2202
+ // fixed for searchMessages in #15 / surfaced again in #28.
2150
2203
  // Only scan INBOX for performance - scanning all mailboxes is too slow
2151
2204
  const script = buildAppLevelScript(`
2152
2205
  set last24h to 0
2153
2206
  set last7d to 0
2154
2207
  set last30d to 0
2155
- set oneDayAgo to ${formatDate(oneDayAgo)}
2156
- set sevenDaysAgo to ${formatDate(sevenDaysAgo)}
2157
- set thirtyDaysAgo to ${formatDate(thirtyDaysAgo)}
2208
+ ${buildAppleScriptDate("oneDayAgo", oneDayAgo)}
2209
+ ${buildAppleScriptDate("sevenDaysAgo", sevenDaysAgo)}
2210
+ ${buildAppleScriptDate("thirtyDaysAgo", thirtyDaysAgo)}
2158
2211
 
2159
2212
  repeat with acct in accounts
2160
2213
  try
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "MCP server for Apple Mail - read, search, send, and manage emails via Claude",
5
5
  "type": "module",
6
6
  "main": "build/index.js",