apple-mail-mcp 2.10.3 → 2.10.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.
Files changed (2) hide show
  1. package/build/index.js +75 -9
  2. package/package.json +1 -1
package/build/index.js CHANGED
@@ -76842,6 +76842,12 @@ var AppleMailManager = class {
76842
76842
  };
76843
76843
  /** Cache TTL in milliseconds (60 seconds). */
76844
76844
  CACHE_TTL_MS = 6e4;
76845
+ /**
76846
+ * Last AppleScript transport error from an account/count read, or null when the
76847
+ * last one succeeded. Lets a tool report "the transport failed" instead of
76848
+ * presenting a fallback zero/empty as a real answer. (#130)
76849
+ */
76850
+ lastAccountsError = null;
76845
76851
  /**
76846
76852
  * Remembers where each message id was last seen: id → {account, mailbox}.
76847
76853
  *
@@ -76877,6 +76883,10 @@ var AppleMailManager = class {
76877
76883
  return this.cache.accounts.data;
76878
76884
  }
76879
76885
  const accounts = this.fetchAccounts();
76886
+ if (accounts === null) {
76887
+ return this.cache.accounts?.data ?? [];
76888
+ }
76889
+ this.lastAccountsError = null;
76880
76890
  this.cache.accounts = { data: accounts, expiry: now + this.CACHE_TTL_MS };
76881
76891
  return accounts;
76882
76892
  }
@@ -78683,6 +78693,7 @@ var AppleMailManager = class {
78683
78693
  const result = executeAppleScript(script, { timeoutMs: 6e4 });
78684
78694
  if (!result.success) {
78685
78695
  console.error(`Failed to get unread count: ${result.error}`);
78696
+ this.lastAccountsError = result.error ?? "AppleScript transport failed";
78686
78697
  return 0;
78687
78698
  }
78688
78699
  return parseInt(result.output) || 0;
@@ -79208,9 +79219,38 @@ end tell`;
79208
79219
  listAccounts() {
79209
79220
  return this.getCachedAccounts();
79210
79221
  }
79222
+ /**
79223
+ * listAccounts() plus whether the underlying AppleScript read actually worked.
79224
+ *
79225
+ * `failed: true` means the list is a fallback (stale cache or empty) because the
79226
+ * transport errored — NOT that Mail has no accounts. (#130)
79227
+ */
79228
+ listAccountsChecked() {
79229
+ this.lastAccountsError = null;
79230
+ const accounts = this.getCachedAccounts();
79231
+ const error2 = this.lastAccountsError;
79232
+ return error2 ? { accounts, failed: true, error: error2 } : { accounts, failed: false };
79233
+ }
79234
+ /**
79235
+ * getUnreadCount() plus whether the AppleScript read actually worked.
79236
+ *
79237
+ * On failure the count is `null` rather than 0, so a caller can never mistake a
79238
+ * wedged transport for an empty inbox. (#130)
79239
+ */
79240
+ getUnreadCountChecked(mailbox, account) {
79241
+ this.lastAccountsError = null;
79242
+ const count = this.getUnreadCount(mailbox, account);
79243
+ const error2 = this.lastAccountsError;
79244
+ return error2 ? { count: null, failed: true, error: error2 } : { count, failed: false };
79245
+ }
79211
79246
  /**
79212
79247
  * Fetches account list directly from Mail.app via AppleScript.
79213
79248
  * Used internally by the cache; prefer getCachedAccounts() or listAccounts().
79249
+ *
79250
+ * Returns `null` when the AppleScript transport itself failed (timeout, wedged
79251
+ * Mail, denied Automation). That is deliberately distinct from `[]`, which means
79252
+ * "Mail answered, and there genuinely are no accounts" — collapsing the two is
79253
+ * what let a wedged transport report a confident "No Mail accounts found". (#130)
79214
79254
  */
