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.
package/dist/lib.js CHANGED
@@ -23431,6 +23431,55 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
23431
23431
  return JSON.stringify(parsed, null, detectIndent(text));
23432
23432
  }
23433
23433
 
23434
+ // src/pagination.ts
23435
+ function detectIndent2(text) {
23436
+ const match = /\n(\s+)\S/.exec(text);
23437
+ return match ? match[1].replace(/\t/g, " ").length : 0;
23438
+ }
23439
+ function stripConsumedPageToken(text) {
23440
+ const trimmed = text.trim();
23441
+ if (trimmed === "" || !trimmed.startsWith("{")) return text;
23442
+ let parsed;
23443
+ try {
23444
+ parsed = JSON.parse(trimmed);
23445
+ } catch {
23446
+ return text;
23447
+ }
23448
+ const obj = parsed;
23449
+ if (obj.nextPageToken !== "") return text;
23450
+ delete obj.nextPageToken;
23451
+ return JSON.stringify(obj, null, detectIndent2(text));
23452
+ }
23453
+ function truncationWarning(returned, count) {
23454
+ 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`;
23455
+ return `INCOMPLETE RESULT SET: ${scope}. Do not report an absence of results based on this response. Page with nextPageToken or narrow the query.`;
23456
+ }
23457
+ function annotateTruncation(out, returned, count) {
23458
+ out.truncated = true;
23459
+ out.returned = returned;
23460
+ if (count.total !== void 0) out.totalMatches = count.total;
23461
+ if (count.atLeast !== void 0) out.totalMatchesAtLeast = count.atLeast;
23462
+ out.warning = truncationWarning(returned, count);
23463
+ }
23464
+ function hasMorePages(parsed) {
23465
+ return typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
23466
+ }
23467
+ function annotateTruncatedList(result, itemsKey) {
23468
+ const first = result.content[0];
23469
+ if (result.isError || first?.type !== "text") return result;
23470
+ let parsed;
23471
+ try {
23472
+ parsed = JSON.parse(first.text);
23473
+ } catch {
23474
+ return result;
23475
+ }
23476
+ const items = parsed[itemsKey];
23477
+ if (!Array.isArray(items) || !hasMorePages(parsed)) return result;
23478
+ const out = { ...parsed };
23479
+ annotateTruncation(out, items.length, {});
23480
+ return rawTextResult(JSON.stringify(out));
23481
+ }
23482
+
23434
23483
  // src/tools/utils.ts
23435
23484
  var PAYLOAD_INLINE_MAX = 4096;
23436
23485
  function payloadArg(inlineFlag, fileFlag, value, ext) {
@@ -23466,14 +23515,25 @@ var ids = {
23466
23515
  // People API uses fully-qualified resource names ("people/c123") not bare IDs.
23467
23516
  person: external_exports.string().describe("Person resource name (people/...) or email")
23468
23517
  };
23518
+ var pageTokenParam = external_exports.string().optional().describe(
23519
+ "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."
23520
+ );
23521
+ var pageAliasParam = external_exports.string().optional().describe(
23522
+ "Deprecated alias for pageToken, accepted so existing callers keep working. Use pageToken \u2014 it matches the nextPageToken field in the response."
23523
+ );
23524
+ function resolvePageToken(p) {
23525
+ return p.pageToken ?? p.page;
23526
+ }
23469
23527
  var paginationParams = {
23470
23528
  max: external_exports.number().int().optional().describe("Max results"),
23471
- page: external_exports.string().optional().describe("Page token"),
23529
+ pageToken: pageTokenParam,
23530
+ page: pageAliasParam,
23472
23531
  all: external_exports.boolean().optional().describe("Fetch all pages")
23473
23532
  };
23474
23533
  function pushPaginationFlags(args, p) {
23475
23534
  if (p.max !== void 0) args.push(`--max=${p.max}`);
23476
- if (p.page) args.push(`--page=${p.page}`);
23535
+ const token = resolvePageToken(p);
23536
+ if (token) args.push(`--page=${token}`);
23477
23537
  if (p.all) args.push("--all");
23478
23538
  }
23479
23539
  function registerRunTool(server, options) {
@@ -23548,7 +23608,7 @@ ${accounts || "(none)"}${hint}`);
23548
23608
  async function runOrDiagnose(args, options) {
23549
23609
  try {
23550
23610
  const raw = await run(args, options);
23551
- return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
23611
+ return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
23552
23612
  } catch (err) {
23553
23613
  return diagnose(err);
23554
23614
  }
@@ -23779,7 +23839,7 @@ function authToolsFor(defaultServices) {
23779
23839
  // src/tools/calendar.ts
23780
23840
  function registerCalendarTools(server) {
23781
23841
  server.registerTool("gog_calendar_events", {
23782
- description: "List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today).",
23842
+ 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.',
23783
23843
  annotations: { readOnlyHint: true },
23784
23844
  inputSchema: {
23785
23845
  calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
@@ -23787,22 +23847,29 @@ function registerCalendarTools(server) {
23787
23847
  to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
23788
23848
  today: external_exports.boolean().optional().describe("Only show today's events"),
23789
23849
  query: external_exports.string().optional().describe("Free text search within events"),
23790
- all: external_exports.boolean().optional().describe("Fetch events from all calendars"),
23850
+ 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."),
23851
+ pageToken: pageTokenParam,
23852
+ page: pageAliasParam,
23853
+ 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.'),
23791
23854
  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)"),
23792
23855
  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.`),
23793
23856
  account: accountParam
23794
23857
  }
23795
- }, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
23858
+ }, async ({ calendarId, from, to, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
23796
23859
  const args = ["calendar", "events"];
23797
23860
  if (calendarId) args.push(calendarId);
23798
23861
  if (from) args.push(`--from=${from}`);
23799
23862
  if (to) args.push(`--to=${to}`);
23800
23863
  if (today) args.push("--today");
23801
23864
  if (query) args.push(`--query=${query}`);
23865
+ if (max !== void 0) args.push(`--max=${max}`);
23866
+ const token = resolvePageToken({ pageToken, page });
23867
+ if (token) args.push(`--page=${token}`);
23802
23868
  if (all) args.push("--all");
23803
23869
  if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
23804
23870
  if (timezone) args.push(`--timezone=${timezone}`);
23805
- return runOrDiagnose(args, { account });
23871
+ const result = await runOrDiagnose(args, { account });
23872
+ return annotateTruncatedList(result, "events");
23806
23873
  });
23807
23874
  server.registerTool("gog_calendar_get", {
23808
23875
  description: "Get a specific calendar event by ID.",
@@ -23919,17 +23986,19 @@ function registerClassroomTools(server) {
23919
23986
  teacher: external_exports.string().optional().describe("Filter by teacher user ID"),
23920
23987
  student: external_exports.string().optional().describe("Filter by student user ID"),
23921
23988
  max: external_exports.number().optional().describe("Max results per page"),
23922
- page: external_exports.string().optional().describe("Page token for pagination"),
23989
+ pageToken: pageTokenParam,
23990
+ page: pageAliasParam,
23923
23991
  all: external_exports.boolean().optional().describe("Fetch all pages"),
23924
23992
  account: accountParam
23925
23993
  }
23926
- }, async ({ state, teacher, student, max, page, all, account }) => {
23994
+ }, async ({ state, teacher, student, max, pageToken, page, all, account }) => {
23927
23995
  const args = ["classroom", "courses", "list"];
23928
23996
  if (state) args.push(`--state=${state}`);
23929
23997
  if (teacher) args.push(`--teacher=${teacher}`);
23930
23998
  if (student) args.push(`--student=${student}`);
23931
23999
  if (max !== void 0) args.push(`--max=${max}`);
23932
- if (page) args.push(`--page=${page}`);
24000
+ const token = resolvePageToken({ pageToken, page });
24001
+ if (token) args.push(`--page=${token}`);
23933
24002
  if (all) args.push("--all");
23934
24003
  return runOrDiagnose(args, { account });
23935
24004
  });
@@ -23949,14 +24018,16 @@ function registerClassroomTools(server) {
23949
24018
  inputSchema: {
23950
24019
  courseId: external_exports.string().describe("Course ID"),
23951
24020
  max: external_exports.number().optional().describe("Max results per page"),
23952
- page: external_exports.string().optional().describe("Page token"),
24021
+ pageToken: pageTokenParam,
24022
+ page: pageAliasParam,
23953
24023
  all: external_exports.boolean().optional().describe("Fetch all pages"),
23954
24024
  account: accountParam
23955
24025
  }
23956
- }, async ({ courseId, max, page, all, account }) => {
24026
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
23957
24027
  const args = ["classroom", "students", "list", courseId];
23958
24028
  if (max !== void 0) args.push(`--max=${max}`);
23959
- if (page) args.push(`--page=${page}`);
24029
+ const token = resolvePageToken({ pageToken, page });
24030
+ if (token) args.push(`--page=${token}`);
23960
24031
  if (all) args.push("--all");
23961
24032
  return runOrDiagnose(args, { account });
23962
24033
  });
@@ -23977,14 +24048,16 @@ function registerClassroomTools(server) {
23977
24048
  inputSchema: {
23978
24049
  courseId: external_exports.string().describe("Course ID"),
23979
24050
  max: external_exports.number().optional().describe("Max results per page"),
23980
- page: external_exports.string().optional().describe("Page token"),
24051
+ pageToken: pageTokenParam,
24052
+ page: pageAliasParam,
23981
24053
  all: external_exports.boolean().optional().describe("Fetch all pages"),
23982
24054
  account: accountParam
23983
24055
  }
23984
- }, async ({ courseId, max, page, all, account }) => {
24056
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
23985
24057
  const args = ["classroom", "teachers", "list", courseId];
23986
24058
  if (max !== void 0) args.push(`--max=${max}`);
23987
- if (page) args.push(`--page=${page}`);
24059
+ const token = resolvePageToken({ pageToken, page });
24060
+ if (token) args.push(`--page=${token}`);
23988
24061
  if (all) args.push("--all");
23989
24062
  return runOrDiagnose(args, { account });
23990
24063
  });
@@ -24007,16 +24080,18 @@ function registerClassroomTools(server) {
24007
24080
  students: external_exports.boolean().optional().describe("Include students only"),
24008
24081
  teachers: external_exports.boolean().optional().describe("Include teachers only"),
24009
24082
  max: external_exports.number().optional().describe("Max results per page"),
24010
- page: external_exports.string().optional().describe("Page token"),
24083
+ pageToken: pageTokenParam,
24084
+ page: pageAliasParam,
24011
24085
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24012
24086
  account: accountParam
24013
24087
  }
24014
- }, async ({ courseId, students, teachers, max, page, all, account }) => {
24088
+ }, async ({ courseId, students, teachers, max, pageToken, page, all, account }) => {
24015
24089
  const args = ["classroom", "roster", courseId];
24016
24090
  if (students) args.push("--students");
24017
24091
  if (teachers) args.push("--teachers");
24018
24092
  if (max !== void 0) args.push(`--max=${max}`);
24019
- if (page) args.push(`--page=${page}`);
24093
+ const token = resolvePageToken({ pageToken, page });
24094
+ if (token) args.push(`--page=${token}`);
24020
24095
  if (all) args.push("--all");
24021
24096
  return runOrDiagnose(args, { account });
24022
24097
  });
@@ -24029,18 +24104,20 @@ function registerClassroomTools(server) {
24029
24104
  topic: external_exports.string().optional().describe("Filter by topic ID"),
24030
24105
  orderBy: external_exports.string().optional().describe('Sort order (e.g. "updateTime desc")'),
24031
24106
  max: external_exports.number().optional().describe("Max results per page"),
24032
- page: external_exports.string().optional().describe("Page token"),
24107
+ pageToken: pageTokenParam,
24108
+ page: pageAliasParam,
24033
24109
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24034
24110
  scanPages: external_exports.number().optional().describe("Max pages to scan when filtering"),
24035
24111
  account: accountParam
24036
24112
  }
24037
- }, async ({ courseId, state, topic, orderBy, max, page, all, scanPages, account }) => {
24113
+ }, async ({ courseId, state, topic, orderBy, max, pageToken, page, all, scanPages, account }) => {
24038
24114
  const args = ["classroom", "coursework", "list", courseId];
24039
24115
  if (state) args.push(`--state=${state}`);
24040
24116
  if (topic) args.push(`--topic=${topic}`);
24041
24117
  if (orderBy) args.push(`--order-by=${orderBy}`);
24042
24118
  if (max !== void 0) args.push(`--max=${max}`);
24043
- if (page) args.push(`--page=${page}`);
24119
+ const token = resolvePageToken({ pageToken, page });
24120
+ if (token) args.push(`--page=${token}`);
24044
24121
  if (all) args.push("--all");
24045
24122
  if (scanPages !== void 0) args.push(`--scan-pages=${scanPages}`);
24046
24123
  return runOrDiagnose(args, { account });
@@ -24066,17 +24143,19 @@ function registerClassroomTools(server) {
24066
24143
  late: external_exports.enum(["late", "not-late"]).optional().describe("Filter by late status"),
24067
24144
  user: external_exports.string().optional().describe("Filter by student user ID"),
24068
24145
  max: external_exports.number().optional().describe("Max results per page"),
24069
- page: external_exports.string().optional().describe("Page token"),
24146
+ pageToken: pageTokenParam,
24147
+ page: pageAliasParam,
24070
24148
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24071
24149
  account: accountParam
24072
24150
  }
24073
- }, async ({ courseId, courseworkId, state, late, user, max, page, all, account }) => {
24151
+ }, async ({ courseId, courseworkId, state, late, user, max, pageToken, page, all, account }) => {
24074
24152
  const args = ["classroom", "submissions", "list", courseId, courseworkId];
24075
24153
  if (state) args.push(`--state=${state}`);
24076
24154
  if (late) args.push(`--late=${late}`);
24077
24155
  if (user) args.push(`--user=${user}`);
24078
24156
  if (max !== void 0) args.push(`--max=${max}`);
24079
- if (page) args.push(`--page=${page}`);
24157
+ const token = resolvePageToken({ pageToken, page });
24158
+ if (token) args.push(`--page=${token}`);
24080
24159
  if (all) args.push("--all");
24081
24160
  return runOrDiagnose(args, { account });
24082
24161
  });
@@ -24153,16 +24232,18 @@ function registerClassroomTools(server) {
24153
24232
  state: external_exports.string().optional().describe("Filter by announcement state"),
24154
24233
  orderBy: external_exports.string().optional().describe("Sort order"),
24155
24234
  max: external_exports.number().optional().describe("Max results per page"),
24156
- page: external_exports.string().optional().describe("Page token"),
24235
+ pageToken: pageTokenParam,
24236
+ page: pageAliasParam,
24157
24237
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24158
24238
  account: accountParam
24159
24239
  }
24160
- }, async ({ courseId, state, orderBy, max, page, all, account }) => {
24240
+ }, async ({ courseId, state, orderBy, max, pageToken, page, all, account }) => {
24161
24241
  const args = ["classroom", "announcements", "list", courseId];
24162
24242
  if (state) args.push(`--state=${state}`);
24163
24243
  if (orderBy) args.push(`--order-by=${orderBy}`);
24164
24244
  if (max !== void 0) args.push(`--max=${max}`);
24165
- if (page) args.push(`--page=${page}`);
24245
+ const token = resolvePageToken({ pageToken, page });
24246
+ if (token) args.push(`--page=${token}`);
24166
24247
  if (all) args.push("--all");
24167
24248
  return runOrDiagnose(args, { account });
24168
24249
  });
@@ -24198,14 +24279,16 @@ function registerClassroomTools(server) {
24198
24279
  inputSchema: {
24199
24280
  courseId: external_exports.string().describe("Course ID"),
24200
24281
  max: external_exports.number().optional().describe("Max results per page"),
24201
- page: external_exports.string().optional().describe("Page token"),
24282
+ pageToken: pageTokenParam,
24283
+ page: pageAliasParam,
24202
24284
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24203
24285
  account: accountParam
24204
24286
  }
24205
- }, async ({ courseId, max, page, all, account }) => {
24287
+ }, async ({ courseId, max, pageToken, page, all, account }) => {
24206
24288
  const args = ["classroom", "topics", "list", courseId];
24207
24289
  if (max !== void 0) args.push(`--max=${max}`);
24208
- if (page) args.push(`--page=${page}`);
24290
+ const token = resolvePageToken({ pageToken, page });
24291
+ if (token) args.push(`--page=${token}`);
24209
24292
  if (all) args.push("--all");
24210
24293
  return runOrDiagnose(args, { account });
24211
24294
  });
@@ -24227,16 +24310,18 @@ function registerClassroomTools(server) {
24227
24310
  course: external_exports.string().optional().describe("Filter by course ID"),
24228
24311
  user: external_exports.string().optional().describe("Filter by user ID"),
24229
24312
  max: external_exports.number().optional().describe("Max results per page"),
24230
- page: external_exports.string().optional().describe("Page token"),
24313
+ pageToken: pageTokenParam,
24314
+ page: pageAliasParam,
24231
24315
  all: external_exports.boolean().optional().describe("Fetch all pages"),
24232
24316
  account: accountParam
24233
24317
  }
24234
- }, async ({ course, user, max, page, all, account }) => {
24318
+ }, async ({ course, user, max, pageToken, page, all, account }) => {
24235
24319
  const args = ["classroom", "invitations", "list"];
24236
24320
  if (course) args.push(`--course=${course}`);
24237
24321
  if (user) args.push(`--user=${user}`);
24238
24322
  if (max !== void 0) args.push(`--max=${max}`);
24239
- if (page) args.push(`--page=${page}`);
24323
+ const token = resolvePageToken({ pageToken, page });
24324
+ if (token) args.push(`--page=${token}`);
24240
24325
  if (all) args.push("--all");
24241
24326
  return runOrDiagnose(args, { account });
24242
24327
  });
@@ -24446,16 +24531,18 @@ function registerDriveTools(server) {
24446
24531
  inputSchema: {
24447
24532
  folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
24448
24533
  max: external_exports.number().optional().describe("Max results (default: 20)"),
24449
- page: external_exports.string().optional().describe("Page token for pagination"),
24534
+ pageToken: pageTokenParam,
24535
+ page: pageAliasParam,
24450
24536
  query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
24451
24537
  allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
24452
24538
  account: accountParam
24453
24539
  }
24454
- }, async ({ folderId, max, page, query, allDrives, account }) => {
24540
+ }, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
24455
24541
  const args = ["drive", "ls"];
24456
24542
  if (folderId) args.push(`--parent=${folderId}`);
24457
24543
  if (max !== void 0) args.push(`--max=${max}`);
24458
- if (page) args.push(`--page=${page}`);
24544
+ const token = resolvePageToken({ pageToken, page });
24545
+ if (token) args.push(`--page=${token}`);
24459
24546
  if (query) args.push(`--query=${query}`);
24460
24547
  if (allDrives === false) args.push("--no-all-drives");
24461
24548
  return runOrDiagnose(args, { account });
@@ -24645,22 +24732,130 @@ function registerDriveTools(server) {
24645
24732
  registerRunTool(server, { service: "drive", examples: '"copy", "upload", "download", "permissions"' });
24646
24733
  }
24647
24734
 
24735
+ // src/gmail-results.ts
24736
+ function sortKey(item) {
24737
+ for (const raw of [item.internalDateIso, item.date]) {
24738
+ if (typeof raw !== "string" || !raw) continue;
24739
+ const t = Date.parse(raw);
24740
+ if (!Number.isNaN(t)) return t;
24741
+ }
24742
+ return Number.NEGATIVE_INFINITY;
24743
+ }
24744
+ function sortNewestFirst(items) {
24745
+ return [...items].sort((a, b) => {
24746
+ const ka = sortKey(a);
24747
+ const kb = sortKey(b);
24748
+ return ka === kb ? 0 : kb - ka;
24749
+ });
24750
+ }
24751
+ var COUNT_PROBE_PAGE_SIZE = 500;
24752
+ async function countMatches(method, itemsKey, query, account) {
24753
+ try {
24754
+ const params = JSON.stringify({
24755
+ userId: "me",
24756
+ q: query,
24757
+ maxResults: COUNT_PROBE_PAGE_SIZE,
24758
+ fields: `${itemsKey}/id,nextPageToken`
24759
+ });
24760
+ const raw = await run(["api", "call", "gmail", "v1", method, `--params=${params}`], { account });
24761
+ const parsed = JSON.parse(raw);
24762
+ const items = parsed[itemsKey];
24763
+ if (!Array.isArray(items)) return {};
24764
+ const more = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
24765
+ return more ? { atLeast: items.length } : { total: items.length };
24766
+ } catch {
24767
+ return {};
24768
+ }
24769
+ }
24770
+ async function finalizeGmailSearch(result, options) {
24771
+ const { itemsKey, method, query, account, queryIsExact = true } = options;
24772
+ const first = result.content[0];
24773
+ if (result.isError || first?.type !== "text") return result;
24774
+ let parsed;
24775
+ try {
24776
+ parsed = JSON.parse(first.text);
24777
+ } catch {
24778
+ return result;
24779
+ }
24780
+ const items = parsed[itemsKey];
24781
+ if (!Array.isArray(items)) return result;
24782
+ const sorted = sortNewestFirst(items);
24783
+ const out = { ...parsed, [itemsKey]: sorted };
24784
+ if (hasMorePages(parsed)) {
24785
+ const count = queryIsExact ? await countMatches(method, itemsKey, query, account) : {};
24786
+ annotateTruncation(out, sorted.length, count);
24787
+ }
24788
+ return rawTextResult(JSON.stringify(out));
24789
+ }
24790
+ async function fetchGmailPages(runPage, itemsKey, maxPages, startToken) {
24791
+ const merged = [];
24792
+ let base;
24793
+ let token = startToken;
24794
+ for (let pages = 0; pages < maxPages; pages++) {
24795
+ const result = await runPage(token);
24796
+ const parsed = parsePage(result, itemsKey);
24797
+ if (parsed === void 0) {
24798
+ return base === void 0 ? result : finish(base, itemsKey, merged, token);
24799
+ }
24800
+ base = parsed;
24801
+ merged.push(...parsed[itemsKey]);
24802
+ token = typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "" ? parsed.nextPageToken : void 0;
24803
+ if (token === void 0) break;
24804
+ }
24805
+ return finish(base, itemsKey, merged, token);
24806
+ }
24807
+ function parsePage(result, itemsKey) {
24808
+ const first = result.content[0];
24809
+ if (result.isError || first?.type !== "text") return void 0;
24810
+ let parsed;
24811
+ try {
24812
+ parsed = JSON.parse(first.text);
24813
+ } catch {
24814
+ return void 0;
24815
+ }
24816
+ if (parsed === null || typeof parsed !== "object") return void 0;
24817
+ const obj = parsed;
24818
+ return Array.isArray(obj[itemsKey]) ? obj : void 0;
24819
+ }
24820
+ function finish(base, itemsKey, merged, token) {
24821
+ const out = { ...base, [itemsKey]: merged };
24822
+ if (token === void 0) delete out.nextPageToken;
24823
+ else out.nextPageToken = token;
24824
+ return rawTextResult(JSON.stringify(out));
24825
+ }
24826
+
24648
24827
  // src/tools/gmail.ts
24649
24828
  function registerGmailTools(server) {
24650
24829
  server.registerTool("gog_gmail_search", {
24651
- 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).`,
24830
+ 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.',
24652
24831
  annotations: { readOnlyHint: true },
24653
24832
  inputSchema: {
24654
24833
  query: external_exports.string().describe("Gmail search query"),
24655
24834
  max: external_exports.number().int().optional().describe("Max results to return (default: 10)"),
24835
+ pageToken: pageTokenParam,
24836
+ page: pageAliasParam,
24837
+ 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.'),
24838
+ 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.'),
24656
24839
  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."),
24657
24840
  account: accountParam
24658
24841
  }
24659
- }, async ({ query, max, fromContact, account }) => {
24842
+ }, async ({ query, max, pageToken, page, maxPages, all, fromContact, account }) => {
24660
24843
  const args = ["gmail", "search", query];
24661
24844
  if (max !== void 0) args.push(`--max=${max}`);
24845
+ if (all) args.push("--all");
24662
24846
  if (fromContact) args.push(`--from-contact=${fromContact}`);
24663
- return runOrDiagnose(args, { account });
24847
+ const runPage = (tok) => runOrDiagnose(tok ? [...args, `--page=${tok}`] : args, { account });
24848
+ const token = resolvePageToken({ pageToken, page });
24849
+ const result = maxPages !== void 0 ? await fetchGmailPages(runPage, "threads", maxPages, token) : await runPage(token);
24850
+ return finalizeGmailSearch(result, {
24851
+ itemsKey: "threads",
24852
+ method: "users.threads.list",
24853
+ query,
24854
+ account,
24855
+ // --from-contact is expanded INSIDE gog, against the People API, so the
24856
+ // query Gmail actually saw is not the one we hold here.
24857
+ queryIsExact: !fromContact
24858
+ });
24664
24859
  });
24665
24860
  server.registerTool("gog_gmail_get", {
24666
24861
  description: "Get a Gmail message by ID.",
@@ -25025,7 +25220,7 @@ function registerTasksTools(server) {
25025
25220
  }
25026
25221
 
25027
25222
  // src/server.ts
25028
- var VERSION = true ? "2.23.1" : "0.0.0";
25223
+ var VERSION = true ? "2.23.2" : "0.0.0";
25029
25224
  var BASE_TOOL_REGISTRARS = [
25030
25225
  registerApiTools,
25031
25226
  registerAuthTools,
@@ -25529,12 +25724,17 @@ export {
25529
25724
  PAYLOAD_INLINE_MAX,
25530
25725
  VERSION,
25531
25726
  accountParam,
25727
+ annotateTruncatedList,
25532
25728
  authToolsFor,
25533
25729
  diagnose,
25534
25730
  errorText,
25731
+ fetchGmailPages,
25732
+ finalizeGmailSearch,
25535
25733
  ids,
25536
25734
  isGogFileArg,
25537
25735
  normalizeTimestamps,
25736
+ pageAliasParam,
25737
+ pageTokenParam,
25538
25738
  paginationParams,
25539
25739
  payloadArg,
25540
25740
  pushPaginationFlags,
@@ -25550,9 +25750,11 @@ export {
25550
25750
  registerSheetsTools,
25551
25751
  registerSlidesTools,
25552
25752
  registerTasksTools,
25753
+ resolvePageToken,
25553
25754
  run,
25554
25755
  runBinary,
25555
25756
  runExecutor,
25556
25757
  runOrDiagnose,
25758
+ stripConsumedPageToken,
25557
25759
  useRemoteGogRunner
25558
25760
  };
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp",
5
5
  "display_name": "gogcli",
6
- "version": "2.23.1",
6
+ "version": "2.23.2",
7
7
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -102,7 +102,7 @@
102
102
  },
103
103
  {
104
104
  "name": "gog_gmail_search",
105
- "description": "Search Gmail threads by query"
105
+ "description": "Search Gmail threads using Gmail query syntax; newest-first, and flags a truncated result set"
106
106
  },
107
107
  {
108
108
  "name": "gog_gmail_get",
@@ -118,7 +118,7 @@
118
118
  },
119
119
  {
120
120
  "name": "gog_calendar_events",
121
- "description": "List calendar events with optional filters"
121
+ "description": "List calendar events; paginated (gog returns only 10 by default) and flags a truncated range"
122
122
  },
123
123
  {
124
124
  "name": "gog_calendar_get",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
- "version": "2.23.1",
3
+ "version": "2.23.2",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp",
5
5
  "description": "MCP server wrapping gogcli for Google service access",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp"
9
9
  },
10
- "version": "2.23.1",
10
+ "version": "2.23.2",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp",
15
- "version": "2.23.1",
15
+ "version": "2.23.2",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },