gogcli-mcp-calendar 2.23.2 → 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
@@ -31756,6 +31756,7 @@ function formatAuthHealth(raw, now) {
31756
31756
  // ../gogcli-mcp/src/tools/auth.ts
31757
31757
  function registerAuthToolsWith(server, defaultServices) {
31758
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.";
31759
31760
  server.registerTool("gog_auth_list", {
31760
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.",
31761
31762
  annotations: { readOnlyHint: true },
@@ -31805,11 +31806,14 @@ function registerAuthToolsWith(server, defaultServices) {
31805
31806
  annotations: { destructiveHint: true },
31806
31807
  inputSchema: {
31807
31808
  email: external_exports.string().describe("Google account email to authorize"),
31808
- 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)
31809
31811
  }
31810
- }, async ({ email: email3, services = defaultServices }) => {
31812
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
31811
31813
  try {
31812
- 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, {
31813
31817
  interactive: true,
31814
31818
  timeout: 3e5
31815
31819
  }));
@@ -31821,14 +31825,14 @@ function registerAuthToolsWith(server, defaultServices) {
31821
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.",
31822
31826
  inputSchema: {
31823
31827
  email: external_exports.string().describe("Google account email to authorize"),
31824
- 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.`)
31825
31830
  }
31826
- }, async ({ email: email3, services = defaultServices }) => {
31831
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
31827
31832
  try {
31828
- return rawTextResult(await run(
31829
- ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"],
31830
- { redactMode: "tokens" }
31831
- ));
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" }));
31832
31836
  } catch (err) {
31833
31837
  return errorResult(errorText(err));
31834
31838
  }
@@ -31843,25 +31847,28 @@ function registerAuthToolsWith(server, defaultServices) {
31843
31847
  ),
31844
31848
  services: external_exports.string().optional().default(defaultServices).describe(
31845
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."
31846
31853
  )
31847
31854
  }
31848
- }, async ({ email: email3, redirectUrl, services = defaultServices }) => {
31855
+ }, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
31849
31856
  try {
31850
- return rawTextResult(await run(
31851
- [
31852
- "auth",
31853
- "add",
31854
- email3,
31855
- "--remote",
31856
- "--step",
31857
- "2",
31858
- "--auth-url",
31859
- redirectUrl,
31860
- "--services",
31861
- services,
31862
- "--force-consent"
31863
- ]
31864
- ));
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));
31865
31872
  } catch (err) {
31866
31873
  return errorResult(errorText(err));
31867
31874
  }
@@ -31880,13 +31887,19 @@ function authToolsFor(defaultServices) {
31880
31887
  // ../gogcli-mcp/src/tools/calendar.ts
31881
31888
  function registerCalendarTools(server) {
31882
31889
  server.registerTool("gog_calendar_events", {
31883
- 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.',
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.',
31884
31891
  annotations: { readOnlyHint: true },
31885
31892
  inputSchema: {
31886
31893
  calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
31887
31894
  from: external_exports.string().optional().describe("Start time filter (RFC3339, date, or natural language)"),
31888
- to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language)"),
31889
- 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."),
31890
31903
  query: external_exports.string().optional().describe("Free text search within events"),
31891
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."),
31892
31905
  pageToken: pageTokenParam,
@@ -31896,11 +31909,12 @@ function registerCalendarTools(server) {
31896
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.`),
31897
31910
  account: accountParam
31898
31911
  }
31899
- }, async ({ calendarId, from, to, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
31912
+ }, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
31900
31913
  const args = ["calendar", "events"];
31901
31914
  if (calendarId) args.push(calendarId);
31902
31915
  if (from) args.push(`--from=${from}`);
31903
31916
  if (to) args.push(`--to=${to}`);
31917
+ if (days !== void 0) args.push(`--days=${days}`);
31904
31918
  if (today) args.push("--today");
31905
31919
  if (query) args.push(`--query=${query}`);
31906
31920
  if (max !== void 0) args.push(`--max=${max}`);
@@ -32027,7 +32041,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
32027
32041
  );
32028
32042
 
32029
32043
  // ../gogcli-mcp/src/server.ts
32030
- var VERSION = true ? "2.23.2" : "0.0.0";
32044
+ var VERSION = true ? "2.24.0" : "0.0.0";
32031
32045
 
32032
32046
  // ../gogcli-mcp/src/auth-log.ts
32033
32047
  var FAILURES = /* @__PURE__ */ new Set([
@@ -32630,16 +32644,19 @@ function registerExtraCalendarTools(server) {
32630
32644
  return runOrDiagnose(args, { account });
32631
32645
  });
32632
32646
  server.registerTool("gog_calendar_search", {
32633
- 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.",
32634
32648
  annotations: { readOnlyHint: true },
32635
32649
  inputSchema: {
32636
32650
  query: external_exports.string().describe("Search query"),
32637
32651
  from: external_exports.string().optional().describe("Start time (RFC3339, date, or relative: now, today, tomorrow, monday)"),
32638
- to: external_exports.string().optional().describe("End time (RFC3339, date, or relative: now, today, tomorrow, monday)"),
32639
- today: external_exports.boolean().optional().describe("Today only"),
32640
- tomorrow: external_exports.boolean().optional().describe("Tomorrow only"),
32641
- week: external_exports.boolean().optional().describe("This week (uses weekStart, default Mon)"),
32642
- 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".'),
32643
32660
  weekStart: external_exports.string().optional().describe("Week start day for week (sun, mon, ...)"),
32644
32661
  calendar: external_exports.string().optional().describe("Calendar ID (default: primary)"),
32645
32662
  max: external_exports.number().optional().describe("Max results (default: 25)"),
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.2",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-calendar",
3
- "version": "2.23.2",
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>",
@@ -134,16 +134,21 @@ export function registerExtraCalendarTools(server: McpServer): void {
134
134
  });
135
135
 
136
136
  server.registerTool('gog_calendar_search', {
137
- 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.',
138
140
  annotations: { readOnlyHint: true },
139
141
  inputSchema: {
140
142
  query: z.string().describe('Search query'),
141
143
  from: z.string().optional().describe('Start time (RFC3339, date, or relative: now, today, tomorrow, monday)'),
142
- to: z.string().optional().describe('End time (RFC3339, date, or relative: now, today, tomorrow, monday)'),
143
- today: z.boolean().optional().describe('Today only'),
144
- tomorrow: z.boolean().optional().describe('Tomorrow only'),
145
- week: z.boolean().optional().describe('This week (uses weekStart, default Mon)'),
146
- 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".'),
147
152
  weekStart: z.string().optional().describe('Week start day for week (sun, mon, ...)'),
148
153
  calendar: z.string().optional().describe('Calendar ID (default: primary)'),
149
154
  max: z.number().optional().describe('Max results (default: 25)'),
@@ -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,