79215
79255
  fetchAccounts() {
79216
79256
  const script = buildAppLevelScript(`
@@ -79231,7 +79271,8 @@ end tell`;
79231
79271
  const result = executeAppleScript(script);
79232
79272
  if (!result.success) {
79233
79273
  console.error(`Failed to list accounts: ${result.error}`);
79234
- return [];
79274
+ this.lastAccountsError = result.error ?? "AppleScript transport failed";
79275
+ return null;
79235
79276
  }
79236
79277
  if (!result.output.trim()) return [];
79237
79278
  const items = result.output.split(RECORD_SEP);
@@ -82824,7 +82865,7 @@ ${mailboxList}`, structured);
82824
82865
  server.registerTool(
82825
82866
  "get-unread-count",
82826
82867
  {
82827
- description: "Use when: you only need the number of unread messages \u2014 INBOX by default, or scoped to one mailbox and/or account \u2014 without listing the messages themselves.\nReturns: the unread count for the requested scope (INBOX when no mailbox is given).\nDo not use when: you need the actual unread messages and their ids (use list-messages with unreadOnly, or search-messages with isRead=false) or broader totals across every mailbox (use get-mail-stats).",
82868
+ description: "Use when: you only need the number of unread messages \u2014 INBOX by default, or scoped to one mailbox and/or account \u2014 without listing the messages themselves.\nReturns: the unread count for the requested scope (INBOX when no mailbox is given). If a source cannot be read the result carries `partial: true` + `failedAccounts`, and a total AppleScript failure returns an ERROR \u2014 a plain count is never a disguised transport failure.\nDo not use when: you need the actual unread messages and their ids (use list-messages with unreadOnly, or search-messages with isRead=false) or broader totals across every mailbox (use get-mail-stats).",
82828
82869
  inputSchema: {
82829
82870
  mailbox: external_exports.string().optional().describe("Mailbox to check (default: INBOX)"),
82830
82871
  account: external_exports.string().optional().describe("Account to check")
@@ -82832,11 +82873,14 @@ server.registerTool(
82832
82873
  outputSchema: {
82833
82874
  unread: external_exports.number().optional(),
82834
82875
  mailbox: external_exports.string().optional(),
82835
- account: external_exports.string().optional()
82876
+ account: external_exports.string().optional(),
82877
+ partial: external_exports.boolean().optional(),
82878
+ failedAccounts: external_exports.array(external_exports.string()).optional()
82836
82879
  }
82837
82880
  },
82838
82881
  withErrorHandling(async ({ mailbox, account }) => {
82839
82882
  let count;
82883
+ const failedAccounts = [];
82840
82884
  if (shouldUseImap(account)) {
82841
82885
  if (account !== void 0) {
82842
82886
  count = await imapUnreadCount(mailbox, { account });
@@ -82849,17 +82893,32 @@ server.registerTool(
82849
82893
  total += await imapUnreadCount(mailbox, { config: src.config });
82850
82894
  } catch (e) {
82851
82895
  console.error(`IMAP unread-count failed for "${src.label}": ${String(e)}`);
82896
+ failedAccounts.push(src.label);
82852
82897
  }
82853
82898
  } else {
82854
- total += mailManager.getUnreadCount(mailbox, src.account.name);
82899
+ const r = mailManager.getUnreadCountChecked(mailbox, src.account.name);
82900
+ if (r.failed) failedAccounts.push(src.account.name);
82901
+ else total += r.count ?? 0;
82855
82902
  }
82856
82903
  }
82857
82904
  count = total;
82858
82905
  }
82859
82906
  } else {
82860
- count = mailManager.getUnreadCount(mailbox, account);
82907
+ const r = mailManager.getUnreadCountChecked(mailbox, account);
82908
+ if (r.failed) {
82909
+ return errorResponse(
82910
+ `Could not read the unread count \u2014 the AppleScript transport failed: ${r.error}. This is NOT the same as zero unread. Mail may be busy, wedged, or missing an Automation grant; run the "doctor" tool to check.`
82911
+ );
82912
+ }
82913
+ count = r.count ?? 0;
82861
82914
  }
82862
82915
  const location = mailbox ? ` in "${mailbox}"` : "";
82916
+ if (failedAccounts.length > 0) {
82917
+ return successResponse(
82918
+ `${count} unread message(s)${location} \u2014 PARTIAL: ${failedAccounts.length} account(s) could not be read (${failedAccounts.join(", ")}), so the real total is higher. Run the "doctor" tool to check.`,
82919
+ { unread: count, mailbox, account, partial: true, failedAccounts }
82920
+ );
82921
+ }
82863
82922
  return successResponse(`${count} unread message(s)${location}`, {
82864
82923
  unread: count,
82865
82924
  mailbox,
@@ -83089,16 +83148,23 @@ ${lines || " (none met the threshold)"}`,
83089
83148
  server.registerTool(
83090
83149
  "list-accounts",
83091
83150
  {
83092
- description: "Use when: discovering the configured Mail accounts (e.g. iCloud, Gmail) so you can pass an exact account name to other tools.\nReturns: the account names and a count.\nDo not use when: you want the folders within an account (use list-mailboxes) or messages (use list-messages / search-messages).",
83151
+ description: "Use when: discovering the configured Mail accounts (e.g. iCloud, Gmail) so you can pass an exact account name to other tools.\nReturns: the account names and a count. If the AppleScript transport fails (timeout / wedged Mail / missing Automation grant) this returns an ERROR rather than an empty list \u2014 an empty list always means Mail really has no accounts.\nDo not use when: you want the folders within an account (use list-mailboxes) or messages (use list-messages / search-messages).",
83093
83152
  inputSchema: {},
83094
83153
  outputSchema: {
83095
83154
  accounts: external_exports.array(external_exports.object({}).passthrough()).optional(),
83096
- count: external_exports.number().optional()
83155
+ count: external_exports.number().optional(),
83156
+ partial: external_exports.boolean().optional(),
83157
+ error: external_exports.string().optional()
83097
83158
  }
83098
83159
  },
83099
83160
  withErrorHandling(() => {
83100
- const accounts = mailManager.listAccounts();
83101
- const structured = { accounts, count: accounts.length };
83161
+ const { accounts, failed, error: error2 } = mailManager.listAccountsChecked();
83162
+ const structured = failed ? { accounts, count: accounts.length, partial: true, error: error2 } : { accounts, count: accounts.length };
83163
+ if (failed) {
83164
+ return errorResponse(
83165
+ `Could not read the Mail account list \u2014 the AppleScript transport failed: ${error2}. This is NOT the same as having no accounts. Mail may be busy, wedged, or missing an Automation grant; run the "doctor" tool to check.`
83166
+ );
83167
+ }
83102
83168
  if (accounts.length === 0) {
83103
83169
  return successResponse("No Mail accounts found", structured);
83104
83170
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.10.3",
3
+ "version": "2.10.4",
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",