gogcli-mcp 2.23.1 → 2.23.2

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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
10
- "version": "2.23.1"
10
+ "version": "2.23.2"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli",
16
16
  "source": "./",
17
17
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
18
- "version": "2.23.1",
18
+ "version": "2.23.2",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
3
  "displayName": "gogcli",
4
- "version": "2.23.1",
4
+ "version": "2.23.2",
5
5
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
6
6
  "author": {
7
7
  "name": "Chris Hall",
package/dist/index.js CHANGED
@@ -31552,6 +31552,55 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
31552
31552
  return JSON.stringify(parsed, null, detectIndent(text));
31553
31553
  }
31554
31554
 
31555
+ // src/pagination.ts
31556
+ function detectIndent2(text) {
31557
+ const match = /\n(\s+)\S/.exec(text);
31558
+ return match ? match[1].replace(/\t/g, " ").length : 0;
31559
+ }
31560
+ function stripConsumedPageToken(text) {
31561
+ const trimmed = text.trim();
31562
+ if (trimmed === "" || !trimmed.startsWith("{")) return text;
31563
+ let parsed;
31564
+ try {
31565
+ parsed = JSON.parse(trimmed);
31566
+ } catch {
31567
+ return text;
31568
+ }
31569
+ const obj = parsed;
31570
+ if (obj.nextPageToken !== "") return text;
31571
+ delete obj.nextPageToken;
31572
+ return JSON.stringify(obj, null, detectIndent2(text));
31573
+ }
31574
+ function truncationWarning(returned, count) {
31575
+ const scope = count.total !== void 0 ? `returned ${returned} of ${count.total} matches` : count.atLeast !== void 0 ? `returned ${returned} of at least ${count.atLeast} matches` : `returned ${returned} matches and MORE EXIST beyond this page`;
31576
+ return `INCOMPLETE RESULT SET: ${scope}. Do not report an absence of results based on this response. Page with nextPageToken or narrow the query.`;
31577
+ }
31578
+ function annotateTruncation(out, returned, count) {
31579
+ out.truncated = true;
31580
+ out.returned = returned;
31581
+ if (count.total !== void 0) out.totalMatches = count.total;
31582
+ if (count.atLeast !== void 0) out.totalMatchesAtLeast = count.atLeast;
31583
+ out.warning = truncationWarning(returned, count);
31584
+ }
31585
+ function hasMorePages(parsed) {
31586
+ return typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
31587
+ }
31588
+ function annotateTruncatedList(result, itemsKey) {
31589
+ const first = result.content[0];
31590
+ if (result.isError || first?.type !== "text") return result;
31591
+ let parsed;
31592
+ try {
31593
+ parsed = JSON.parse(first.text);
31594
+ } catch {
31595
+ return result;
31596
+ }
31597
+ const items = parsed[itemsKey];
31598
+ if (!Array.isArray(items) || !hasMorePages(parsed)) return result;
31599
+ const out = { ...parsed };
31600
+ annotateTruncation(out, items.length, {});
31601
+ return rawTextResult(JSON.stringify(out));
31602
+ }
31603
+
31555
31604
  // src/tools/utils.ts
31556
31605
  var PAYLOAD_INLINE_MAX = 4096;
