gogcli-mcp-calendar 2.23.2 → 2.25.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 +109 -45
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/src/tools/calendar-extra.ts +11 -6
- package/tests/tools/calendar-extra.test.ts +33 -8
package/dist/index.js
CHANGED
|
@@ -31177,10 +31177,11 @@ function sanitizedEnv() {
|
|
|
31177
31177
|
}
|
|
31178
31178
|
return result;
|
|
31179
31179
|
}
|
|
31180
|
+
var TOKEN_LEFT_BOUNDARY = "(?<![A-Za-z0-9+/])";
|
|
31180
31181
|
var GOOGLE_TOKEN_PATTERNS = [
|
|
31181
|
-
|
|
31182
|
+
new RegExp(`${TOKEN_LEFT_BOUNDARY}ya29\\.[A-Za-z0-9._\\-]+`, "g"),
|
|
31182
31183
|
// OAuth2 access tokens
|
|
31183
|
-
|
|
31184
|
+
new RegExp(`${TOKEN_LEFT_BOUNDARY}1//[A-Za-z0-9._\\-]+`, "g")
|
|
31184
31185
|
// OAuth2 refresh tokens
|
|
31185
31186
|
];
|
|
31186
31187
|
function redactGoogleTokens(text) {
|
|
@@ -31193,6 +31194,26 @@ function redactGoogleTokens(text) {
|
|
|
31193
31194
|
function redactSecrets2(text) {
|
|
31194
31195
|
return redactGoogleTokens(redactSecrets(text));
|
|
31195
31196
|
}
|
|
31197
|
+
var OPAQUE_FIELD_VALUE = "[A-Za-z0-9+/_-]{16,}={0,2}";
|
|
31198
|
+
var opaquePlaceholder = (i) => `\0gogOpaque${i}\0`;
|
|
31199
|
+
function redactPreservingOpaqueFields(text, fields, redact) {
|
|
31200
|
+
const lifted = [];
|
|
31201
|
+
let staged = text;
|
|
31202
|
+
for (const field of fields) {
|
|
31203
|
+
const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31204
|
+
const re = new RegExp(`("${escaped}"\\s*:\\s*")(${OPAQUE_FIELD_VALUE})(")`, "g");
|
|
31205
|
+
staged = staged.replace(re, (_m, open, value, close) => {
|
|
31206
|
+
lifted.push(value);
|
|
31207
|
+
return `${open}${opaquePlaceholder(lifted.length - 1)}${close}`;
|
|
31208
|
+
});
|
|
31209
|
+
}
|
|
31210
|
+
if (lifted.length === 0) return redact(text);
|
|
31211
|
+
let redacted = redact(staged);
|
|
31212
|
+
lifted.forEach((value, i) => {
|
|
31213
|
+
redacted = redacted.split(opaquePlaceholder(i)).join(value);
|
|
31214
|
+
});
|
|
31215
|
+
return redacted;
|
|
31216
|
+
}
|
|
31196
31217
|
function augmentedPath() {
|
|
31197
31218
|
const home = process.env.HOME;
|
|
31198
31219
|
const candidates = [
|
|
@@ -31223,19 +31244,24 @@ function formatTimeout(ms) {
|
|
|
31223
31244
|
return `${ms}ms`;
|
|
31224
31245
|
}
|
|
31225
31246
|
async function spawnWithTempFiles(args, opts) {
|
|
31226
|
-
const { mkdtemp, writeFile, rm } = await import("node:fs/promises");
|
|
31247
|
+
const { mkdtemp, mkdir, writeFile, rm } = await import("node:fs/promises");
|
|
31227
31248
|
const { tmpdir } = await import("node:os");
|
|
31228
31249
|
const dir = await mkdtemp(join(tmpdir(), "gogcli-mcp-"));
|
|
31229
31250
|
try {
|
|
31230
31251
|
const argv = [];
|
|
31252
|
+
let seq = 0;
|
|
31231
31253
|
for (const arg of args) {
|
|
31232
31254
|
if (!isGogFileArg(arg)) {
|
|
31233
31255
|
argv.push(arg);
|
|
31234
31256
|
continue;
|
|
31235
31257
|
}
|
|
31236
|
-
const
|
|
31237
|
-
|
|
31238
|
-
|
|
31258
|
+
const sub = join(dir, String(seq));
|
|
31259
|
+
seq += 1;
|
|
31260
|
+
await mkdir(sub, { recursive: true, mode: 448 });
|
|
31261
|
+
const path = join(sub, arg.filename ?? `${arg.flag}.${arg.ext ?? "txt"}`);
|
|
31262
|
+
const data = arg.encoding === "base64" ? Buffer.from(arg.contents, "base64") : Buffer.from(arg.contents, "utf8");
|
|
31263
|
+
await writeFile(path, data, { mode: 384 });
|
|
31264
|
+
argv.push(arg.positional ? path : `--${arg.flag}=${path}`);
|
|
31239
31265
|
}
|
|
31240
31266
|
return await spawnGog(argv, opts);
|
|
31241
31267
|
} finally {
|
|
@@ -31320,8 +31346,9 @@ function assembleArgs(args, opts) {
|
|
|
31320
31346
|
return fullArgs;
|
|
31321
31347
|
}
|
|
31322
31348
|
async function run(args, options = {}) {
|
|
31323
|
-
const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full" } = options;
|
|
31324
|
-
const
|
|
31349
|
+
const { account, spawner, interactive = false, timeout, readonly: readonly2 = false, redactMode = "full", opaqueFields } = options;
|
|
31350
|
+
const base = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
|
|
31351
|
+
const redact = opaqueFields?.length ? (text) => redactPreservingOpaqueFields(text, opaqueFields, base) : base;
|
|
31325
31352
|
const fullArgs = assembleArgs(args, { account, interactive, readonly: readonly2 });
|
|
31326
31353
|
const store = activeExecutor();
|
|
31327
31354
|
try {
|
|
@@ -31335,7 +31362,7 @@ async function run(args, options = {}) {
|
|
|
31335
31362
|
}
|
|
31336
31363
|
return redact(output);
|
|
31337
31364
|
} catch (err) {
|
|
31338
|
-
const message =
|
|
31365
|
+
const message = base(err instanceof Error ? err.message : String(err));
|
|
31339
31366
|
if (isRunnerTransportError(err)) {
|
|
31340
31367
|
throw new RunnerTransportError(message, err.kind, err.status);
|
|
31341
31368
|
}
|
|
@@ -31756,6 +31783,7 @@ function formatAuthHealth(raw, now) {
|
|
|
31756
31783
|
// ../gogcli-mcp/src/tools/auth.ts
|
|
31757
31784
|
function registerAuthToolsWith(server, defaultServices) {
|
|
31758
31785
|
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.`;
|
|
31786
|
+
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
31787
|
server.registerTool("gog_auth_list", {
|
|
31760
31788
|
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
31789
|
annotations: { readOnlyHint: true },
|
|
@@ -31805,11 +31833,14 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31805
31833
|
annotations: { destructiveHint: true },
|
|
31806
31834
|
inputSchema: {
|
|
31807
31835
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
31808
|
-
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
|
|
31836
|
+
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
31837
|
+
extraScopes: external_exports.string().optional().describe(extraScopesDescribe)
|
|
31809
31838
|
}
|
|
31810
|
-
}, async ({ email: email3, services = defaultServices }) => {
|
|
31839
|
+
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
31811
31840
|
try {
|
|
31812
|
-
|
|
31841
|
+
const args = ["auth", "add", email3, "--services", services];
|
|
31842
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
|
|
31843
|
+
return rawTextResult(await run(args, {
|
|
31813
31844
|
interactive: true,
|
|
31814
31845
|
timeout: 3e5
|
|
31815
31846
|
}));
|
|
@@ -31821,14 +31852,14 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31821
31852
|
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
31853
|
inputSchema: {
|
|
31823
31854
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
31824
|
-
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
|
|
31855
|
+
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
31856
|
+
extraScopes: external_exports.string().optional().describe(`${extraScopesDescribe} Pass the SAME value to gog_auth_add_complete.`)
|
|
31825
31857
|
}
|
|
31826
|
-
}, async ({ email: email3, services = defaultServices }) => {
|
|
31858
|
+
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
31827
31859
|
try {
|
|
31828
|
-
|
|
31829
|
-
|
|
31830
|
-
|
|
31831
|
-
));
|
|
31860
|
+
const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
31861
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
31862
|
+
return rawTextResult(await run(args, { redactMode: "tokens" }));
|
|
31832
31863
|
} catch (err) {
|
|
31833
31864
|
return errorResult(errorText(err));
|
|
31834
31865
|
}
|
|
@@ -31843,25 +31874,28 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31843
31874
|
),
|
|
31844
31875
|
services: external_exports.string().optional().default(defaultServices).describe(
|
|
31845
31876
|
`Services authorized \u2014 MUST match the value passed to gog_auth_add_url. Default: "${defaultServices}".`
|
|
31877
|
+
),
|
|
31878
|
+
extraScopes: external_exports.string().optional().describe(
|
|
31879
|
+
"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
31880
|
)
|
|
31847
31881
|
}
|
|
31848
|
-
}, async ({ email: email3, redirectUrl, services = defaultServices }) => {
|
|
31882
|
+
}, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
|
|
31849
31883
|
try {
|
|
31850
|
-
|
|
31851
|
-
|
|
31852
|
-
|
|
31853
|
-
|
|
31854
|
-
|
|
31855
|
-
|
|
31856
|
-
|
|
31857
|
-
|
|
31858
|
-
|
|
31859
|
-
|
|
31860
|
-
|
|
31861
|
-
|
|
31862
|
-
|
|
31863
|
-
|
|
31864
|
-
));
|
|
31884
|
+
const args = [
|
|
31885
|
+
"auth",
|
|
31886
|
+
"add",
|
|
31887
|
+
email3,
|
|
31888
|
+
"--remote",
|
|
31889
|
+
"--step",
|
|
31890
|
+
"2",
|
|
31891
|
+
"--auth-url",
|
|
31892
|
+
redirectUrl,
|
|
31893
|
+
"--services",
|
|
31894
|
+
services,
|
|
31895
|
+
"--force-consent"
|
|
31896
|
+
];
|
|
31897
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
31898
|
+
return rawTextResult(await run(args));
|
|
31865
31899
|
} catch (err) {
|
|
31866
31900
|
return errorResult(errorText(err));
|
|
31867
31901
|
}
|
|
@@ -31880,13 +31914,19 @@ function authToolsFor(defaultServices) {
|
|
|
31880
31914
|
// ../gogcli-mcp/src/tools/calendar.ts
|
|
31881
31915
|
function registerCalendarTools(server) {
|
|
31882
31916
|
server.registerTool("gog_calendar_events", {
|
|
31883
|
-
description: 'List calendar events.
|
|
31917
|
+
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
31918
|
annotations: { readOnlyHint: true },
|
|
31885
31919
|
inputSchema: {
|
|
31886
31920
|
calendarId: external_exports.string().optional().describe("Calendar ID (default: primary calendar)"),
|
|
31887
31921
|
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
|
-
|
|
31922
|
+
to: external_exports.string().optional().describe("End time filter (RFC3339, date, or natural language). Mutually exclusive with today and with days."),
|
|
31923
|
+
// gog >= 0.36.0 (openclaw/gogcli#981). Before that release --days sat in
|
|
31924
|
+
// a switch arm evaluated ahead of --from, so `--from 2026-09-25 --days 5`
|
|
31925
|
+
// silently threw --from away and answered for today instead — at exit 0,
|
|
31926
|
+
// in a well-formed table. It is only exposed here now that it means what
|
|
31927
|
+
// it says.
|
|
31928
|
+
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.'),
|
|
31929
|
+
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
31930
|
query: external_exports.string().optional().describe("Free text search within events"),
|
|
31891
31931
|
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
31932
|
pageToken: pageTokenParam,
|
|
@@ -31896,11 +31936,12 @@ function registerCalendarTools(server) {
|
|
|
31896
31936
|
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
31937
|
account: accountParam
|
|
31898
31938
|
}
|
|
31899
|
-
}, async ({ calendarId, from, to, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
|
|
31939
|
+
}, async ({ calendarId, from, to, days, today, query, max, pageToken, page, all, eventTypes, timezone, account }) => {
|
|
31900
31940
|
const args = ["calendar", "events"];
|
|
31901
31941
|
if (calendarId) args.push(calendarId);
|
|
31902
31942
|
if (from) args.push(`--from=${from}`);
|
|
31903
31943
|
if (to) args.push(`--to=${to}`);
|
|
31944
|
+
if (days !== void 0) args.push(`--days=${days}`);
|
|
31904
31945
|
if (today) args.push("--today");
|
|
31905
31946
|
if (query) args.push(`--query=${query}`);
|
|
31906
31947
|
if (max !== void 0) args.push(`--max=${max}`);
|
|
@@ -32017,6 +32058,26 @@ function registerCalendarTools(server) {
|
|
|
32017
32058
|
registerRunTool(server, { service: "calendar", examples: '"calendars", "freebusy"' });
|
|
32018
32059
|
}
|
|
32019
32060
|
|
|
32061
|
+
// ../gogcli-mcp/src/attachments.ts
|
|
32062
|
+
var MAX_INLINE_ATTACHMENT_BYTES = 8 * 1024 * 1024;
|
|
32063
|
+
var RUNNER_MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
32064
|
+
var RUNNER_BODY_JSON_RESERVE_BYTES = 256 * 1024;
|
|
32065
|
+
var MAX_REQUEST_PAYLOAD_WIRE_BYTES = RUNNER_MAX_BODY_BYTES - RUNNER_BODY_JSON_RESERVE_BYTES;
|
|
32066
|
+
var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
|
|
32067
|
+
var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
|
|
32068
|
+
var INLINE_ATTACHMENT_LIMITS_TEXT = `up to ${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)} per file and ${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)} in total`;
|
|
32069
|
+
var inlineAttachmentSchema = external_exports.object({
|
|
32070
|
+
filename: external_exports.string().min(1).describe(
|
|
32071
|
+
`Filename the recipient will see, e.g. "pendant-layouts.png". gog infers the attachment's MIME type from this extension, so give it the right one \u2014 a .png sent as "layouts" arrives as an untyped blob. Must be a single filename, not a path.`
|
|
32072
|
+
),
|
|
32073
|
+
contentBase64: external_exports.string().min(1).describe(
|
|
32074
|
+
"The file's bytes, base64-encoded (standard alphabet, with padding). This is the whole point of this parameter: the bytes travel with the request, so nothing needs to exist on the gog server's filesystem."
|
|
32075
|
+
)
|
|
32076
|
+
});
|
|
32077
|
+
var attachInlineParam = external_exports.array(inlineAttachmentSchema).optional().describe(
|
|
32078
|
+
`Attachments supplied as BYTES rather than as server-side paths \u2014 use this whenever you hold a file and the gog server does not, which is always the case on the hosted connector and on any remote deployment. Each entry is {filename, contentBase64} (${INLINE_ATTACHMENT_LIMITS_TEXT}). Can be combined with \`attach\`: the two name disjoint files (paths read on the server vs. bytes sent with the call), and both end up as ordinary attachments on the message.`
|
|
32079
|
+
);
|
|
32080
|
+
|
|
32020
32081
|
// ../gogcli-mcp/src/tools/sheets.ts
|
|
32021
32082
|
var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
|
|
32022
32083
|
var dryRunParam = external_exports.boolean().optional().describe(
|
|
@@ -32027,7 +32088,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
|
32027
32088
|
);
|
|
32028
32089
|
|
|
32029
32090
|
// ../gogcli-mcp/src/server.ts
|
|
32030
|
-
var VERSION = true ? "2.
|
|
32091
|
+
var VERSION = true ? "2.25.0" : "0.0.0";
|
|
32031
32092
|
|
|
32032
32093
|
// ../gogcli-mcp/src/auth-log.ts
|
|
32033
32094
|
var FAILURES = /* @__PURE__ */ new Set([
|
|
@@ -32630,16 +32691,19 @@ function registerExtraCalendarTools(server) {
|
|
|
32630
32691
|
return runOrDiagnose(args, { account });
|
|
32631
32692
|
});
|
|
32632
32693
|
server.registerTool("gog_calendar_search", {
|
|
32633
|
-
description: "Full-text search for events matching a query string, with optional time filters.",
|
|
32694
|
+
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
32695
|
annotations: { readOnlyHint: true },
|
|
32635
32696
|
inputSchema: {
|
|
32636
32697
|
query: external_exports.string().describe("Search query"),
|
|
32637
32698
|
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
|
-
|
|
32699
|
+
to: external_exports.string().optional().describe("End time (RFC3339, date, or relative: now, today, tomorrow, monday). Mutually exclusive with days."),
|
|
32700
|
+
today: external_exports.boolean().optional().describe("Today only. A complete window on its own \u2014 not combinable with from/to/days."),
|
|
32701
|
+
tomorrow: external_exports.boolean().optional().describe("Tomorrow only. A complete window on its own \u2014 not combinable with from/to/days."),
|
|
32702
|
+
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."),
|
|
32703
|
+
// gog >= 0.36.0 (openclaw/gogcli#981) anchors --days at --from. It used
|
|
32704
|
+
// to mean "next N days from today" no matter what --from said, which is
|
|
32705
|
+
// why the old description here read that way.
|
|
32706
|
+
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
32707
|
weekStart: external_exports.string().optional().describe("Week start day for week (sun, mon, ...)"),
|
|
32644
32708
|
calendar: external_exports.string().optional().describe("Calendar ID (default: primary)"),
|
|
32645
32709
|
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.
|
|
6
|
+
"version": "2.25.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.
|
|
3
|
+
"version": "2.25.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
|
-
|
|
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
|
-
|
|
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,
|