gogcli-mcp-calendar 2.23.1 → 2.24.0

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/index.js CHANGED
@@ -31542,6 +31542,55 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
31542
31542
  return JSON.stringify(parsed, null, detectIndent(text));
31543
31543
  }
31544
31544
 
31545
+ // ../gogcli-mcp/src/pagination.ts
31546
+ function detectIndent2(text) {
31547
+ const match = /\n(\s+)\S/.exec(text);
31548
+ return match ? match[1].replace(/\t/g, " ").length : 0;
31549
+ }
31550
+ function stripConsumedPageToken(text) {
31551
+ const trimmed = text.trim();
31552
+ if (trimmed === "" || !trimmed.startsWith("{")) return text;
31553
+ let parsed;
31554
+ try {
31555
+ parsed = JSON.parse(trimmed);
31556
+ } catch {
31557
+ return text;
31558
+ }
31559
+ const obj = parsed;
31560
+ if (obj.nextPageToken !== "") return text;
31561
+ delete obj.nextPageToken;
31562
+ return JSON.stringify(obj, null, detectIndent2(text));
31563
+ }
31564
+ function truncationWarning(returned, count) {
31565
+ 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`;
31566
+ return `INCOMPLETE RESULT SET: ${scope}. Do not report an absence of results based on this response. Page with nextPageToken or narrow the query.`;
31567
+ }
31568
+ function annotateTruncation(out, returned, count) {
31569
+ out.truncated = true;
31570
+ out.returned = returned;
31571
+ if (count.total !== void 0) out.totalMatches = count.total;
31572
+ if (count.atLeast !== void 0) out.totalMatchesAtLeast = count.atLeast;
31573
+ out.warning = truncationWarning(returned, count);
31574
+ }
31575
+ function hasMorePages(parsed) {
31576
+ return typeof parsed.nextPageToken === "string" && parsed.nextPageToken !== "";
31577
+ }
31578
+ function annotateTruncatedList(result, itemsKey) {
31579
+ const first = result.content[0];
31580
+ if (result.isError || first?.type !== "text") return result;
31581
+ let parsed;
31582
+ try {
31583
+ parsed = JSON.parse(first.text);
31584
+ } catch {
31585
+ return result;
31586
+ }
31587
+ const items = parsed[itemsKey];
31588
+ if (!Array.isArray(items) || !hasMorePages(parsed)) return result;
31589
+ const out = { ...parsed };
31590
+ annotateTruncation(out, items.length, {});
31591
+ return rawTextResult(JSON.stringify(out));
31592
+ }
31593
+
31545
31594
  // ../gogcli-mcp/src/tools/utils.ts
31546
31595
  var accountParam = external_exports.string().optional().describe(
31547
31596
  "Google account email to use, e.g. you@gmail.com \u2014 must be the full address, not a bare username. Overrides the GOG_ACCOUNT env var. Omit to use the single configured account."
@@ -31570,9 +31619,19 @@ var ids = {
31570
31619
  // People API uses fully-qualified resource names ("people/c123") not bare IDs.
31571
31620
  person: external_exports.string().describe("Person resource name (people/...) or email")
31572
31621
  };
31622
+ var pageTokenParam = external_exports.string().optional().describe(
31623
+ "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."
31624
+ );
31625
+ var pageAliasParam = external_exports.string().optional().describe(
31626
+ "Deprecated alias for pageToken, accepted so existing callers keep working. Use pageToken \u2014 it matches the nextPageToken field in the response."
31627
+ );
31628
+ function resolvePageToken(p) {
31629
+ return p.pageToken ?? p.page;
31630
+ }
31573
31631
  var paginationParams = {
31574
31632
  max: external_exports.number().int().optional().describe("Max results"),
31575
- page: external_exports.string().optional().describe("Page token"),
31633
+ pageToken: pageTokenParam,
31634
+ page: pageAliasParam,
31576
31635
  all: external_exports.boolean().optional().describe("Fetch all pages")
31577
31636
  };
31578
31637
  function registerRunTool(server, options) {
@@ -31647,7 +31706,7 @@ ${accounts || "(none)"}${hint}`);
31647
31706
  async function runOrDiagnose(args, options) {
31648
31707
  try {
31649
31708
  const raw = await run(args, options);
31650
- return rawTextResult(options.lossless ? raw : normalizeTimestamps(raw));
31709
+ return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
31651
31710
  } catch (err) {
31652
31711
  return diagnose(err);
31653
31712
  }
@@ -31697,6 +31756,7 @@ function formatAuthHealth(raw, now) {
31697
31756
  // ../gogcli-mcp/src/tools/auth.ts
31698
31757
  function registerAuthToolsWith(server, defaultServices) {
31699
31758
  const servicesDescribe = `Services to authorize: "all" or comma-separated list (e.g. "sheets,gmail,calendar"). Default: "${defaultServices}". Prefer the narrowest set you need \u2014 requesting a service whose Google API is not enabled on the OAuth client's project makes Google reject the WHOLE request with invalid_scope.`;
31759
+ const extraScopesDescribe = "Additional raw OAuth scope URIs to request, comma-separated, on top of the ones `services` implies. Use for scopes no service covers \u2014 e.g. https://www.googleapis.com/auth/bigquery.readonly, required before gog_sheets_datasource_* can read BigQuery-backed Connected Sheets. Leave unset otherwise: an extra scope whose API is not enabled on the OAuth client project makes Google reject the WHOLE authorization with invalid_scope.";
31700
31760
  server.registerTool("gog_auth_list", {
31701
31761
  description: "List the Google accounts stored in gogcli, with their scopes. This reads local configuration only \u2014 it does not contact Google and does NOT tell you whether an account still works: a signed-out account whose refresh token expired or was revoked is listed here exactly like a healthy one, scopes and all. Use gog_auth_health to check whether an account can actually authenticate.",
31702
31762
  annotations: { readOnlyHint: true },
@@ -31746,11 +31806,14 @@ function registerAuthToolsWith(server, defaultServices) {
31746
31806
  annotations: { destructiveHint: true },
31747
31807
  inputSchema: {
31748
31808
  email: external_exports.string().describe("Google account email to authorize"),
31749
- services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
31809
+ services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
31810
+ extraScopes: external_exports.string().optional().describe(extraScopesDescribe)
31750
31811
  }
31751
- }, async ({ email: email3, services = defaultServices }) => {
31812
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
31752
31813
  try {
31753
- return rawTextResult(await run(["auth", "add", email3, "--services", services], {
31814
+ const args = ["auth", "add", email3, "--services", services];
31815
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
31816
+ return rawTextResult(await run(args, {
31754
31817
  interactive: true,
31755
31818
  timeout: 3e5
31756
31819
  }));
@@ -31762,14 +31825,14 @@ function registerAuthToolsWith(server, defaultServices) {
31762
31825
  description: "Begin REMOTE/headless Google authorization (step 1 of 2). Returns a sign-in URL to open in any browser \u2014 no local server or terminal on the gogcli host is needed, so this works over the hosted connector where the interactive gog_auth_add cannot. Hand the URL to the user; after they sign in, the browser is redirected to a localhost URL that fails to load \u2014 that is expected. They copy that full redirected URL (from the address bar) and you pass it to gog_auth_add_complete. The link is valid for 10 minutes. If you pass a custom `services` here, pass the SAME value to gog_auth_add_complete or the second step will not match this one.",
31763
31826
  inputSchema: {
31764
31827
  email: external_exports.string().describe("Google account email to authorize"),
31765
- services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
31828
+ services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
31829
+ extraScopes: external_exports.string().optional().describe(`${extraScopesDescribe} Pass the SAME value to gog_auth_add_complete.`)
31766
31830
  }
31767
- }, async ({ email: email3, services = defaultServices }) => {
31831
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
31768
31832
  try {
31769
- return rawTextResult(await run(
31770
- ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"],
31771
- { redactMode: "tokens" }
31772
- ));
31833
+ const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
31834
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
31835
+ return rawTextResult(await run(args, { redactMode: "tokens" }));
31773
31836
  } catch (err) {
31774
31837
  return errorResult(errorText(err));
31775
31838
  }
@@ -31784,25 +31847,28 @@ function registerAuthToolsWith(server, defaultServices) {
31784
31847
  ),
31785
31848
  services: external_exports.string().optional().default(defaultServices).describe(
31786
31849
  `Services authorized \u2014 MUST match the value passed to gog_auth_add_url. Default: "${defaultServices}".`
31850
+ ),
31851
+ extraScopes: external_exports.string().optional().describe(
31852
+ "Extra OAuth scope URIs \u2014 MUST match the value passed to gog_auth_add_url, for the same reason `services` must: the two steps have to describe the same grant."
31787
31853
  )
31788
31854
  }
31789
- }, async ({ email: email3, redirectUrl, services = defaultServices }) => {
31855
+ }, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
31790
31856
  try {
31791
- return rawTextResult(await run(
31792
- [
31793
- "auth",
31794
- "add",
31795
- email3,
31796
- "--remote",
31797
- "--step",
31798
- "2",
31799
- "--auth-url",
31800
- redirectUrl,
31801
- "--services",
31802
- services,
31803
- "--force-consent"
31804
- ]
31805
- ));
31857
+ const args = [
31858
+ "auth",
31859
+ "add",
31860
+ email3,
31861
+ "--remote",
31862
+ "--step",
31863
+ "2",
31864
+ "--auth-url",
31865
+ redirectUrl,
31866
+ "--services",
31867
+ services,
31868
+ "--force-consent"
31869
+ ];
31870
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
31871
+ return rawTextResult(await run(args));
31806
31872
  } catch (err) {
31807
31873
  return errorResult(errorText(err));
31808
31874
  }
@@ -31821,30 +31887,44 @@ function authToolsFor(defaultServices) {
31821
31887
  // ../gogcli-mcp/src/tools/calendar.ts
31822
31888
  function registerCalendarTools(server) {
31823
31889
  server.registerTool("gog_calendar_events", {
31824
- description: "List calendar events. Filters can be combined (e.g. --from + --to for a range, or --today for just today).",
31890
+ description: 'List calendar events. Describe the window ONE way and one way only (gog >= 0.36.0 rejects the rest as ambiguous rather than silently discarding a flag): today on its own; or from + to; or from + days; or days on its own (a window of that many days starting today). today cannot be combined with from, to or days, and days cannot be combined with to. 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.',
31825
31891
  annotations: { readOnlyHint: true },
31826
31892
  inputSchema: {
31827
31893
  calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
31828
31894
  from: external_exports.string().optional().describe("Start time filter (RFC3339, date, or natural language)"),
31829
- to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
31830
- today: external_exports.boolean().optional().describe("Only show today's events"),
31895
+ to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language). Mutually exclusive with today and with days."),
31896
+ // gog >= 0.36.0 (openclaw/gogcli#981). Before that release --days sat in
31897
+ // a switch arm evaluated ahead of --from, so `--from 2026-09-25 --days 5`
31898
+ // silently threw --from away and answered for today instead — at exit 0,
31899
+ // in a well-formed table. It is only exposed here now that it means what
31900
+ // it says.
31901
+ days: external_exports.number().int().positive().optional().describe('Window LENGTH in days (calendar days, DST-aware), measured from `from` when one is given and from today otherwise. Use from + days for "the week of the 25th"; days alone for "the next N days". Mutually exclusive with to and with today.'),
31902
+ today: external_exports.boolean().optional().describe("Only show today's events. A complete window on its own \u2014 mutually exclusive with from, to and days."),
31831
31903
  query: external_exports.string().optional().describe("Free text search within events"),
31832
- all: external_exports.boolean().optional().describe("Fetch events from all calendars"),
31904
+ 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."),
31905
+ pageToken: pageTokenParam,
31906
+ page: pageAliasParam,
31907
+ 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.'),
31833
31908
  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)"),
31834
31909
  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.`),
31835
31910
  account: accountParam
31836
31911
  }
31837
- }, async ({ calendarId, from, to, today, query, all, eventTypes, timezone, account }) => {
31912
+ }, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
31838
31913
  const args = ["calendar", "events"];
31839
31914
  if (calendarId) args.push(calendarId);
31840
31915
  if (from) args.push(`--from=${from}`);
31841
31916
  if (to) args.push(`--to=${to}`);
31917
+ if (days !== void 0) args.push(`--days=${days}`);
31842
31918
  if (today) args.push("--today");
31843
31919
  if (query) args.push(`--query=${query}`);
31920
+ if (max !== void 0) args.push(`--max=${max}`);
31921
+ const token = resolvePageToken({ pageToken, page });
31922
+ if (token) args.push(`--page=${token}`);
31844
31923
  if (all) args.push("--all");
31845
31924
  if (eventTypes) for (const t of eventTypes) args.push(`--event-types=${t}`);
31846
31925
  if (timezone) args.push(`--timezone=${timezone}`);
31847
- return runOrDiagnose(args, { account });
31926
+ const result = await runOrDiagnose(args, { account });
31927
+ return annotateTruncatedList(result, "events");
31848
31928
  });
31849
31929
  server.registerTool("gog_calendar_get", {
31850
31930
  description: "Get a specific calendar event by ID.",
@@ -31961,7 +32041,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
31961
32041
  );
31962
32042
 
31963
32043
  // ../gogcli-mcp/src/server.ts
31964
- var VERSION = true ? "2.23.1" : "0.0.0";
32044
+ var VERSION = true ? "2.24.0" : "0.0.0";
31965
32045
 
31966
32046
  // ../gogcli-mcp/src/auth-log.ts
31967
32047
  var FAILURES = /* @__PURE__ */ new Set([
@@ -32502,14 +32582,16 @@ function registerExtraCalendarTools(server) {
32502
32582
  inputSchema: {
32503
32583
  meetingCode: external_exports.string().describe("Meeting code"),
32504
32584
  max: external_exports.number().optional().describe("Max results (default: 20)"),
32505
- page: external_exports.string().optional().describe("Page token"),
32585
+ pageToken: pageTokenParam,
32586
+ page: pageAliasParam,
32506
32587
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32507
32588
  account: accountParam
32508
32589
  }
32509
- }, async ({ meetingCode, max, page, all, account }) => {
32590
+ }, async ({ meetingCode, max, pageToken, page, all, account }) => {
32510
32591
  const args = ["meet", "history", meetingCode];
32511
32592
  if (max !== void 0) args.push(`--max=${max}`);
32512
- if (page) args.push(`--page=${page}`);
32593
+ const token = resolvePageToken({ pageToken, page });
32594
+ if (token) args.push(`--page=${token}`);
32513
32595
  if (all) args.push("--all");
32514
32596
  return runOrDiagnose(args, { account });
32515
32597
  });
@@ -32548,28 +32630,33 @@ function registerExtraCalendarTools(server) {
32548
32630
  annotations: { readOnlyHint: true },
32549
32631
  inputSchema: {
32550
32632
  max: external_exports.number().optional().describe("Max results (default: 100)"),
32551
- page: external_exports.string().optional().describe("Page token"),
32633
+ pageToken: pageTokenParam,
32634
+ page: pageAliasParam,
32552
32635
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32553
32636
  account: accountParam
32554
32637
  }
32555
- }, async ({ max, page, all, account }) => {
32638
+ }, async ({ max, pageToken, page, all, account }) => {
32556
32639
  const args = ["calendar", "calendars"];
32557
32640
  if (max !== void 0) args.push(`--max=${max}`);
32558
- if (page) args.push(`--page=${page}`);
32641
+ const token = resolvePageToken({ pageToken, page });
32642
+ if (token) args.push(`--page=${token}`);
32559
32643
  if (all) args.push("--all");
32560
32644
  return runOrDiagnose(args, { account });
32561
32645
  });
32562
32646
  server.registerTool("gog_calendar_search", {
32563
- description: "Full-text search for events matching a query string, with optional time filters.",
32647
+ description: "Full-text search for events matching a query string, with optional time filters. Describe the window ONE way only (gog >= 0.36.0 rejects the rest as ambiguous instead of discarding a flag): one of today / tomorrow / week on its own, or from + to, or from + days, or days on its own. The fixed presets cannot be combined with from, to or days, and days cannot be combined with to.",
32564
32648
  annotations: { readOnlyHint: true },
32565
32649
  inputSchema: {
32566
32650
  query: external_exports.string().describe("Search query"),
32567
32651
  from: external_exports.string().optional().describe("Start time (RFC3339, date, or relative: now, today, tomorrow, monday)"),
32568
- to: external_exports.string().optional().describe("End time (RFC3339, date, or relative: now, today, tomorrow, monday)"),
32569
- today: external_exports.boolean().optional().describe("Today only"),
32570
- tomorrow: external_exports.boolean().optional().describe("Tomorrow only"),
32571
- week: external_exports.boolean().optional().describe("This week (uses weekStart, default Mon)"),
32572
- days: external_exports.number().optional().describe("Next N days"),
32652
+ to: external_exports.string().optional().describe("End time (RFC3339, date, or relative: now, today, tomorrow, monday). Mutually exclusive with days."),
32653
+ today: external_exports.boolean().optional().describe("Today only. A complete window on its own \u2014 not combinable with from/to/days."),
32654
+ tomorrow: external_exports.boolean().optional().describe("Tomorrow only. A complete window on its own \u2014 not combinable with from/to/days."),
32655
+ week: external_exports.boolean().optional().describe("This week (uses weekStart, default Mon). A complete window on its own \u2014 not combinable with from/to/days."),
32656
+ // gog >= 0.36.0 (openclaw/gogcli#981) anchors --days at --from. It used
32657
+ // to mean "next N days from today" no matter what --from said, which is
32658
+ // why the old description here read that way.
32659
+ days: external_exports.number().optional().describe('Window LENGTH in days, measured from `from` when one is given and from today otherwise \u2014 NOT always "the next N days".'),
32573
32660
  weekStart: external_exports.string().optional().describe("Week start day for week (sun, mon, ...)"),
32574
32661
  calendar: external_exports.string().optional().describe("Calendar ID (default: primary)"),
32575
32662
  max: external_exports.number().optional().describe("Max results (default: 25)"),
@@ -32641,14 +32728,16 @@ function registerExtraCalendarTools(server) {
32641
32728
  inputSchema: {
32642
32729
  calendarId: external_exports.string().describe("Calendar ID"),
32643
32730
  max: external_exports.number().optional().describe("Max results (default: 100)"),
32644
- page: external_exports.string().optional().describe("Page token"),
32731
+ pageToken: pageTokenParam,
32732
+ page: pageAliasParam,
32645
32733
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32646
32734
  account: accountParam
32647
32735
  }
32648
- }, async ({ calendarId, max, page, all, account }) => {
32736
+ }, async ({ calendarId, max, pageToken, page, all, account }) => {
32649
32737
  const args = ["calendar", "acl", calendarId];
32650
32738
  if (max !== void 0) args.push(`--max=${max}`);
32651
- if (page) args.push(`--page=${page}`);
32739
+ const token = resolvePageToken({ pageToken, page });
32740
+ if (token) args.push(`--page=${token}`);
32652
32741
  if (all) args.push("--all");
32653
32742
  return runOrDiagnose(args, { account });
32654
32743
  });
@@ -32716,15 +32805,17 @@ function registerExtraCalendarTools(server) {
32716
32805
  meetingCode: external_exports.string().describe("Meeting code"),
32717
32806
  conference: external_exports.string().optional().describe("Specific conference ID (default: most recent)"),
32718
32807
  max: external_exports.number().optional().describe("Max results (default: 50)"),
32719
- page: external_exports.string().optional().describe("Page token"),
32808
+ pageToken: pageTokenParam,
32809
+ page: pageAliasParam,
32720
32810
  all: external_exports.boolean().optional().describe("Fetch all pages"),
32721
32811
  account: accountParam
32722
32812
  }
32723
- }, async ({ meetingCode, conference, max, page, all, account }) => {
32813
+ }, async ({ meetingCode, conference, max, pageToken, page, all, account }) => {
32724
32814
  const args = ["meet", "participants", meetingCode];
32725
32815
  if (conference) args.push(`--conference=${conference}`);
32726
32816
  if (max !== void 0) args.push(`--max=${max}`);
32727
- if (page) args.push(`--page=${page}`);
32817
+ const token = resolvePageToken({ pageToken, page });
32818
+ if (token) args.push(`--page=${token}`);
32728
32819
  if (all) args.push("--all");
32729
32820
  return runOrDiagnose(args, { account });
32730
32821
  });
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-calendar",
5
5
  "display_name": "gogcli (Calendar)",
6
- "version": "2.23.1",
6
+ "version": "2.24.0",
7
7
  "description": "Extended Google Calendar for Claude via gogcli — auth + Calendar events + Meet space management",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -88,7 +88,7 @@
88
88
  },
89
89
  {
90
90
  "name": "gog_calendar_events",
91
- "description": "List calendar events with optional filters"
91
+ "description": "List calendar events; paginated (gog returns only 10 by default) and flags a truncated range"
92
92
  },
93
93
  {
94
94
  "name": "gog_calendar_get",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-calendar",
3
- "version": "2.23.1",
3
+ "version": "2.24.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-calendar",
5
5
  "description": "Extended Google Calendar + Meet MCP server via gogcli — auth + Calendar events + Meet space management",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -1,6 +1,6 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
- import { accountParam, runOrDiagnose } from '../../../gogcli-mcp/src/lib.js';
3
+ import { accountParam, runOrDiagnose, pageTokenParam, pageAliasParam, resolvePageToken} from '../../../gogcli-mcp/src/lib.js';
4
4
 
5
5
  const meetAccess = z.enum(['open', 'trusted', 'restricted']);
6
6
 
@@ -63,14 +63,16 @@ export function registerExtraCalendarTools(server: McpServer): void {
63
63
  inputSchema: {
64
64
  meetingCode: z.string().describe('Meeting code'),
65
65
  max: z.number().optional().describe('Max results (default: 20)'),
66
- page: z.string().optional().describe('Page token'),
66
+ pageToken: pageTokenParam,
67
+ page: pageAliasParam,
67
68
  all: z.boolean().optional().describe('Fetch all pages'),
68
69
  account: accountParam,
69
70
  },
70
- }, async ({ meetingCode, max, page, all, account }) => {
71
+ }, async ({ meetingCode, max, pageToken, page, all, account }) => {
71
72
  const args = ['meet', 'history', meetingCode];
72
73
  if (max !== undefined) args.push(`--max=${max}`);
73
- if (page) args.push(`--page=${page}`);
74
+ const token = resolvePageToken({ pageToken, page });
75
+ if (token) args.push(`--page=${token}`);
74
76
  if (all) args.push('--all');
75
77
  return runOrDiagnose(args, { account });
76
78
  });
@@ -117,29 +119,36 @@ export function registerExtraCalendarTools(server: McpServer): void {
117
119
  annotations: { readOnlyHint: true },
118
120
  inputSchema: {
119
121
  max: z.number().optional().describe('Max results (default: 100)'),
120
- page: z.string().optional().describe('Page token'),
122
+ pageToken: pageTokenParam,
123
+ page: pageAliasParam,
121
124
  all: z.boolean().optional().describe('Fetch all pages'),
122
125
  account: accountParam,
123
126
  },
124
- }, async ({ max, page, all, account }) => {
127
+ }, async ({ max, pageToken, page, all, account }) => {
125
128
  const args = ['calendar', 'calendars'];
126
129
  if (max !== undefined) args.push(`--max=${max}`);
127
- if (page) args.push(`--page=${page}`);
130
+ const token = resolvePageToken({ pageToken, page });
131
+ if (token) args.push(`--page=${token}`);
128
132
  if (all) args.push('--all');
129
133
  return runOrDiagnose(args, { account });
130
134
  });
131
135
 
132
136
  server.registerTool('gog_calendar_search', {
133
- description: 'Full-text search for events matching a query string, with optional time filters.',
137
+ description: 'Full-text search for events matching a query string, with optional time filters. '
138
+ + 'Describe the window ONE way only (gog >= 0.36.0 rejects the rest as ambiguous instead of discarding a flag): one of today / tomorrow / week on its own, '
139
+ + 'or from + to, or from + days, or days on its own. The fixed presets cannot be combined with from, to or days, and days cannot be combined with to.',
134
140
  annotations: { readOnlyHint: true },
135
141
  inputSchema: {
136
142
  query: z.string().describe('Search query'),
137
143
  from: z.string().optional().describe('Start time (RFC3339, date, or relative: now, today, tomorrow, monday)'),
138
- to: z.string().optional().describe('End time (RFC3339, date, or relative: now, today, tomorrow, monday)'),
139
- today: z.boolean().optional().describe('Today only'),
140
- tomorrow: z.boolean().optional().describe('Tomorrow only'),
141
- week: z.boolean().optional().describe('This week (uses weekStart, default Mon)'),
142
- days: z.number().optional().describe('Next N days'),
144
+ to: z.string().optional().describe('End time (RFC3339, date, or relative: now, today, tomorrow, monday). Mutually exclusive with days.'),
145
+ today: z.boolean().optional().describe('Today only. A complete window on its own — not combinable with from/to/days.'),
146
+ tomorrow: z.boolean().optional().describe('Tomorrow only. A complete window on its own — not combinable with from/to/days.'),
147
+ week: z.boolean().optional().describe('This week (uses weekStart, default Mon). A complete window on its own — not combinable with from/to/days.'),
148
+ // gog >= 0.36.0 (openclaw/gogcli#981) anchors --days at --from. It used
149
+ // to mean "next N days from today" no matter what --from said, which is
150
+ // why the old description here read that way.
151
+ days: z.number().optional().describe('Window LENGTH in days, measured from `from` when one is given and from today otherwise — NOT always "the next N days".'),
143
152
  weekStart: z.string().optional().describe('Week start day for week (sun, mon, ...)'),
144
153
  calendar: z.string().optional().describe('Calendar ID (default: primary)'),
145
154
  max: z.number().optional().describe('Max results (default: 25)'),
@@ -215,14 +224,16 @@ export function registerExtraCalendarTools(server: McpServer): void {
215
224
  inputSchema: {
216
225
  calendarId: z.string().describe('Calendar ID'),
217
226
  max: z.number().optional().describe('Max results (default: 100)'),
218
- page: z.string().optional().describe('Page token'),
227
+ pageToken: pageTokenParam,
228
+ page: pageAliasParam,
219
229
  all: z.boolean().optional().describe('Fetch all pages'),
220
230
  account: accountParam,
221
231
  },
222
- }, async ({ calendarId, max, page, all, account }) => {
232
+ }, async ({ calendarId, max, pageToken, page, all, account }) => {
223
233
  const args = ['calendar', 'acl', calendarId];
224
234
  if (max !== undefined) args.push(`--max=${max}`);
225
- if (page) args.push(`--page=${page}`);
235
+ const token = resolvePageToken({ pageToken, page });
236
+ if (token) args.push(`--page=${token}`);
226
237
  if (all) args.push('--all');
227
238
  return runOrDiagnose(args, { account });
228
239
  });
@@ -295,15 +306,17 @@ export function registerExtraCalendarTools(server: McpServer): void {
295
306
  meetingCode: z.string().describe('Meeting code'),
296
307
  conference: z.string().optional().describe('Specific conference ID (default: most recent)'),
297
308
  max: z.number().optional().describe('Max results (default: 50)'),
298
- page: z.string().optional().describe('Page token'),
309
+ pageToken: pageTokenParam,
310
+ page: pageAliasParam,
299
311
  all: z.boolean().optional().describe('Fetch all pages'),
300
312
  account: accountParam,
301
313
  },
302
- }, async ({ meetingCode, conference, max, page, all, account }) => {
314
+ }, async ({ meetingCode, conference, max, pageToken, page, all, account }) => {
303
315
  const args = ['meet', 'participants', meetingCode];
304
316
  if (conference) args.push(`--conference=${conference}`);
305
317
  if (max !== undefined) args.push(`--max=${max}`);
306
- if (page) args.push(`--page=${page}`);
318
+ const token = resolvePageToken({ pageToken, page });
319
+ if (token) args.push(`--page=${token}`);
307
320
  if (all) args.push('--all');
308
321
  return runOrDiagnose(args, { account });
309
322
  });
@@ -172,16 +172,16 @@ describe('gog_calendar_search', () => {
172
172
  expect(lib.runOrDiagnose).toHaveBeenCalledWith(['calendar', 'search', 'standup'], { account: undefined });
173
173
  });
174
174
 
175
- it('passes all filter flags when provided', async () => {
175
+ // One case per LEGAL window, not one case passing every flag at once. gog
176
+ // >= 0.36.0 (openclaw/gogcli#981) rejects a fixed preset alongside
177
+ // from/to/days, and days alongside to, with exit 2 — so the all-flags array
178
+ // this used to assert is one gog will not run, and a mocked test asserting
179
+ // it would keep passing forever while the tool was broken in the field.
180
+ it('passes an explicit from/to range with the non-window filters', async () => {
176
181
  await harness.callTool('gog_calendar_search', {
177
182
  query: 'standup',
178
183
  from: 'today',
179
184
  to: 'tomorrow',
180
- today: true,
181
- tomorrow: true,
182
- week: true,
183
- days: 7,
184
- weekStart: 'sun',
185
185
  calendar: 'primary',
186
186
  max: 10,
187
187
  });
@@ -189,14 +189,39 @@ describe('gog_calendar_search', () => {
189
189
  [
190
190
  'calendar', 'search', 'standup',
191
191
  '--from=today', '--to=tomorrow',
192
- '--today', '--tomorrow', '--week',
193
- '--days=7', '--week-start=sun',
194
192
  '--calendar=primary', '--max=10',
195
193
  ],
196
194
  { account: undefined },
197
195
  );
198
196
  });
199
197
 
198
+ it('anchors --days at --from when both are given', async () => {
199
+ await harness.callTool('gog_calendar_search', { query: 'standup', from: '2026-09-25', days: 7 });
200
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
201
+ ['calendar', 'search', 'standup', '--from=2026-09-25', '--days=7'],
202
+ { account: undefined },
203
+ );
204
+ });
205
+
206
+ it('passes each fixed preset on its own', async () => {
207
+ for (const [param, flag] of [['today', '--today'], ['tomorrow', '--tomorrow'], ['week', '--week']] as const) {
208
+ vi.mocked(lib.runOrDiagnose).mockClear();
209
+ await harness.callTool('gog_calendar_search', { query: 'standup', [param]: true });
210
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
211
+ ['calendar', 'search', 'standup', flag],
212
+ { account: undefined },
213
+ );
214
+ }
215
+ });
216
+
217
+ it('passes --week-start alongside --week', async () => {
218
+ await harness.callTool('gog_calendar_search', { query: 'standup', week: true, weekStart: 'sun' });
219
+ expect(lib.runOrDiagnose).toHaveBeenCalledWith(
220
+ ['calendar', 'search', 'standup', '--week', '--week-start=sun'],
221
+ { account: undefined },
222
+ );
223
+ });
224
+
200
225
  it('omits boolean flags when false', async () => {
201
226
  await harness.callTool('gog_calendar_search', {
202
227
  query: 'standup', today: false, tomorrow: false, week: false,