31557
31606
  function payloadArg(inlineFlag, fileFlag, value, ext) {
@@ -31587,9 +31636,19 @@ var ids = {
31587
31636
  // People API uses fully-qualified resource names ("people/c123") not bare IDs.
31588
31637
  person: external_exports.string().describe("Person resource name (people/...) or email")
31589
31638
  };
31639
+ var pageTokenParam = external_exports.string().optional().describe(
31640
+ "Cursor for the NEXT page. Pass back the nextPageToken from a previous response verbatim, keeping the query and max identical: call once, then call again with pageToken=<that value>. A response with NO nextPageToken is the last page."
31641
+ );
31642
+ var pageAliasParam = external_exports.string().optional().describe(
31643
+ "Deprecated alias for pageToken, accepted so existing callers keep working. Use pageToken \u2014 it matches the nextPageToken field in the response."
31644
+ );
31645
+ function resolvePageToken(p) {
31646
+ return p.pageToken ?? p.page;
31647
+ }
31590
31648
  var paginationParams = {
31591
31649
  max: external_exports.number().int().optional().describe("Max results"),
31592
- page: external_exports.string().optional().describe("Page token"),
31650
+ pageToken: pageTokenParam,
31651
+ page: pageAliasParam,
31593
31652
  all: external_exports.boolean().optional().describe("Fetch all pages")
31594
31653
  };
31595
31654
  function registerRunTool(server, options) {
@@ -31664,7 +31723,7 @@ ${accounts || "(none)"}${hint}`);
31664
31723
  async function runOrDiagnose(args, options) {
31665
31724
  try {
31666
31725
  const raw = await run(args, options);
31667
- return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
31726
+ return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
31668
31727
  } catch (err) {
31669
31728
  return diagnose(err);
31670
31729
  }
@@ -31892,7 +31951,7 @@ function registerAuthTools(server) {
31892
31951
  // src/tools/calendar.ts
31893
31952
  function registerCalendarTools(server) {
31894
31953
  server.registerTool("gog_calendar_events", {
31895
- description: "List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today).",
31954
+ description: 'List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today). gog returns only 10 events by default, so a wide date range is USUALLY INCOMPLETE: raise max, or page with pageToken until the response carries no nextPageToken. A response carrying "truncated": true is an incomplete view \u2014 never conclude an event does not exist from one.',
31896
31955
  annotations: { readOnlyHint: true },
31897
31956
  inputSchema: {
31898
31957
  calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
@@ -31900,22 +31959,29 @@ function registerCalendarTools(server) {
31900
31959
  to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
31901
31960
  today: external_exports.boolean().optional().describe("Only show today's events"),
31902
31961
  query: external_exports.string().optional().describe("Free text search within events"),
31903
- all: external_exports.boolean().optional().describe("Fetch events from all calendars"),
31962
+ max: external_exports.number().int().optional().describe("Max events to return. gog defaults to 10, which silently hides the rest \u2014 raise it, or page with pageToken."),
31963
+ pageToken: pageTokenParam,
31964
+ page: pageAliasParam,
31965
+ all: external_exports.boolean().optional().describe('Fetch events from ALL CALENDARS. NOTE: unlike the gmail search tools, this does NOT mean "all pages" \u2014 it widens the calendar set, not the page window. Use pageToken to reach later pages.'),
31904
31966
  eventTypes: external_exports.array(external_exports.enum(["default", "birthday", "focus-time", "from-gmail", "out-of-office", "working-location"])).optional().describe("Filter to specific event types (repeatable)"),
31905
31967
  timezone: external_exports.string().optional().describe(`Display timezone for event times (IANA name, e.g. America/New_York, or "local" for the system timezone). Default: each event's timezone, then its calendar's timezone.`),
31906
31968
  account: accountParam
31907
31969
  }
31908
- }, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
31970
+ }, async ({ calendarId, from, to, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
31909
31971
  const args = ["calendar", "events"];
31910
31972
  if (calendarId) args.push(calendarId);
31911
31973
  if (from) args.push(`--from=${from}`);
31912
31974
  if (to) args.push(`--to=${to}`);
31913
31975
  if (today) args.push("--today");
31914
31976
  if (query) args.push(`--query=${query}`);
31977
+ if (max !== void 0) args.push(`--max=${max}`);
31978
+ const token = resolvePageToken({ pageToken, page });
31979
+ if (token) args.push(`--page=${token}`);
31915
31980
  if (all) args.push("--all");
31916
31981
  if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
31917
31982
  if (timezone) args.push(`--timezone=${timezone}`);
31918
- return runOrDiagnose(args, { account });
31983
+ const result = await runOrDiagnose(args, { account });
31984
+ return annotateTruncatedList(result, "events");
31919
31985
  });
31920
31986
  server.registerTool("gog_calendar_get", {
31921
31987
  description: "Get a specific calendar event by ID.",
@@ -32032,17 +32098,19 @@ function registerClassroomTools(server) {
32032
32098
  teacher: external_exports.string().optional().describe("Filter by teacher user ID"),
32033
32099
  student: external_exports.string().optional().describe("Filter by student user ID"),
32034
32100
  max: external_exports.number().optional().describe("Max results per page"),
32035
- page: external_exports.string().optional().describe("Page token for pagination"),
32101
+ pageToken: pageTokenParam,
32102
+ page: pageAliasParam,
32036
32103
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32037
32104
  account: accountParam
32038
32105
  }
32039
- }, async ({ state, teacher, student, max, page, all, account }) => {
32106
+ }, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
32040
32107
  const args = ["classroom", "courses", "list"];
32041
32108
  if (state) args.push(`--state=${state}`);
32042
32109
  if (teacher) args.push(`--teacher=${teacher}`);
32043
32110
  if (student) args.push(`--student=${student}`);
32044
32111
  if (max !== void 0) args.push(`--max=${max}`);
32045
- if (page) args.push(`--page=${page}`);
32112
+ const token = resolvePageToken({ pageToken, page });
32113
+ if (token) args.push(`--page=${token}`);
32046
32114
  if (all) args.push("--all");
32047
32115
  return runOrDiagnose(args, { account });
32048
32116
  });
@@ -32062,14 +32130,16 @@ function registerClassroomTools(server) {
32062
32130
  inputSchema: {
32063
32131
  courseId: external_exports.string().describe("Course ID"),
32064
32132
  max: external_exports.number().optional().describe("Max results per page"),
32065
- page: external_exports.string().optional().describe("Page token"),
32133
+ pageToken: pageTokenParam,
32134
+ page: pageAliasParam,
32066
32135
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32067
32136
  account: accountParam
32068
32137
  }
32069
- }, async ({ courseId, max, page, all, account }) => {
32138
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
32070
32139
  const args = ["classroom", "students", "list", courseId];
32071
32140
  if (max !== void 0) args.push(`--max=${max}`);
32072
- if (page) args.push(`--page=${page}`);
32141
+ const token = resolvePageToken({ pageToken, page });
32142
+ if (token) args.push(`--page=${token}`);
32073
32143
  if (all) args.push("--all");
32074
32144
  return runOrDiagnose(args, { account });
32075
32145
  });
@@ -32090,14 +32160,16 @@ function registerClassroomTools(server) {
32090
32160
  inputSchema: {
32091
32161
  courseId: external_exports.string().describe("Course ID"),
32092
32162
  max: external_exports.number().optional().describe("Max results per page"),
32093
- page: external_exports.string().optional().describe("Page token"),
32163
+ pageToken: pageTokenParam,
32164
+ page: pageAliasParam,
32094
32165
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32095
32166
  account: accountParam
32096
32167
  }
32097
- }, async ({ courseId, max, page, all, account }) => {
32168
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
32098
32169
  const args = ["classroom", "teachers", "list", courseId];
32099
32170
  if (max !== void 0) args.push(`--max=${max}`);
32100
- if (page) args.push(`--page=${page}`);
32171
+ const token = resolvePageToken({ pageToken, page });
32172
+ if (token) args.push(`--page=${token}`);
32101
32173
  if (all) args.push("--all");
32102
32174
  return runOrDiagnose(args, { account });
32103
32175
  });
@@ -32120,16 +32192,18 @@ function registerClassroomTools(server) {
32120
32192
  students: external_exports.boolean().optional().describe("Include students only"),
32121
32193
  teachers: external_exports.boolean().optional().describe("Include teachers only"),
32122
32194
  max: external_exports.number().optional().describe("Max results per page"),
32123
- page: external_exports.string().optional().describe("Page token"),
32195
+ pageToken: pageTokenParam,
32196
+ page: pageAliasParam,
32124
32197
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32125
32198
  account: accountParam
32126
32199
  }
32127
- }, async ({ courseId, students, teachers, max, page, all, account }) => {
32200
+ }, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
32128
32201
  const args = ["classroom", "roster", courseId];
32129
32202
  if (students) args.push("--students");
32130
32203
  if (teachers) args.push("--teachers");
32131
32204
  if (max !== void 0) args.push(`--max=${max}`);
32132
- if (page) args.push(`--page=${page}`);
32205
+ const token = resolvePageToken({ pageToken, page });
32206
+ if (token) args.push(`--page=${token}`);
32133
32207
  if (all) args.push("--all");
32134
32208
  return runOrDiagnose(args, { account });
32135
32209
  });
@@ -32142,18 +32216,20 @@ function registerClassroomTools(server) {
32142
32216
  topic: external_exports.string().optional().describe("Filter by topic ID"),
32143
32217
  orderBy: external_exports.string().optional().describe('Sort order (e.g. "updateTime desc")'),
32144
32218
  max: external_exports.number().optional().describe("Max results per page"),
32145
- page: external_exports.string().optional().describe("Page token"),
32219
+ pageToken: pageTokenParam,
32220
+ page: pageAliasParam,
32146
32221
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32147
32222
  scanPages: external_exports.number().optional().describe("Max pages to scan when filtering"),
32148
32223
  account: accountParam
32149
32224
  }
32150
- }, async ({ courseId, state, topic, orderBy, max, page, all, scanPages, account }) => {
32225
+ }, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
32151
32226
  const args = ["classroom", "coursework", "list", courseId];
32152
32227
  if (state) args.push(`--state=${state}`);
32153
32228
  if (topic) args.push(`--topic=${topic}`);
32154
32229
  if (orderBy) args.push(`--order-by=${orderBy}`);
32155
32230
  if (max !== void 0) args.push(`--max=${max}`);
32156
- if (page) args.push(`--page=${page}`);
32231
+ const token = resolvePageToken({ pageToken, page });
32232
+ if (token) args.push(`--page=${token}`);
32157
32233
  if (all) args.push("--all");
32158
32234
  if (scanPages !== void 0) args.push(`--scan-pages=${scanPages}`);
32159
32235
  return runOrDiagnose(args, { account });
@@ -32179,17 +32255,19 @@ function registerClassroomTools(server) {
32179
32255
  late: external_exports.enum(["late", "not-late"]).optional().describe("Filter by late status"),
32180
32256
  user: external_exports.string().optional().describe("Filter by student user ID"),
32181
32257
  max: external_exports.number().optional().describe("Max results per page"),
32182
- page: external_exports.string().optional().describe("Page token"),
32258
+ pageToken: pageTokenParam,
32259
+ page: pageAliasParam,
32183
32260
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32184
32261
  account: accountParam
32185
32262
  }
32186
- }, async ({ courseId, courseworkId, state, late: late2, user, max, page, all, account }) => {
32263
+ }, async ({ courseId, courseworkId, state, late: late2, user, max, pageToken, page, all, account }) => {
32187
32264
  const args = ["classroom", "submissions", "list", courseId, courseworkId];
32188
32265
  if (state) args.push(`--state=${state}`);
32189
32266
  if (late2) args.push(`--late=${late2}`);
32190
32267
  if (user) args.push(`--user=${user}`);
32191
32268
  if (max !== void 0) args.push(`--max=${max}`);
32192
- if (page) args.push(`--page=${page}`);
32269
+ const token = resolvePageToken({ pageToken, page });
32270
+ if (token) args.push(`--page=${token}`);
32193
32271
  if (all) args.push("--all");
32194
32272
  return runOrDiagnose(args, { account });
32195
32273
  });
@@ -32266,16 +32344,18 @@ function registerClassroomTools(server) {
32266
32344
  state: external_exports.string().optional().describe("Filter by announcement state"),
32267
32345
  orderBy: external_exports.string().optional().describe("Sort order"),
32268
32346
  max: external_exports.number().optional().describe("Max results per page"),
32269
- page: external_exports.string().optional().describe("Page token"),
32347
+ pageToken: pageTokenParam,
32348
+ page: pageAliasParam,
32270
32349
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32271
32350
  account: accountParam
32272
32351
  }
32273
- }, async ({ courseId, state, orderBy, max, page, all, account }) => {
32352
+ }, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
32274
32353
  const args = ["classroom", "announcements", "list", courseId];
32275
32354
  if (state) args.push(`--state=${state}`);
32276
32355
  if (orderBy) args.push(`--order-by=${orderBy}`);
32277
32356
  if (max !== void 0) args.push(`--max=${max}`);
32278
- if (page) args.push(`--page=${page}`);
32357
+ const token = resolvePageToken({ pageToken, page });
32358
+ if (token) args.push(`--page=${token}`);
32279
32359
  if (all) args.push("--all");
32280
32360
  return runOrDiagnose(args, { account });
32281
32361
  });
@@ -32311,14 +32391,16 @@ function registerClassroomTools(server) {
32311
32391
  inputSchema: {
32312
32392
  courseId: external_exports.string().describe("Course ID"),
32313
32393
  max: external_exports.number().optional().describe("Max results per page"),
32314
- page: external_exports.string().optional().describe("Page token"),
32394
+ pageToken: pageTokenParam,
32395
+ page: pageAliasParam,
32315
32396
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32316
32397
  account: accountParam
32317
32398
  }
32318
- }, async ({ courseId, max, page, all, account }) => {
32399
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
32319
32400
  const args = ["classroom", "topics", "list", courseId];
32320
32401
  if (max !== void 0) args.push(`--max=${max}`);
32321
- if (page) args.push(`--page=${page}`);
32402
+ const token = resolvePageToken({ pageToken, page });
32403
+ if (token) args.push(`--page=${token}`);
32322
32404
  if (all) args.push("--all");
32323
32405
  return runOrDiagnose(args, { account });
32324
32406
  });
@@ -32340,16 +32422,18 @@ function registerClassroomTools(server) {
32340
32422
  course: external_exports.string().optional().describe("Filter by course ID"),
32341
32423
  user: external_exports.string().optional().describe("Filter by user ID"),
32342
32424
  max: external_exports.number().optional().describe("Max results per page"),
32343
- page: external_exports.string().optional().describe("Page token"),
32425
+ pageToken: pageTokenParam,
32426
+ page: pageAliasParam,
32344
32427
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32345
32428
  account: accountParam
32346
32429
  }
32347
- }, async ({ course, user, max, page, all, account }) => {
32430
+ }, async ({ course, user, max, pageToken, page, all, account }) => {
32348
32431
  const args = ["classroom", "invitations", "list"];
32349
32432
  if (course) args.push(`--course=${course}`);
32350
32433
  if (user) args.push(`--user=${user}`);
32351
32434
  if (max !== void 0) args.push(`--max=${max}`);
32352
- if (page) args.push(`--page=${page}`);
32435
+ const token = resolvePageToken({ pageToken, page });
32436
+ if (token) args.push(`--page=${token}`);
32353
32437
  if (all) args.push("--all");
32354
32438
  return runOrDiagnose(args, { account });
32355
32439
  });
@@ -32559,16 +32643,18 @@ function registerDriveTools(server) {
32559
32643
  inputSchema: {
32560
32644
  folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
32561
32645
  max: external_exports.number().optional().describe("Max results (default: 20)"),
32562
- page: external_exports.string().optional().describe("Page token for pagination"),
32646
+ pageToken: pageTokenParam,
32647
+ page: pageAliasParam,
32563
32648
  query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
32564
32649
  allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
32565
32650
  account: accountParam
32566
32651
  }
32567
- }, async ({ folderId, max, page, query, allDrives, account }) => {
32652
+ }, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
32568
32653
  const args = ["drive", "ls"];
32569
32654
  if (folderId) args.push(`--parent=${folderId}`);
32570
32655
  if (max !== void 0) args.push(`--max=${max}`);
32571
- if (page) args.push(`--page=${page}`);
32656
+ const token = resolvePageToken({ pageToken, page });
32657
+ if (token) args.push(`--page=${token}`);
32572
32658
  if (query) args.push(`--query=${query}`);
32573
32659
  if (allDrives === false) args.push("--no-all-drives");
32574
32660
  return runOrDiagnose(args, { account });
@@ -32758,22 +32844,130 @@ function registerDriveTools(server) {
32758
32844
  registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
32759
32845
  }
32760
32846
 
32847
+ // src/gmail-results.ts
32848
+ function sortKey(item) {
32849
+ for (const raw of [item.internalDateIso, item.date]) {
32850
+ if (typeof raw !== "string" || !raw) continue;
32851
+ const t = Date.parse(raw);
32852
+ if (!Number.isNaN(t)) return t;
32853
+ }
32854
+ return Number.NEGATIVE_INFINITY;
32855
+ }
32856
+ function sortNewestFirst(items) {
32857
+ return [...items].sort((a, b) => {
32858
+ const ka = sortKey(a);
32859
+ const kb = sortKey(b);
32860
+ return ka === kb ? 0 : kb - ka;
32861
+ });
32862
+ }
32863
+ var COUNT_PROBE_PAGE_SIZE = 500;
32864
+ async function countMatches(method, itemsKey, query, account) {
32865
+ try {
32866
+ const params = JSON.stringify({
32867
+ userId: "me",
32868
+ q: query,
32869
+ maxResults: COUNT_PROBE_PAGE_SIZE,
32870
+ fields: `${itemsKey}/id,nextPageToken`
32871
+ });
32872
+ const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
32873
+ const parsed = JSON.parse(raw);
32874
+ const items = parsed[itemsKey];
32875
+ if (!Array.isArray(items)) return {};
32876
+ const more = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
32877
+ return more ? { atLeast: items.length } : { total: items.length };
32878
+ } catch {
32879
+ return {};
32880
+ }
32881
+ }
32882
+ async function finalizeGmailSearch(result, options) {
32883
+ const { itemsKey, method, query, account, queryIsExact = true } = options;
32884
+ const first = result.content[0];
32885
+ if (result.isError || first?.type !== "text") return result;
32886
+ let parsed;
32887
+ try {
32888
+ parsed = JSON.parse(first.text);
32889
+ } catch {
32890
+ return result;
32891
+ }
32892
+ const items = parsed[itemsKey];
32893
+ if (!Array.isArray(items)) return result;
32894
+ const sorted = sortNewestFirst(items);
32895
+ const out = { ...parsed, [itemsKey]: sorted };
32896
+ if (hasMorePages(parsed)) {
32897
+ const count = queryIsExact ? await countMatches(method, itemsKey, query, account) : {};
32898
+ annotateTruncation(out, sorted.length, count);
32899
+ }
32900
+ return rawTextResult(JSON.stringify(out));
32901
+ }
32902
+ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
32903
+ const merged = [];
32904
+ let base;
32905
+ let token = startToken;
32906
+ for (let pages = 0; pages < maxPages; pages++) {
32907
+ const result = await runPage(token);
32908
+ const parsed = parsePage(result, itemsKey);
32909
+ if (parsed === void 0) {
32910
+ return base === void 0 ? result : finish(base, itemsKey, merged, token);
32911
+ }
32912
+ base = parsed;
32913
+ merged.push(...parsed[itemsKey]);
32914
+ token = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
32915
+ if (token === void 0) break;
32916
+ }
32917
+ return finish(base, itemsKey, merged, token);
32918
+ }
32919
+ function parsePage(result, itemsKey) {
32920
+ const first = result.content[0];
32921
+ if (result.isError || first?.type !== "text") return void 0;
32922
+ let parsed;
32923
+ try {
32924
+ parsed = JSON.parse(first.text);
32925
+ } catch {
32926
+ return void 0;
32927
+ }
32928
+ if (parsed === null || typeof parsed !== "object") return void 0;
32929
+ const obj = parsed;
32930
+ return Array.isArray(obj[itemsKey]) ? obj : void 0;
32931
+ }
32932
+ function finish(base, itemsKey, merged, token) {
32933
+ const out = { ...base, [itemsKey]: merged };
32934
+ if (token === void 0) delete out.nextPageToken;
32935
+ else out.nextPageToken = token;
32936
+ return rawTextResult(JSON.stringify(out));
32937
+ }
32938
+
32761
32939
  // src/tools/gmail.ts
32762
32940
  function registerGmailTools(server) {
32763
32941
  server.registerTool("gog_gmail_search", {
32764
- description: `Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail's own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com).`,
32942
+ description: 'Search Gmail threads using Gmail query syntax (e.g. "from:alice subject:invoice is:unread"). The query is passed verbatim to Gmail; a bare name token (from:alison) matches per Gmail\'s own heuristics, a full address (from:alison@example.com) is exact. To match a contact across several addresses, OR them: from:(a@x.com OR b@y.com). Results are ALWAYS newest-first by Gmail\'s internalDate \u2014 the wrapper sorts them, so the first result is the most recent match and a recent message can never be buried below older ones. IMPORTANT \u2014 a response carrying "truncated": true is an INCOMPLETE view of the matches: NEVER report that a message does not exist, or that there is no such mail, on the strength of one. Page through it (pass nextPageToken back as `pageToken`), set maxPages to walk several pages in one call, or narrow the query, and only then draw a conclusion. If you already know the thread, do not search for it at all \u2014 read it directly with gog_gmail_thread_get, which returns the whole thread and cannot be truncated or mis-ranked.',
32765
32943
  annotations: { readOnlyHint: true },
32766
32944
  inputSchema: {
32767
32945
  query: external_exports.string().describe("Gmail search query"),
32768
32946
  max: external_exports.number().int().optional().describe("Max results to return (default: 10)"),
32947
+ pageToken: pageTokenParam,
32948
+ page: pageAliasParam,
32949
+ maxPages: external_exports.number().int().positive().max(20).optional().describe('Walk up to this many pages in ONE call and merge the results, instead of returning a single page. Use it for existence questions ("is there any mail matching X?"), which a single page cannot answer. Stops early at the last page; if pages remain when the cap is hit the response is still marked truncated. Prefer this over all=true, which is unbounded.'),
32950
+ all: external_exports.boolean().optional().describe('Fetch every page instead of one. Removes truncation entirely, at the cost of one API round-trip per page \u2014 the reliable way to answer "does any message match?" for a query with few expected hits.'),
32769
32951
  fromContact: external_exports.string().optional().describe("Resolve a Google Contact (name or email) to its addresses and AND a from:(addr OR addr) clause onto the query \u2014 saves looking the contact up first when you only know who, not which address."),
32770
32952
  account: accountParam
32771
32953
  }
32772
- }, async ({ query, max, fromContact, account }) => {
32954
+ }, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
32773
32955
  const args = ["gmail", "search", query];
32774
32956
  if (max !== void 0) args.push(`--max=${max}`);
32957
+ if (all) args.push("--all");
32775
32958
  if (fromContact) args.push(`--from-contact=${fromContact}`);
32776
- return runOrDiagnose(args, { account });
32959
+ const runPage = (tok) => runOrDiagnose(tok ? [...args, `--page=${tok}`] : args, { account });
32960
+ const token = resolvePageToken({ pageToken, page });
32961
+ const result = maxPages !== void 0 ? await fetchGmailPages(runPage, "threads", maxPages, token) : await runPage(token);
32962
+ return finalizeGmailSearch(result, {
32963
+ itemsKey: "threads",
32964
+ method: "users.threads.list",
32965
+ query,
32966
+ account,
32967
+ // --from-contact is expanded INSIDE gog, against the People API, so the
32968
+ // query Gmail actually saw is not the one we hold here.
32969
+ queryIsExact: !fromContact
32970
+ });
32777
32971
  });
32778
32972
  server.registerTool("gog_gmail_get", {
32779
32973
  description: "Get a Gmail message by ID.",
@@ -33138,7 +33332,7 @@ function registerTasksTools(server) {
33138
33332
  }
33139
33333
 
33140
33334
  // src/server.ts
33141
- var VERSION = true ? "2.23.1" : "0.0.0";
33335
+ var VERSION = true ? "2.23.2" : "0.0.0";
33142
33336
  var BASE_TOOL_REGISTRARS = [
33143
33337
  registerApiTools,
33144
33338
  registerAuthTools,