apple-mail-mcp 2.19.18 → 2.19.19

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
@@ -240,6 +240,7 @@ Search for messages matching criteria. Searches all accounts by default.
240
240
  | `dateFrom` | string | No | Start date filter (e.g., "January 1, 2026") |
241
241
  | `dateTo` | string | No | End date filter (e.g., "March 1, 2026") |
242
242
  | `limit` | number | No | Max results, 1–500 (default: 50) |
243
+ | `offset` | number | No | Newest matches to skip, ≥ 0 (for pagination). IMAP accounts only; without a `mailbox`, `offset` + `limit` may not exceed 5,000 |
243
244
 
244
245
  **Returns:** List of matching messages with ID, date, subject, sender, and read state.
245
246
 
@@ -1103,14 +1104,23 @@ which made a filter-less `list-messages` fail on such a mailbox before 2.19.16.
1103
1104
  **Very large IMAP mailboxes are read newest-first in bounded windows**
1104
1105
  ([#256](https://github.com/sweetrb/apple-mail-mcp/issues/256)). Above 10,000
1105
1106
  messages, a filter-less `list-messages`/`search-messages` pages by message
1106
- sequence number from the top of the mailbox — it fetches UIDs and flags for
1107
- just enough of the newest messages to fill `limit` + `offset` (skipping
1108
- `\Deleted` ones) and full rows for the page alone, so `limit: 1` on a
1109
- 793,614-message mailbox costs one small `FETCH`, not a whole-mailbox `SEARCH`.
1107
+ sequence number: page `offset` of `limit` is sequence numbers
1108
+ `N-offset-limit+1 .. N-offset`, so it fetches UIDs and flags for that range
1109
+ alone (plus a small margin; `\Deleted` ones are skipped and the page topped up
1110
+ from below) and full rows for the page. The cost is the page, not the offset:
1111
+ `limit: 1` at offset 790,000 of a 793,614-message mailbox is one ~33-message
1112
+ `FETCH`, not a whole-mailbox `SEARCH` or a walk over every skipped message.
1113
+ Offsets count sequence positions. iCloud keeps messages awaiting expunge out of
1114
+ the sequence space, so there they are exact; on a server that keeps them in it,
1115
+ a page shifts by the number of such messages above it (they are never
1116
+ returned).
1110
1117
  A filtered search there runs the same criteria over newest-first sequence
1111
1118
  windows (5,000 messages, growing to 50,000) and stops once the page is full;
1112
1119
  the reported total then reads `at least N` unless the walk reached the bottom
1113
- of the mailbox. A `SEARCH` or `FETCH` that fails names the mailbox, its size and
1120
+ of the mailbox. A filtered search's deep `offset` has no such shortcut — which
1121
+ messages match is only known by searching the skipped windows — so its cost
1122
+ still grows with the offset (in 50,000-message `SEARCH` windows); narrow it
1123
+ with `dateTo` instead. A `SEARCH` or `FETCH` that fails names the mailbox, its size and
1114
1124
  the server's own reason (or a likely timeout) instead of reporting "no
1115
1125
  messages".
1116
1126
 
package/build/cli.js CHANGED
@@ -58049,6 +58049,7 @@ __export(imapClient_exports, {
58049
58049
  MAX_COMPOSE_SOURCE_BYTES: () => MAX_COMPOSE_SOURCE_BYTES,
58050
58050
  MAX_RFC822_FILE_BYTES: () => MAX_RFC822_FILE_BYTES,
58051
58051
  MAX_RFC822_INLINE_BYTES: () => MAX_RFC822_INLINE_BYTES,
58052
+ MAX_UNSCOPED_SEARCH_DEPTH: () => MAX_UNSCOPED_SEARCH_DEPTH,
58052
58053
  __resetPool: () => __resetPool,
58053
58054
  __setPoolConnect: () => __setPoolConnect,
58054
58055
  bodyStructureHasAttachments: () => bodyStructureHasAttachments,
@@ -58096,7 +58097,8 @@ __export(imapClient_exports, {
58096
58097
  resolveImapConfig: () => resolveImapConfig,
58097
58098
  resolveImapConfigs: () => resolveImapConfigs,
58098
58099
  resolveMailboxPath: () => resolveMailboxPath,
58099
- shouldUseImap: () => shouldUseImap
58100
+ shouldUseImap: () => shouldUseImap,
58101
+ unscopedSearchOffsetError: () => unscopedSearchOffsetError
58100
58102
  });
58101
58103
  import { createHash } from "node:crypto";
58102
58104
  function encodeImapId(account, path, uid) {
@@ -58442,6 +58444,10 @@ function messageIdentity(entry) {
58442
58444
  const messageId = raw.replace(/^<+|>+$/g, "").trim().toLowerCase();
58443
58445
  return messageId ? `mid:${messageId}` : `${entry.path}\0${entry.message.uid}`;
58444
58446
  }
58447
+ function unscopedSearchOffsetError(offset, limit) {
58448
+ if (offset <= 0 || offset + limit <= MAX_UNSCOPED_SEARCH_DEPTH) return void 0;
58449
+ return `search-messages without a mailbox merges every mailbox's newest offset+limit matches, so it pages at most ${MAX_UNSCOPED_SEARCH_DEPTH.toLocaleString("en-US")} deep (offset ${offset} + limit ${limit} asked). Name a mailbox (e.g. the account's All Mail or Archive) to page deeper, or narrow the search with dateTo.`;
58450
+ }
58445
58451
  function isUnfiltered(criteria) {
58446
58452
  const keys = Object.keys(criteria);
58447
58453
  return keys.length === 1 && criteria.deleted === false;
@@ -58565,16 +58571,16 @@ function pageRows(fetch) {
58565
58571
  function omissionReason(fetch) {
58566
58572
  return "the server's FETCH response for this message could not be read, even without BODYSTRUCTURE or ENVELOPE" + (fetch.cause ? ` (${fetch.cause})` : "");
58567
58573
  }
58568
- async function walkWindows(exists, page, firstWindow, readWindow) {
58574
+ async function walkWindows(top, page, firstWindow, readWindow, openTop = true) {
58569
58575
  const wanted = page.skip + page.take;
58570
58576
  const uids = [];
58571
58577
  let seen = 0;
58572
58578
  let matched = 0;
58573
- let hi = exists;
58579
+ let hi = top;
58574
58580
  let width = Math.max(1, Math.min(firstWindow, MAX_WINDOW));
58575
58581
  while (hi >= 1 && seen < wanted) {
58576
58582
  const lo = Math.max(1, hi - width + 1);
58577
- const live = (await readWindow(lo, hi === exists ? "*" : hi, hi - lo + 1)).slice().sort((a, b) => b - a);
58583
+ const live = (await readWindow(lo, hi === top && openTop ? "*" : hi, hi - lo + 1)).slice().sort((a, b) => b - a);
58578
58584
  matched += live.length;
58579
58585
  for (const uid of live) {
58580
58586
  if (seen >= wanted) break;
@@ -58586,13 +58592,21 @@ async function walkWindows(exists, page, firstWindow, readWindow) {
58586
58592
  }
58587
58593
  return { uids, matched, exhausted: hi < 1 };
58588
58594
  }
58595
+ function sequenceTop(client, statusCount) {
58596
+ const mb = client.mailbox;
58597
+ const exists = mb && typeof mb.exists === "number" && mb.exists >= 0 ? mb.exists : void 0;
58598
+ return exists ?? statusCount;
58599
+ }
58589
58600
  async function listLargeMailbox(client, exists, page) {
58601
+ const top = sequenceTop(client, exists) - page.skip;
58602
+ if (top < 1 || page.take === 0) {
58603
+ return { messages: [], omitted: [], total: exists, totalExact: true };
58604
+ }
58590
58605
  let deletedSeen = 0;
58591
58606
  const walk = await walkWindows(
58592
- exists,
58593
- page,
58594
- // Room for a few ghosts on the first read without a second round trip.
58595
- page.skip + page.take + 64,
58607
+ top,
58608
+ { skip: 0, take: page.take },
58609
+ page.take + DELETED_MARGIN,
58596
58610
  async (lo, hi) => {
58597
58611
  const live = [];
58598
58612
  for await (const msg of client.fetch(`${lo}:${hi}`, { uid: true, flags: true })) {
@@ -58600,7 +58614,10 @@ async function listLargeMailbox(client, exists, page) {
58600
58614
  else live.push(msg.uid);
58601
58615
  }
58602
58616
  return live;
58603
- }
58617
+ },
58618
+ // From the very top the first window is `lo:*`, as before; below it the
58619
+ // range is exact.
58620
+ page.skip === 0
58604
58621
  );
58605
58622
  return {
58606
58623
  ...pageRows(await fetchRows(client, walk.uids)),
@@ -58678,6 +58695,10 @@ async function run(args, listMode, deps) {
58678
58695
  }
58679
58696
  const limit = args.limit ?? 50;
58680
58697
  const offset = args.offset ?? 0;
58698
+ if (unscopedSearch) {
58699
+ const tooDeep = unscopedSearchOffsetError(offset, limit);
58700
+ if (tooDeep) throw new Error(tooDeep);
58701
+ }
58681
58702
  const criteria = buildCriteria(args, listMode);
58682
58703
  const page = unscopedSearch ? { skip: 0, take: offset + limit } : { skip: offset, take: limit };
58683
58704
  const fetched = [];
@@ -59755,7 +59776,7 @@ async function imapThread(id, deps = {}, limit = 50) {
59755
59776
  true
59756
59777
  );
59757
59778
  }
59758
- var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
59779
+ 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, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
59759
59780
  var init_imapClient = __esm({
59760
59781
  "src/services/imapClient.ts"() {
59761
59782
  "use strict";
@@ -59829,6 +59850,7 @@ var init_imapClient = __esm({
59829
59850
  LARGE_MAILBOX_MESSAGES = 1e4;
59830
59851
  FIRST_SEARCH_WINDOW = 5e3;
59831
59852
  MAX_WINDOW = 5e4;
59853
+ MAX_UNSCOPED_SEARCH_DEPTH = 5e3;
59832
59854
  ROW_QUERY = {
59833
59855
  envelope: true,
59834
59856
  flags: true,
@@ -59847,6 +59869,7 @@ var init_imapClient = __esm({
59847
59869
  internalDate: true,
59848
59870
  headers: ["date", "from", "subject", "message-id", "in-reply-to", "content-type"]
59849
59871
  };
59872
+ DELETED_MARGIN = 32;
59850
59873
  poolConnect = defaultConnect;
59851
59874
  pools = /* @__PURE__ */ new Map();
59852
59875
  connecting = /* @__PURE__ */ new Map();
package/build/index.js CHANGED
@@ -65378,6 +65378,7 @@ __export(imapClient_exports, {
65378
65378
  MAX_COMPOSE_SOURCE_BYTES: () => MAX_COMPOSE_SOURCE_BYTES,
65379
65379
  MAX_RFC822_FILE_BYTES: () => MAX_RFC822_FILE_BYTES,
65380
65380
  MAX_RFC822_INLINE_BYTES: () => MAX_RFC822_INLINE_BYTES,
65381
+ MAX_UNSCOPED_SEARCH_DEPTH: () => MAX_UNSCOPED_SEARCH_DEPTH,
65381
65382
  __resetPool: () => __resetPool,
65382
65383
  __setPoolConnect: () => __setPoolConnect,
65383
65384
  bodyStructureHasAttachments: () => bodyStructureHasAttachments,
@@ -65425,7 +65426,8 @@ __export(imapClient_exports, {
65425
65426
  resolveImapConfig: () => resolveImapConfig,
65426
65427
  resolveImapConfigs: () => resolveImapConfigs,
65427
65428
  resolveMailboxPath: () => resolveMailboxPath,
65428
- shouldUseImap: () => shouldUseImap
65429
+ shouldUseImap: () => shouldUseImap,
65430
+ unscopedSearchOffsetError: () => unscopedSearchOffsetError
65429
65431
  });
65430
65432
  import { createHash } from "node:crypto";
65431
65433
  function encodeImapId(account, path, uid) {
@@ -65771,6 +65773,10 @@ function messageIdentity(entry) {
65771
65773
  const messageId = raw.replace(/^<+|>+$/g, "").trim().toLowerCase();
65772
65774
  return messageId ? `mid:${messageId}` : `${entry.path}\0${entry.message.uid}`;
65773
65775
  }
65776
+ function unscopedSearchOffsetError(offset, limit) {
65777
+ if (offset <= 0 || offset + limit <= MAX_UNSCOPED_SEARCH_DEPTH) return void 0;
65778
+ return `search-messages without a mailbox merges every mailbox's newest offset+limit matches, so it pages at most ${MAX_UNSCOPED_SEARCH_DEPTH.toLocaleString("en-US")} deep (offset ${offset} + limit ${limit} asked). Name a mailbox (e.g. the account's All Mail or Archive) to page deeper, or narrow the search with dateTo.`;
65779
+ }
65774
65780
  function isUnfiltered(criteria) {
65775
65781
  const keys = Object.keys(criteria);
65776
65782
  return keys.length === 1 && criteria.deleted === false;
@@ -65894,16 +65900,16 @@ function pageRows(fetch) {
65894
65900
  function omissionReason(fetch) {
65895
65901
  return "the server's FETCH response for this message could not be read, even without BODYSTRUCTURE or ENVELOPE" + (fetch.cause ? ` (${fetch.cause})` : "");
65896
65902
  }
65897
- async function walkWindows(exists, page, firstWindow, readWindow) {
65903
+ async function walkWindows(top, page, firstWindow, readWindow, openTop = true) {
65898
65904
  const wanted = page.skip + page.take;
65899
65905
  const uids = [];
65900
65906
  let seen = 0;
65901
65907
  let matched = 0;
65902
- let hi = exists;
65908
+ let hi = top;
65903
65909
  let width = Math.max(1, Math.min(firstWindow, MAX_WINDOW));
65904
65910
  while (hi >= 1 && seen < wanted) {
65905
65911
  const lo = Math.max(1, hi - width + 1);
65906
- const live = (await readWindow(lo, hi === exists ? "*" : hi, hi - lo + 1)).slice().sort((a, b) => b - a);
65912
+ const live = (await readWindow(lo, hi === top && openTop ? "*" : hi, hi - lo + 1)).slice().sort((a, b) => b - a);
65907
65913
  matched += live.length;
65908
65914
  for (const uid of live) {
65909
65915
  if (seen >= wanted) break;
@@ -65915,13 +65921,21 @@ async function walkWindows(exists, page, firstWindow, readWindow) {
65915
65921
  }
65916
65922
  return { uids, matched, exhausted: hi < 1 };
65917
65923
  }
65924
+ function sequenceTop(client, statusCount) {
65925
+ const mb = client.mailbox;
65926
+ const exists = mb && typeof mb.exists === "number" && mb.exists >= 0 ? mb.exists : void 0;
65927
+ return exists ?? statusCount;
65928
+ }
65918
65929
  async function listLargeMailbox(client, exists, page) {
65930
+ const top = sequenceTop(client, exists) - page.skip;
65931
+ if (top < 1 || page.take === 0) {
65932
+ return { messages: [], omitted: [], total: exists, totalExact: true };
65933
+ }
65919
65934
  let deletedSeen = 0;
65920
65935
  const walk = await walkWindows(
65921
- exists,
65922
- page,
65923
- // Room for a few ghosts on the first read without a second round trip.
65924
- page.skip + page.take + 64,
65936
+ top,
65937
+ { skip: 0, take: page.take },
65938
+ page.take + DELETED_MARGIN,
65925
65939
  async (lo, hi) => {
65926
65940
  const live = [];
65927
65941
  for await (const msg of client.fetch(`${lo}:${hi}`, { uid: true, flags: true })) {
@@ -65929,7 +65943,10 @@ async function listLargeMailbox(client, exists, page) {
65929
65943
  else live.push(msg.uid);
65930
65944
  }
65931
65945
  return live;
65932
- }
65946
+ },
65947
+ // From the very top the first window is `lo:*`, as before; below it the
65948
+ // range is exact.
65949
+ page.skip === 0
65933
65950
  );
65934
65951
  return {
65935
65952
  ...pageRows(await fetchRows(client, walk.uids)),
@@ -66007,6 +66024,10 @@ async function run(args, listMode, deps) {
66007
66024
  }
66008
66025
  const limit = args.limit ?? 50;
66009
66026
  const offset = args.offset ?? 0;
66027
+ if (unscopedSearch) {
66028
+ const tooDeep = unscopedSearchOffsetError(offset, limit);
66029
+ if (tooDeep) throw new Error(tooDeep);
66030
+ }
66010
66031
  const criteria = buildCriteria(args, listMode);
66011
66032
  const page = unscopedSearch ? { skip: 0, take: offset + limit } : { skip: offset, take: limit };
66012
66033
  const fetched = [];
@@ -67084,7 +67105,7 @@ async function imapThread(id, deps = {}, limit = 50) {
67084
67105
  true
67085
67106
  );
67086
67107
  }
67087
- var import_imapflow, IMAP_ENV, defaultConnect, SPECIAL_USE_ALIASES, NOT_DELETED, LARGE_MAILBOX_MESSAGES, FIRST_SEARCH_WINDOW, MAX_WINDOW, ROW_QUERY, ROW_QUERY_NO_STRUCTURE, ROW_QUERY_HEADERS_ONLY, poolConnect, pools, connecting, MAX_COMPOSE_SOURCE_BYTES, MAX_RFC822_INLINE_BYTES, MAX_RFC822_FILE_BYTES, HEADER_WINDOW_BYTES, MAIL_FLAG_BITS, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
67108
+ 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, imapMarkRead, imapMarkUnread, FALLBACK_TRASH_PATH, imapBatchMarkRead, imapBatchMarkUnread, imapBatchFlag, imapBatchUnflag, imapBatchDelete;
67088
67109
  var init_imapClient = __esm({
67089
67110
  "src/services/imapClient.ts"() {
67090
67111
  "use strict";
@@ -67158,6 +67179,7 @@ var init_imapClient = __esm({
67158
67179
  LARGE_MAILBOX_MESSAGES = 1e4;
67159
67180
  FIRST_SEARCH_WINDOW = 5e3;
67160
67181
  MAX_WINDOW = 5e4;
67182
+ MAX_UNSCOPED_SEARCH_DEPTH = 5e3;
67161
67183
  ROW_QUERY = {
67162
67184
  envelope: true,
67163
67185
  flags: true,
@@ -67176,6 +67198,7 @@ var init_imapClient = __esm({
67176
67198
  internalDate: true,
67177
67199
  headers: ["date", "from", "subject", "message-id", "in-reply-to", "content-type"]
67178
67200
  };
67201
+ DELETED_MARGIN = 32;
67179
67202
  poolConnect = defaultConnect;
67180
67203
  pools = /* @__PURE__ */ new Map();
67181
67204
  connecting = /* @__PURE__ */ new Map();
@@ -88471,7 +88494,10 @@ registerTool(
88471
88494
  isFlagged: external_exports.boolean().optional().describe("Filter by flagged status"),
88472
88495
  dateFrom: DATE_FILTER_SCHEMA.describe("Start date filter (e.g., 'January 1, 2026')"),
88473
88496
  dateTo: DATE_FILTER_SCHEMA.describe("End date filter (e.g., 'March 1, 2026')"),
88474
- limit: external_exports.number().int().min(1).max(500).optional().describe("Maximum number of results (default: 50, max: 500)")
88497
+ limit: external_exports.number().int().min(1).max(500).optional().describe("Maximum number of results (default: 50, max: 500)"),
88498
+ offset: external_exports.number().int().min(0).optional().describe(
88499
+ "Number of newest matches to skip (for pagination; IMAP accounts only). Without a mailbox, offset + limit may not exceed 5,000 \u2014 name a mailbox to page deeper."
88500
+ )
88475
88501
  },
88476
88502
  outputSchema: LIST_OUTPUT_SCHEMA
88477
88503
  },
@@ -88482,6 +88508,7 @@ registerTool(
88482
88508
  mailbox,
88483
88509
  account,
88484
88510
  limit = 50,
88511
+ offset = 0,
88485
88512
  dateFrom,
88486
88513
  dateTo,
88487
88514
  from,
@@ -88489,12 +88516,17 @@ registerTool(
88489
88516
  isRead,
88490
88517
  isFlagged
88491
88518
  }) => {
88519
+ if (offset > 0 && !mailbox) {
88520
+ const tooDeep = unscopedSearchOffsetError(offset, limit);
88521
+ if (tooDeep) return errorResponse(tooDeep);
88522
+ }
88492
88523
  if (shouldUseImap(account)) {
88493
88524
  const imapArgs = {
88494
88525
  query,
88495
88526
  body,
88496
88527
  mailbox,
88497
88528
  limit,
88529
+ offset,
88498
88530
  dateFrom,
88499
88531
  dateTo,
88500
88532
  from,
@@ -88513,11 +88545,16 @@ registerTool(
88513
88545
  omittedMessages: r.omittedMessages
88514
88546
  });
88515
88547
  }
88516
- const fan = await fanOutImapMessages(imapArgs, "search");
88517
88548
  const { appleScriptOnly } = partitionAccountsForCounts(
88518
88549
  mailManager.listAccounts(),
88519
88550
  resolveImapConfigs()
88520
88551
  );
88552
+ if (offset > 0 && appleScriptOnly.length > 0 && !body) {
88553
+ return errorResponse(
88554
+ `offset is supported only on IMAP accounts, and ${appleScriptOnly.map((a) => `"${a.name}"`).join(", ")} ${appleScriptOnly.length === 1 ? "is" : "are"} AppleScript-only. Pass an IMAP account to page, or omit offset.`
88555
+ );
88556
+ }
88557
+ const fan = await fanOutImapMessages(imapArgs, "search");
88521
88558
  if (body) {
88522
88559
  const apple2 = {
88523
88560
  rows: [],
@@ -88548,6 +88585,11 @@ registerTool(
88548
88585
  );
88549
88586
  return mergedMessageResponse(fan, apple, limit, "matched");
88550
88587
  }
88588
+ if (offset > 0) {
88589
+ return errorResponse(
88590
+ `offset is supported only on IMAP accounts; IMAP is not configured for ${account ? `account "${account}"` : "any account"}. Omit offset, or narrow the search with dateFrom/dateTo instead.`
88591
+ );
88592
+ }
88551
88593
  if (body) {
88552
88594
  return errorResponse(
88553
88595
  `Body search requires the IMAP backend, which is not configured for ${account ? `account "${account}"` : "any account"}. Configure IMAP (see the IMAP backend section of the README) or search by query/subject/from instead.`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-mail-mcp",
3
- "version": "2.19.18",
3
+ "version": "2.19.19",
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",