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 +15 -5
- package/build/cli.js +33 -10
- package/build/index.js +54 -12
- package/package.json +1 -1
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
|
|
1107
|
-
|
|
1108
|
-
`\Deleted` ones
|
|
1109
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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 ===
|
|
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
|
-
|
|
58593
|
-
page,
|
|
58594
|
-
|
|
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(
|
|
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 =
|
|
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 ===
|
|
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
|
-
|
|
65922
|
-
page,
|
|
65923
|
-
|
|
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