gogcli-mcp-docs 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.
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "metadata": {
9
9
  "description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
10
- "version": "2.23.2"
10
+ "version": "2.25.0"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Docs)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
18
- "version": "2.23.2",
18
+ "version": "2.25.0",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-docs",
3
3
  "displayName": "gogcli (Docs)",
4
- "version": "2.23.2",
4
+ "version": "2.25.0",
5
5
  "description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
6
6
  "author": {
7
7
  "name": "Chris Hall",
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
- /ya29\.[A-Za-z0-9._\-]+/g,
31182
+ new RegExp(`${TOKEN_LEFT_BOUNDARY}ya29\\.[A-Za-z0-9._\\-]+`, "g"),
31182
31183
  // OAuth2 access tokens
31183
- /1\/\/[A-Za-z0-9._\-]+/g
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 path = join(dir, `${arg.flag}.${arg.ext ?? "txt"}`);
31237
- await writeFile(path, arg.contents, { encoding: "utf8", mode: 384 });
31238
- argv.push(`--${arg.flag}=${path}`);
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 redact = redactMode === "tokens" ? redactGoogleTokens : redactSecrets2;
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 = redact(err instanceof Error ? err.message : String(err));
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
  }
@@ -31740,6 +31767,7 @@ function formatAuthHealth(raw, now) {
31740
31767
  // ../gogcli-mcp/src/tools/auth.ts
31741
31768
  function registerAuthToolsWith(server, defaultServices) {
31742
31769
  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.`;
31770
+ 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.";
31743
31771
  server.registerTool("gog_auth_list", {
31744
31772
  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.",
31745
31773
  annotations: { readOnlyHint: true },
@@ -31789,11 +31817,14 @@ function registerAuthToolsWith(server, defaultServices) {
31789
31817
  annotations: { destructiveHint: true },
31790
31818
  inputSchema: {
31791
31819
  email: external_exports.string().describe("Google account email to authorize"),
31792
- services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
31820
+ services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
31821
+ extraScopes: external_exports.string().optional().describe(extraScopesDescribe)
31793
31822
  }
31794
- }, async ({ email: email3, services = defaultServices }) => {
31823
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
31795
31824
  try {
31796
- return rawTextResult(await run(["auth", "add", email3, "--services", services], {
31825
+ const args = ["auth", "add", email3, "--services", services];
31826
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
31827
+ return rawTextResult(await run(args, {
31797
31828
  interactive: true,
31798
31829
  timeout: 3e5
31799
31830
  }));
@@ -31805,14 +31836,14 @@ function registerAuthToolsWith(server, defaultServices) {
31805
31836
  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.",
31806
31837
  inputSchema: {
31807
31838
  email: external_exports.string().describe("Google account email to authorize"),
31808
- services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
31839
+ services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
31840
+ extraScopes: external_exports.string().optional().describe(`${extraScopesDescribe} Pass the SAME value to gog_auth_add_complete.`)
31809
31841
  }
31810
- }, async ({ email: email3, services = defaultServices }) => {
31842
+ }, async ({ email: email3, services = defaultServices, extraScopes }) => {
31811
31843
  try {
31812
- return rawTextResult(await run(
31813
- ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"],
31814
- { redactMode: "tokens" }
31815
- ));
31844
+ const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
31845
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
31846
+ return rawTextResult(await run(args, { redactMode: "tokens" }));
31816
31847
  } catch (err) {
31817
31848
  return errorResult(errorText(err));
31818
31849
  }
@@ -31827,25 +31858,28 @@ function registerAuthToolsWith(server, defaultServices) {
31827
31858
  ),
31828
31859
  services: external_exports.string().optional().default(defaultServices).describe(
31829
31860
  `Services authorized \u2014 MUST match the value passed to gog_auth_add_url. Default: "${defaultServices}".`
31861
+ ),
31862
+ extraScopes: external_exports.string().optional().describe(
31863
+ "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."
31830
31864
  )
31831
31865
  }
31832
- }, async ({ email: email3, redirectUrl, services = defaultServices }) => {
31866
+ }, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
31833
31867
  try {
31834
- return rawTextResult(await run(
31835
- [
31836
- "auth",
31837
- "add",
31838
- email3,
31839
- "--remote",
31840
- "--step",
31841
- "2",
31842
- "--auth-url",
31843
- redirectUrl,
31844
- "--services",
31845
- services,
31846
- "--force-consent"
31847
- ]
31848
- ));
31868
+ const args = [
31869
+ "auth",
31870
+ "add",
31871
+ email3,
31872
+ "--remote",
31873
+ "--step",
31874
+ "2",
31875
+ "--auth-url",
31876
+ redirectUrl,
31877
+ "--services",
31878
+ services,
31879
+ "--force-consent"
31880
+ ];
31881
+ if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
31882
+ return rawTextResult(await run(args));
31849
31883
  } catch (err) {
31850
31884
  return errorResult(errorText(err));
31851
31885
  }
@@ -31960,6 +31994,26 @@ function registerDocsTools(server) {
31960
31994
  registerRunTool(server, { service: "docs", examples: '"copy", "clear", "insert", "sed", "export"' });
31961
31995
  }
31962
31996
 
31997
+ // ../gogcli-mcp/src/attachments.ts
31998
+ var MAX_INLINE_ATTACHMENT_BYTES = 8 * 1024 * 1024;
31999
+ var RUNNER_MAX_BODY_BYTES = 32 * 1024 * 1024;
32000
+ var RUNNER_BODY_JSON_RESERVE_BYTES = 256 * 1024;
32001
+ var MAX_REQUEST_PAYLOAD_WIRE_BYTES = RUNNER_MAX_BODY_BYTES - RUNNER_BODY_JSON_RESERVE_BYTES;
32002
+ var MAX_INLINE_ATTACHMENT_TOTAL_BYTES = Math.floor(MAX_REQUEST_PAYLOAD_WIRE_BYTES * 3 / 4);
32003
+ var formatMiB = (bytes) => `${Math.floor(bytes / (1024 * 1024))} MiB`;
32004
+ var INLINE_ATTACHMENT_LIMITS_TEXT = `up to ${formatMiB(MAX_INLINE_ATTACHMENT_BYTES)} per file and ${formatMiB(MAX_INLINE_ATTACHMENT_TOTAL_BYTES)} in total`;
32005
+ var inlineAttachmentSchema = external_exports.object({
32006
+ filename: external_exports.string().min(1).describe(
32007
+ `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.`
32008
+ ),
32009
+ contentBase64: external_exports.string().min(1).describe(
32010
+ "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."
32011
+ )
32012
+ });
32013
+ var attachInlineParam = external_exports.array(inlineAttachmentSchema).optional().describe(
32014
+ `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.`
32015
+ );
32016
+
31963
32017
  // ../gogcli-mcp/src/tools/sheets.ts
31964
32018
  var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
31965
32019
  var dryRunParam = external_exports.boolean().optional().describe(
@@ -31970,7 +32024,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
31970
32024
  );
31971
32025
 
31972
32026
  // ../gogcli-mcp/src/server.ts
31973
- var VERSION = true ? "2.23.2" : "0.0.0";
32027
+ var VERSION = true ? "2.25.0" : "0.0.0";
31974
32028
 
31975
32029
  // ../gogcli-mcp/src/auth-log.ts
31976
32030
  var FAILURES = /* @__PURE__ */ new Set([
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-docs",
5
5
  "display_name": "gogcli (Docs)",
6
- "version": "2.23.2",
6
+ "version": "2.25.0",
7
7
  "description": "Extended Google Docs for Claude via gogcli — auth + full Docs and comments support",
8
8
  "author": {
9
9
  "name": "Chris Hall",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-docs",
3
- "version": "2.23.2",
3
+ "version": "2.25.0",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-docs",
5
5
  "description": "Extended Google Docs MCP server via gogcli — all base tools plus full Docs support",
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-docs"
9
9
  },
10
- "version": "2.23.2",
10
+ "version": "2.25.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-docs",
15
- "version": "2.23.2",
15
+ "version": "2.25.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },