gogcli-mcp-drive 4.6.3 → 4.7.1

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 Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
10
- "version": "4.6.3"
10
+ "version": "4.7.1"
11
11
  },
12
12
  "plugins": [
13
13
  {
@@ -15,7 +15,7 @@
15
15
  "displayName": "gogcli (Drive)",
16
16
  "source": "./",
17
17
  "description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
18
- "version": "4.6.3",
18
+ "version": "4.7.1",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp-drive",
3
3
  "displayName": "gogcli (Drive)",
4
- "version": "4.6.3",
4
+ "version": "4.7.1",
5
5
  "description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
6
6
  "author": {
7
7
  "name": "Chris Hall",
@@ -21,6 +21,6 @@
21
21
  "comments",
22
22
  "shared-drives"
23
23
  ],
24
- "skills": "./SKILL.md",
25
- "mcp": "./.mcp.json"
24
+ "skills": ".",
25
+ "mcpServers": "./.mcp.json"
26
26
  }
package/dist/index.js CHANGED
@@ -34537,11 +34537,19 @@ var DEFAULT_MAX_REQUEST_BODY_SIZE = 4 * 1024 * 1024;
34537
34537
  var McpToolError = class extends Error {
34538
34538
  /** Actionable remediation text, when one applies. */
34539
34539
  hint;
34540
+ /** The upstream HTTP status this failure came from, when known. */
34541
+ status;
34542
+ /** What kind of failure this is, when the thrower knows. */
34543
+ kind;
34540
34544
  constructor(message, opts) {
34541
34545
  super(message, opts?.cause !== void 0 ? { cause: opts.cause } : void 0);
34542
34546
  this.name = "McpToolError";
34543
34547
  if (opts?.hint !== void 0)
34544
34548
  this.hint = opts.hint;
34549
+ if (opts?.status !== void 0)
34550
+ this.status = opts.status;
34551
+ if (opts?.kind !== void 0)
34552
+ this.kind = opts.kind;
34545
34553
  Object.setPrototypeOf(this, new.target.prototype);
34546
34554
  }
34547
34555
  };
@@ -34566,7 +34574,7 @@ var API_KEY_RE = new RegExp([
34566
34574
  // webhook signing secret (Stripe-style)
34567
34575
  ].map((p) => `\\b${p}`).join("|"), "g");
34568
34576
  var GOOGLE_OAUTH_TOKEN_RE = /(?<![A-Za-z0-9+/])(?:ya29\.|1\/\/)[A-Za-z0-9._-]{8,}/g;
34569
- var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
34577
+ var QUERY_SECRET_RE = new RegExp(`([?&](?:(?:access|refresh|id|auth|session|csrf|xsrf)[_-]?token|client[_-]?secret|api[_-]?secret|api[_-]?key|password|passwd|signature|token|key|sig|(?:code[_-]?)?verifier|(?:php|j)?sess(?:ion)?[_-]?id|sid|x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN})=)[^&#\\s"'<>\`]+`, "gi");
34570
34578
  var OAUTH_CODE_RE = /([?&]code=)(?=[^&#\s"'<>`]{16,})[^&#\s"'<>`]+/gi;
34571
34579
  var FORM_PASSWORD_RE = /((?:^|[\s,;:"'(\[{])(?:password|passwd)=)[^&#\s"'<>`]+/gim;
34572
34580
  var HEADER_SECRET_RE = new RegExp(`(\\b(?:x[-_]${HEADER_NAME_RUN}?(?:api[-_]?key|token|secret|auth)${HEADER_NAME_RUN}|api[-_]?key)\\s*:\\s*)[^\\s,;"'<>\`]+`, "gi");
@@ -34577,6 +34585,8 @@ var JSON_SECRET_KEYS = [
34577
34585
  "api[_-]?secret",
34578
34586
  "(?:x[_-])?api[_-]?key",
34579
34587
  "private[_-]?key",
34588
+ "code[_-]?verifier",
34589
+ // PKCE; a bare `verifier` key is too generic for JSON
34580
34590
  `x[_-]${HEADER_NAME_RUN}?(?:api[_-]?key|token|secret|auth)${HEADER_NAME_RUN}`,
34581
34591
  // bounded like HEADER_SECRET_RE
34582
34592
  "(?:proxy-)?authorization",
@@ -34607,6 +34617,11 @@ function redactSecrets(text) {
34607
34617
  return text.replace(BEARER_RE, "$1[REDACTED]").replace(BASIC_AUTH_RE, "$1[REDACTED]").replace(SET_COOKIE_RE, "$1$2=[REDACTED]").replace(COOKIE_HEADER_RE, (_m, prefix, pairs) => `${prefix}${pairs.replace(/=[^;,\s]*/g, "=[REDACTED]")}`).replace(API_KEY_RE, "[REDACTED]").replace(GOOGLE_OAUTH_TOKEN_RE, "[REDACTED]").replace(HEADER_SECRET_RE, "$1[REDACTED]").replace(QUERY_SECRET_RE, "$1[REDACTED]").replace(OAUTH_CODE_RE, "$1[REDACTED]").replace(FORM_PASSWORD_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_COOKIE_DQ_RE, redactJsonCookie('"')).replace(JSON_COOKIE_SQ_RE, redactJsonCookie("'")).replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_NUM_RE, "$1[REDACTED]").replace(JWT_RE, "[REDACTED]");
34608
34618
  }
34609
34619
  var ERROR_REDACTION_INPUT_MAX = 64 * 1024;
34620
+ function messageOf(err) {
34621
+ if (err instanceof Error)
34622
+ return err.message;
34623
+ return String(err);
34624
+ }
34610
34625
 
34611
34626
  // ../../node_modules/@chrischall/mcp-utils/dist/response/view.js
34612
34627
  var VIEWS = ["compact", "full", "raw"];
@@ -34752,6 +34767,7 @@ function escapeKey(key) {
34752
34767
  var DEFAULT_REQUEST_KEY = "confirmation";
34753
34768
  var DEFAULT_CONFIRMATION_LABEL = "Confirm this action should proceed.";
34754
34769
  var UNSUPPORTED_NOTE = "Nothing was done because this client cannot show a confirmation prompt (it declares no MCP elicitation capability), and this action is never taken without one";
34770
+ var DISABLED_NOTE = "Nothing was done because confirmation prompts are turned off for this server, and this action is never taken without one";
34755
34771
  var STATE_PREFIX = "mcpu.confirm.v1.";
34756
34772
  function bindingKey(binding) {
34757
34773
  if (binding.ttlSeconds !== void 0 && !(Number.isFinite(binding.ttlSeconds) && binding.ttlSeconds > 0)) {
@@ -34824,6 +34840,15 @@ ${JSON.stringify(preview, null, 2)}`,
34824
34840
  };
34825
34841
  const response = inputResponse(ctx.mcpReq.inputResponses, requestKey);
34826
34842
  const accepted = acceptedContent(ctx.mcpReq.inputResponses, requestKey, confirmationSchema);
34843
+ if (options.elicitation === false) {
34844
+ return textResult({
34845
+ confirmed: false,
34846
+ dispatched: false,
34847
+ action: options.action,
34848
+ reason: "confirmation-unsupported",
34849
+ note: options.unsupportedNote ? `${DISABLED_NOTE}. ${options.unsupportedNote}` : `${DISABLED_NOTE}.`
34850
+ });
34851
+ }
34827
34852
  if (response.kind === "missing") {
34828
34853
  if (callerAcceptsFormElicitation(ctx) === false) {
34829
34854
  return textResult({
@@ -35018,7 +35043,7 @@ async function tokenConfirmation(action, fb) {
35018
35043
  }
35019
35044
  async function requireConfirmationWithFallback(ctx, options) {
35020
35045
  const { tokenFallback, ...confirmation } = options;
35021
- if (tokenFallback && callerAcceptsFormElicitation(ctx) === false) {
35046
+ if (tokenFallback && (options.elicitation === false || callerAcceptsFormElicitation(ctx) === false)) {
35022
35047
  return tokenConfirmation(options.action, tokenFallback);
35023
35048
  }
35024
35049
  return requireConfirmation(ctx, confirmation);
@@ -35038,7 +35063,7 @@ function readEnvVar(key, opts = {}) {
35038
35063
  if (typeof raw === "string") {
35039
35064
  const trimmed = raw.trim();
35040
35065
  if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
35041
- return trimmed;
35066
+ return opts.trim === false ? raw : trimmed;
35042
35067
  }
35043
35068
  }
35044
35069
  return opts.default;
@@ -35164,6 +35189,20 @@ function readConfirmMode(env = process.env) {
35164
35189
  }
35165
35190
  return "refuse";
35166
35191
  }
35192
+ function readConfirmElicitation(env = process.env) {
35193
+ const raw = readEnvVar("MCP_CONFIRM_ELICITATION", { env })?.trim().toLowerCase();
35194
+ if (!raw || raw === "on")
35195
+ return "on";
35196
+ if (raw === "off")
35197
+ return "off";
35198
+ const key = `elicitation:${raw}`;
35199
+ if (!warned.has(key)) {
35200
+ warned.add(key);
35201
+ process.stderr.write(`MCP_CONFIRM_ELICITATION="${raw}" is not one of on, off; treating it as on.
35202
+ `);
35203
+ }
35204
+ return "on";
35205
+ }
35167
35206
  function readConfirmTtl(env) {
35168
35207
  const raw = readEnvVar("MCP_CONFIRM_TTL_SECONDS", { env })?.trim();
35169
35208
  if (!raw)
@@ -35208,6 +35247,7 @@ function spentTokenStoreFromEnv(env = process.env) {
35208
35247
  }
35209
35248
  var CONFIRM_TOKEN_AUTO_INSTRUCTION = "Nothing has been done yet. Review this preview; if it is what was intended, call again with the same arguments plus confirmToken. (This server runs with MCP_CONFIRM_MODE=auto, so the user's approval in chat is not required.)";
35210
35249
  var REFUSE_HINT = "Set MCP_CONFIRM_MODE=ask-user on the server to allow two-step confirmation instead.";
35250
+ var ELICITATION_OFF_HINT = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
35211
35251
  var BAD_TTL_HINT = "MCP_CONFIRM_TTL_SECONDS on the server is not a positive whole number of seconds; fix it to allow two-step confirmation.";
35212
35252
  function boundArgs(args) {
35213
35253
  if (args === null || typeof args !== "object" || Array.isArray(args) || !("confirmToken" in args))
@@ -35218,19 +35258,29 @@ function boundArgs(args) {
35218
35258
  }
35219
35259
  function confirmationFromEnv(options) {
35220
35260
  const { tool, account, confirmToken, subject, instruction, args, env = process.env, spent: callerSpent, ...rest } = options;
35261
+ if (args === void 0 || args === null) {
35262
+ throw new TypeError(`confirmationFromEnv: ${tool} passed no args; pass the tool's validated arguments ({} for a tool with none).`);
35263
+ }
35221
35264
  const spent2 = callerSpent ?? spentTokenStoreFromEnv(env);
35222
35265
  const mode = readConfirmMode(env);
35223
35266
  const ttlSeconds = readConfirmTtl(env);
35224
- const bound = args === void 0 ? void 0 : boundArgs(args);
35225
- const confirmation = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
35267
+ const elicitationOff = readConfirmElicitation(env) === "off";
35268
+ const bound = boundArgs(args);
35269
+ const elicitationBound = account === void 0 ? { args: bound } : { account, args: bound };
35270
+ const bindingAdded = rest.binding ? rest : {
35271
+ ...rest,
35272
+ binding: { key: confirmKeyFromEnv(env), args: elicitationBound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 }
35273
+ };
35274
+ const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
35226
35275
  if (mode === "refuse" || ttlSeconds === void 0) {
35227
- const hint = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
35276
+ const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
35277
+ const hint = elicitationOff ? `${ELICITATION_OFF_HINT} ${reason}` : reason;
35228
35278
  return {
35229
35279
  ...confirmation,
35230
35280
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
35231
35281
  };
35232
35282
  }
35233
- const boundSubject = bound === void 0 ? subject : async () => {
35283
+ const boundSubject = async () => {
35234
35284
  const read = await subject();
35235
35285
  if ("content" in read)
35236
35286
  return read;
@@ -35257,12 +35307,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
35257
35307
  ...SUPPORTED_PROTOCOL_VERSIONS
35258
35308
  ]);
35259
35309
  function hintResultOrRethrow(err) {
35310
+ if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
35311
+ throw err;
35260
35312
  if (err instanceof McpToolError && err.hint) {
35261
35313
  return errorResult(`${err.message}
35262
35314
 
35263
35315
  Hint: ${err.hint}`);
35264
35316
  }
35265
- throw err;
35317
+ return errorResult(messageOf(err));
35266
35318
  }
35267
35319
  function mcpRequestFrom(args) {
35268
35320
  const ctx = args.at(-1);
@@ -35496,7 +35548,7 @@ var PositiveInt = external_exports.number().int().positive();
35496
35548
  var NonNegInt = external_exports.number().int().nonnegative();
35497
35549
  var NonEmptyString = external_exports.string().min(1);
35498
35550
  var IsoDate = external_exports.iso.date();
35499
- var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30");
35551
+ var IsoTime = external_exports.string().regex(/^([01]?\d|2[0-3]):[0-5]\d$/, "must be HH:MM (24h), e.g. 19:30").overwrite((v) => v.padStart(5, "0"));
35500
35552
  var NumericIdString = external_exports.string().regex(/^\d+$/, "must be a numeric id (digits only)").describe("A numeric id string (digits only), safe to interpolate into a URL path.");
35501
35553
  var SafePathSegment = external_exports.string().min(1).regex(/^[^/?#\s]+$/, 'must not contain "/", "?", "#", or whitespace').refine((v) => !v.includes(".."), 'must not contain ".."').describe('A single URL path segment, safe to interpolate: no "/", "..", "?", "#", or whitespace.');
35502
35554
  var schemaOrigin = external_exports.string().optional().describe("Portal origin (e.g. https://<vendor>.example.co) selecting which active session to use. Optional when only one session is active.");
@@ -36194,7 +36246,7 @@ function registerRunTool(server, options) {
36194
36246
  }
36195
36247
  server.registerTool(`gog_${service}_run`, {
36196
36248
  description,
36197
- annotations: { destructiveHint: true },
36249
+ annotations: { destructiveHint: true, openWorldHint: true },
36198
36250
  inputSchema: external_exports.object(inputSchema)
36199
36251
  }, async (rawArgs) => {
36200
36252
  const { subcommand, args, account } = rawArgs;
@@ -36398,11 +36450,20 @@ async function requireDispatchConfirmation(ctx, options) {
36398
36450
  confirmationLabel: options.confirmationLabel,
36399
36451
  ...options.unsupportedNote ? { unsupportedNote: options.unsupportedNote } : {}
36400
36452
  };
36401
- if (!fallback) return requireConfirmationWithFallback(ctx, confirmation);
36453
+ if (!fallback) {
36454
+ if (readConfirmElicitation() !== "off") return requireConfirmationWithFallback(ctx, confirmation);
36455
+ const offNote = "MCP_CONFIRM_ELICITATION=off on the server turns confirmation prompts off.";
36456
+ return requireConfirmationWithFallback(ctx, {
36457
+ ...confirmation,
36458
+ elicitation: false,
36459
+ unsupportedNote: confirmation.unsupportedNote ? `${offNote} ${confirmation.unsupportedNote}` : offNote
36460
+ });
36461
+ }
36402
36462
  return requireConfirmationWithFallback(ctx, confirmationFromEnv({
36403
36463
  ...confirmation,
36404
36464
  tool: fallback.tool,
36405
36465
  account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
36466
+ args: fallback.args,
36406
36467
  confirmToken: fallback.confirmToken,
36407
36468
  subject: fallback.subject,
36408
36469
  instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
@@ -36415,13 +36476,13 @@ function resultText(result) {
36415
36476
  }
36416
36477
 
36417
36478
  // ../gogcli-mcp/src/tools/auth.ts
36418
- var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "remove", "alias"];
36479
+ var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "alias"];
36419
36480
  function registerAuthToolsWith(server, defaultServices) {
36420
36481
  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.`;
36421
36482
  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.";
36422
36483
  server.registerTool("gog_auth_list", {
36423
36484
  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.",
36424
- annotations: { readOnlyHint: true },
36485
+ annotations: { readOnlyHint: true, openWorldHint: false },
36425
36486
  inputSchema: external_exports.object({})
36426
36487
  }, async () => {
36427
36488
  try {
@@ -36432,7 +36493,7 @@ function registerAuthToolsWith(server, defaultServices) {
36432
36493
  });
36433
36494
  server.registerTool("gog_auth_status", {
36434
36495
  description: "Show gogcli auth CONFIGURATION: keyring backend, credential files, and auth setup. Despite the name this is not a health check \u2014 it reads local setup and does not contact Google, so it says nothing about whether an account can still authenticate. Use gog_auth_health for that.",
36435
- annotations: { readOnlyHint: true },
36496
+ annotations: { readOnlyHint: true, openWorldHint: false },
36436
36497
  inputSchema: external_exports.object({})
36437
36498
  }, async () => {
36438
36499
  try {
@@ -36443,7 +36504,7 @@ function registerAuthToolsWith(server, defaultServices) {
36443
36504
  });
36444
36505
  server.registerTool("gog_auth_health", {
36445
36506
  description: `Check the LIVE health of each stored Google account. Unlike gog_auth_status (which only reports keyring/config setup), this performs a real token refresh against Google, so it detects expired or revoked (invalid_grant) refresh tokens \u2014 the account-wide sign-out that blocks every service. Reports per account: whether the token is currently valid, the mapped cause when it is not, how long ago it was authorized, and a warning as it approaches the 7-day refresh-token limit that applies to OAuth apps whose consent screen is still in "Testing" mode. Run it proactively to re-authorize on your own schedule instead of mid-task. On a hosted deployment this is the ONLY check that measures Google: a connector showing "connected" or "refreshed" has verified the client's connection to the MCP host, and nothing else \u2014 the Google credential lives in gog's keyring on that host and can be dead while the connection looks perfectly healthy.`,
36446
- annotations: { readOnlyHint: true },
36507
+ annotations: { readOnlyHint: true, openWorldHint: true },
36447
36508
  inputSchema: external_exports.object({})
36448
36509
  }, async () => {
36449
36510
  try {
@@ -36454,7 +36515,7 @@ function registerAuthToolsWith(server, defaultServices) {
36454
36515
  });
36455
36516
  server.registerTool("gog_auth_services", {
36456
36517
  description: "List all Google services supported by gogcli and the OAuth scopes each requires.",
36457
- annotations: { readOnlyHint: true },
36518
+ annotations: { readOnlyHint: true, openWorldHint: false },
36458
36519
  inputSchema: external_exports.object({})
36459
36520
  }, async () => {
36460
36521
  try {
@@ -36465,7 +36526,7 @@ function registerAuthToolsWith(server, defaultServices) {
36465
36526
  });
36466
36527
  server.registerTool("gog_auth_add", {
36467
36528
  description: "Authorize a Google account via browser-based OAuth. Opens a browser window where the user must sign in and grant access. Blocks for up to 5 minutes waiting for the user to complete authorization. If the browser does not open automatically, a fallback URL is included in the response. Use gog_auth_list to check which accounts are already configured.",
36468
- annotations: { destructiveHint: true },
36529
+ annotations: { destructiveHint: true, openWorldHint: true },
36469
36530
  inputSchema: external_exports.object({
36470
36531
  email: external_exports.string().describe("Google account email to authorize"),
36471
36532
  services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
@@ -36485,7 +36546,7 @@ function registerAuthToolsWith(server, defaultServices) {
36485
36546
  });
36486
36547
  server.registerTool("gog_auth_add_url", {
36487
36548
  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 on a hosted deployment 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.",
36488
- annotations: { readOnlyHint: true },
36549
+ annotations: { readOnlyHint: true, openWorldHint: false },
36489
36550
  inputSchema: external_exports.object({
36490
36551
  email: external_exports.string().describe("Google account email to authorize"),
36491
36552
  services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
@@ -36502,7 +36563,7 @@ function registerAuthToolsWith(server, defaultServices) {
36502
36563
  });
36503
36564
  server.registerTool("gog_auth_add_complete", {
36504
36565
  description: "Complete REMOTE/headless Google authorization (step 2 of 2). Takes the full redirected localhost URL the user copied after finishing gog_auth_add_url, exchanges it for a refresh token, and stores it. Use the SAME `services` value you passed to gog_auth_add_url. Must run within 10 minutes of step 1 and against the same gogcli host.",
36505
- annotations: { destructiveHint: true },
36566
+ annotations: { destructiveHint: true, openWorldHint: true },
36506
36567
  inputSchema: external_exports.object({
36507
36568
  email: external_exports.string().describe("Google account email being authorized (same as step 1)"),
36508
36569
  redirectUrl: external_exports.string().describe(
@@ -36538,7 +36599,7 @@ function registerAuthToolsWith(server, defaultServices) {
36538
36599
  });
36539
36600
  registerRunTool(server, {
36540
36601
  service: "auth",
36541
- examples: '"remove", "alias", "list"',
36602
+ examples: '"alias", "list", "status"',
36542
36603
  omitAccount: true,
36543
36604
  allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
36544
36605
  note: "For browser-based authorization, use gog_auth_add instead."
@@ -36695,7 +36756,7 @@ var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime
36695
36756
  function registerDriveTools(server) {
36696
36757
  server.registerTool("gog_drive_ls", {
36697
36758
  description: "List files in a Google Drive folder (default: root).",
36698
- annotations: { readOnlyHint: true },
36759
+ annotations: { readOnlyHint: true, openWorldHint: true },
36699
36760
  inputSchema: external_exports.object({
36700
36761
  folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
36701
36762
  max: external_exports.number().optional().describe("Max results (default: 20)"),
@@ -36724,7 +36785,7 @@ function registerDriveTools(server) {
36724
36785
  });
36725
36786
  server.registerTool("gog_drive_search", {
36726
36787
  description: "Search Google Drive files by full-text query.",
36727
- annotations: { readOnlyHint: true },
36788
+ annotations: { readOnlyHint: true, openWorldHint: true },
36728
36789
  inputSchema: external_exports.object({
36729
36790
  query: external_exports.string().describe("Search query"),
36730
36791
  // gog's `drive search` accepts no --fields mask, so unlike gog_drive_ls
@@ -36741,7 +36802,7 @@ function registerDriveTools(server) {
36741
36802
  });
36742
36803
  server.registerTool("gog_drive_get", {
36743
36804
  description: "Get metadata for a Google Drive file.",
36744
- annotations: { readOnlyHint: true },
36805
+ annotations: { readOnlyHint: true, openWorldHint: true },
36745
36806
  inputSchema: external_exports.object({
36746
36807
  fileId: external_exports.string().describe("File ID"),
36747
36808
  // A --fields mask saves only 7% here: the default set is already narrow,
@@ -36761,7 +36822,7 @@ function registerDriveTools(server) {
36761
36822
  });
36762
36823
  server.registerTool("gog_drive_mkdir", {
36763
36824
  description: "Create a new folder in Google Drive.",
36764
- annotations: { destructiveHint: false },
36825
+ annotations: { destructiveHint: false, openWorldHint: true },
36765
36826
  inputSchema: external_exports.object({
36766
36827
  name: external_exports.string().describe("Folder name"),
36767
36828
  account: accountParam
@@ -36771,7 +36832,7 @@ function registerDriveTools(server) {
36771
36832
  });
36772
36833
  server.registerTool("gog_drive_rename", {
36773
36834
  description: "Rename a file or folder in Google Drive.",
36774
- annotations: { destructiveHint: false },
36835
+ annotations: { destructiveHint: false, openWorldHint: true },
36775
36836
  inputSchema: external_exports.object({
36776
36837
  fileId: external_exports.string().describe("File or folder ID"),
36777
36838
  newName: external_exports.string().describe("New name"),
@@ -36782,7 +36843,7 @@ function registerDriveTools(server) {
36782
36843
  });
36783
36844
  server.registerTool("gog_drive_move", {
36784
36845
  description: "Move a file to a different folder in Google Drive.",
36785
- annotations: { destructiveHint: false },
36846
+ annotations: { destructiveHint: false, openWorldHint: true },
36786
36847
  inputSchema: external_exports.object({
36787
36848
  fileId: external_exports.string().describe("File ID to move"),
36788
36849
  parentId: external_exports.string().describe("Destination folder ID"),
@@ -36793,7 +36854,7 @@ function registerDriveTools(server) {
36793
36854
  });
36794
36855
  server.registerTool("gog_drive_delete", {
36795
36856
  description: "Move a Google Drive file to trash, or permanently delete it with permanent=true (irreversible). A permanent delete reads the file and asks the MCP host to show the user a confirmation prompt naming it first; nothing is deleted unless they accept. Moving to trash is recoverable and does not ask." + CONFIRM_FALLBACK_DESCRIPTION,
36796
- annotations: { destructiveHint: true },
36857
+ annotations: { destructiveHint: true, openWorldHint: true },
36797
36858
  inputSchema: external_exports.object({
36798
36859
  fileId: external_exports.string().describe("File ID to delete"),
36799
36860
  permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
@@ -36813,6 +36874,7 @@ function registerDriveTools(server) {
36813
36874
  details: target,
36814
36875
  unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
36815
36876
  fallback: {
36877
+ args: { fileId, permanent, account },
36816
36878
  tool: "gog_drive_delete",
36817
36879
  account,
36818
36880
  confirmToken,
@@ -36827,7 +36889,7 @@ function registerDriveTools(server) {
36827
36889
  });
36828
36890
  server.registerTool("gog_drive_share", {
36829
36891
  description: "Share a Google Drive file or folder. Granting access is the risk even though no email is sent (gog does not notify by default), so this reads the file and asks the MCP host to show the user a confirmation prompt naming it, who gets access and with what role; nothing is shared unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
36830
- annotations: { destructiveHint: true },
36892
+ annotations: { destructiveHint: true, openWorldHint: true },
36831
36893
  inputSchema: external_exports.object({
36832
36894
  fileId: external_exports.string().describe("File or folder ID"),
36833
36895
  to: external_exports.enum(["user", "anyone", "domain"]).describe("Share target type"),
@@ -36856,6 +36918,7 @@ function registerDriveTools(server) {
36856
36918
  details: grant,
36857
36919
  unsupportedNote: "Ask the user to share it themselves from Google Drive.",
36858
36920
  fallback: {
36921
+ args: { fileId, to, email: email3, domain: domain2, role, account },
36859
36922
  tool: "gog_drive_share",
36860
36923
  account,
36861
36924
  confirmToken,
@@ -36873,7 +36936,7 @@ function registerDriveTools(server) {
36873
36936
  server.registerTool("gog_drive_extract_text", {
36874
36937
  description: `Extract readable TEXT from a Drive file \u2014 PDF, image, docx, or a native Google Doc \u2014 and return it to you directly (closing the webViewLink dead end). Accepts a Drive file id, including the id from gog_gmail_attachment's deliveredVia:"drive" response. For non-Docs it copies the file to a temporary Google Doc so Drive OCRs it (works for scanned PDFs too), exports plain text, then deletes the temp Doc. Operates entirely via the Drive API within the existing drive scope \u2014 no host filesystem, no scope widening. For a large file, page through with offset/maxChars.`,
36875
36938
  // Creates and deletes a temporary Doc for non-native files, so not read-only.
36876
- annotations: { destructiveHint: false },
36939
+ annotations: { destructiveHint: false, openWorldHint: true },
36877
36940
  inputSchema: external_exports.object({
36878
36941
  fileId: external_exports.string().describe("Drive file ID (e.g. the id returned by gog_gmail_attachment)"),
36879
36942
  ocrLanguage: external_exports.string().optional().describe(
@@ -36938,7 +37001,7 @@ function registerDriveTools(server) {
36938
37001
  });
36939
37002
  server.registerTool("gog_drive_read_bytes", {
36940
37003
  description: "Fetch a Drive file's raw bytes and return them base64-encoded as an embedded resource \u2014 the generic fallback for callers that want the file itself (to parse locally) rather than extracted text. Files over 8 MiB are refused (checked before downloading). For readable text from a PDF, prefer gog_drive_extract_text.",
36941
- annotations: { readOnlyHint: true },
37004
+ annotations: { readOnlyHint: true, openWorldHint: true },
36942
37005
  inputSchema: external_exports.object({
36943
37006
  fileId: external_exports.string().describe("Drive file ID"),
36944
37007
  account: accountParam
@@ -37011,7 +37074,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
37011
37074
  );
37012
37075
 
37013
37076
  // ../gogcli-mcp/src/server.ts
37014
- var VERSION = true ? "4.6.3" : "0.0.0";
37077
+ var VERSION = true ? "4.7.1" : "0.0.0";
37015
37078
 
37016
37079
  // ../gogcli-mcp/src/bootstrap-auth.ts
37017
37080
  import { join as join7 } from "node:path";
@@ -37090,7 +37153,7 @@ function registerExtraDriveTools(server) {
37090
37153
  server.registerTool("gog_drive_download", {
37091
37154
  description: "Download a Drive file to the local filesystem. For Google Docs formats, specify an export format (pdf, csv, xlsx, pptx, txt, png, docx, md).",
37092
37155
  // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
37093
- annotations: { readOnlyHint: false, destructiveHint: true },
37156
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
37094
37157
  inputSchema: external_exports.object({
37095
37158
  fileId: external_exports.string().describe("File ID to download"),
37096
37159
  out: external_exports.string().optional().describe("Output file path (default: gogcli config dir)"),
@@ -37108,7 +37171,7 @@ function registerExtraDriveTools(server) {
37108
37171
  });
37109
37172
  server.registerTool("gog_drive_upload", {
37110
37173
  description: `Upload a file to Drive. Supply it EITHER as localPath (a path on the gog server's own filesystem \u2014 local stdio deployments only) OR as content (the bytes, base64-encoded, carried with the request), which is what works when this server runs remotely from you and no path you can name exists on it. Use replace to replace the content of an existing file (preserves link/permissions), or convert to auto-convert to a Google format. Pair replace with ifVersion to make the overwrite conditional: gog sends an atomic If-Match precondition and reports a conflict \u2014 applying nothing \u2014 if the file changed since you read it, instead of silently clobbering a concurrent edit. No read tool surfaces the version number: fetch it immediately before uploading with gog_drive_run { subcommand: "raw", args: ["<fileId>", "--fields=version"] } \u2014 it comes back as a JSON string, so pass it on as a number. Conditional replacement refuses Google Workspace files (Docs/Sheets/Slides), which have no replaceable binary content.`,
37111
- annotations: { destructiveHint: false },
37174
+ annotations: { destructiveHint: false, openWorldHint: true },
37112
37175
  inputSchema: external_exports.object({
37113
37176
  localPath: external_exports.string().optional().describe(`Path to the file to upload, resolved ON THE GOG SERVER's filesystem \u2014 NOT this client's. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) this path does not exist and the call fails with "no such file or directory" \u2014 use content there. Exactly one of localPath / content is required. Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).`),
37114
37177
  content: external_exports.string().optional().describe("The file's bytes, base64-encoded (standard alphabet, with padding) \u2014 upload a file you hold without it existing anywhere on the gog server. This is the only route that works when the caller and gog share no filesystem. Requires name (there is no path to take a filename from). Max 8 MiB; for anything larger use localPath from a local deployment. Exactly one of localPath / content is required \u2014 supplying both is an error, not a precedence rule."),
@@ -37159,7 +37222,7 @@ function registerExtraDriveTools(server) {
37159
37222
  });
37160
37223
  server.registerTool("gog_drive_sync_push", {
37161
37224
  description: "Recursively push a local directory's contents into an existing Drive folder \u2014 uploads new files, updates changed ones (matched by name + MD5), and creates missing subfolders. One-way and additive: it never deletes anything on Drive. Use dryRun to preview the planned actions first.",
37162
- annotations: { destructiveHint: true },
37225
+ annotations: { destructiveHint: true, openWorldHint: true },
37163
37226
  inputSchema: external_exports.object({
37164
37227
  localPath: external_exports.string().describe("Local directory to push (its contents are mirrored into the parent folder). Must be inside the server's GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files)."),
37165
37228
  parent: external_exports.string().describe("Existing destination Drive folder ID"),
@@ -37176,7 +37239,7 @@ function registerExtraDriveTools(server) {
37176
37239
  });
37177
37240
  server.registerTool("gog_drive_copy", {
37178
37241
  description: "Copy a Drive file to a new file with the given name.",
37179
- annotations: { destructiveHint: false },
37242
+ annotations: { destructiveHint: false, openWorldHint: true },
37180
37243
  inputSchema: external_exports.object({
37181
37244
  fileId: external_exports.string().describe("File ID to copy"),
37182
37245
  name: external_exports.string().describe("Name for the new copy"),
@@ -37190,7 +37253,7 @@ function registerExtraDriveTools(server) {
37190
37253
  });
37191
37254
  server.registerTool("gog_drive_url", {
37192
37255
  description: "Print shareable web URLs for one or more Drive files.",
37193
- annotations: { readOnlyHint: true },
37256
+ annotations: { readOnlyHint: true, openWorldHint: true },
37194
37257
  inputSchema: external_exports.object({
37195
37258
  fileIds: external_exports.array(external_exports.string()).min(1).describe("One or more file IDs to get URLs for"),
37196
37259
  account: accountParam
@@ -37200,7 +37263,7 @@ function registerExtraDriveTools(server) {
37200
37263
  });
37201
37264
  server.registerTool("gog_drive_permissions", {
37202
37265
  description: "List permissions on a Drive file.",
37203
- annotations: { readOnlyHint: true },
37266
+ annotations: { readOnlyHint: true, openWorldHint: true },
37204
37267
  inputSchema: external_exports.object({
37205
37268
  fileId: external_exports.string().describe("File ID"),
37206
37269
  max: external_exports.number().optional().describe("Max results (default: 100)"),
@@ -37217,7 +37280,7 @@ function registerExtraDriveTools(server) {
37217
37280
  });
37218
37281
  server.registerTool("gog_drive_unshare", {
37219
37282
  description: "Remove a permission from a Drive file. Get the permissionId from gog_drive_permissions.",
37220
- annotations: { destructiveHint: true },
37283
+ annotations: { destructiveHint: true, openWorldHint: true },
37221
37284
  inputSchema: external_exports.object({
37222
37285
  fileId: external_exports.string().describe("File ID"),
37223
37286
  permissionId: external_exports.string().describe("Permission ID to remove"),
@@ -37228,7 +37291,7 @@ function registerExtraDriveTools(server) {
37228
37291
  });
37229
37292
  server.registerTool("gog_drive_drives_list", {
37230
37293
  description: "List shared drives (Team Drives) accessible to the account.",
37231
- annotations: { readOnlyHint: true },
37294
+ annotations: { readOnlyHint: true, openWorldHint: true },
37232
37295
  inputSchema: external_exports.object({
37233
37296
  max: external_exports.number().optional().describe("Max results (default: 100, max allowed: 100)"),
37234
37297
  pageToken: pageTokenParam,
@@ -37248,7 +37311,7 @@ function registerExtraDriveTools(server) {
37248
37311
  });
37249
37312
  server.registerTool("gog_drive_comments_list", {
37250
37313
  description: "List comments on a Drive file.",
37251
- annotations: { readOnlyHint: true },
37314
+ annotations: { readOnlyHint: true, openWorldHint: true },
37252
37315
  inputSchema: external_exports.object({
37253
37316
  fileId: external_exports.string().describe("File ID"),
37254
37317
  since: external_exports.string().optional().describe("Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)"),
@@ -37265,7 +37328,7 @@ function registerExtraDriveTools(server) {
37265
37328
  });
37266
37329
  server.registerTool("gog_drive_comments_get", {
37267
37330
  description: "Get a single comment on a Drive file by ID.",
37268
- annotations: { readOnlyHint: true },
37331
+ annotations: { readOnlyHint: true, openWorldHint: true },
37269
37332
  inputSchema: external_exports.object({
37270
37333
  fileId: external_exports.string().describe("File ID"),
37271
37334
  commentId: external_exports.string().describe("Comment ID"),
@@ -37276,7 +37339,7 @@ function registerExtraDriveTools(server) {
37276
37339
  });
37277
37340
  server.registerTool("gog_drive_comments_add", {
37278
37341
  description: "Add a new comment to a Drive file. Drive notifies the file's owner and everyone the text +mentions, so this reads the file and asks the MCP host to show the user a confirmation prompt with the file, the text and who is mentioned first; nothing is posted unless they accept." + CONFIRM_FALLBACK_DESCRIPTION,
37279
- annotations: { destructiveHint: false },
37342
+ annotations: { destructiveHint: false, openWorldHint: true },
37280
37343
  inputSchema: external_exports.object({
37281
37344
  fileId: external_exports.string().describe("File ID"),
37282
37345
  content: external_exports.string().describe("Comment text"),
@@ -37296,6 +37359,7 @@ function registerExtraDriveTools(server) {
37296
37359
  details: { file: file2, textPreview: bodyPreview(content), mentions },
37297
37360
  unsupportedNote: "Ask the user to comment from Google Drive.",
37298
37361
  fallback: {
37362
+ args: { fileId, content, account },
37299
37363
  tool: "gog_drive_comments_add",
37300
37364
  account,
37301
37365
  confirmToken,
@@ -37310,7 +37374,7 @@ function registerExtraDriveTools(server) {
37310
37374
  });
37311
37375
  server.registerTool("gog_drive_comments_update", {
37312
37376
  description: "Update the text of an existing comment.",
37313
- annotations: { destructiveHint: false },
37377
+ annotations: { destructiveHint: false, openWorldHint: true },
37314
37378
  inputSchema: external_exports.object({
37315
37379
  fileId: external_exports.string().describe("File ID"),
37316
37380
  commentId: external_exports.string().describe("Comment ID to update"),
@@ -37322,7 +37386,7 @@ function registerExtraDriveTools(server) {
37322
37386
  });
37323
37387
  server.registerTool("gog_drive_comments_delete", {
37324
37388
  description: "Delete a comment from a Drive file.",
37325
- annotations: { destructiveHint: true },
37389
+ annotations: { destructiveHint: true, openWorldHint: true },
37326
37390
  inputSchema: external_exports.object({
37327
37391
  fileId: external_exports.string().describe("File ID"),
37328
37392
  commentId: external_exports.string().describe("Comment ID to delete"),
@@ -37333,7 +37397,7 @@ function registerExtraDriveTools(server) {
37333
37397
  });
37334
37398
  server.registerTool("gog_drive_comments_reply", {
37335
37399
  description: 'Reply to an existing comment on a Drive file. Pass `action: "resolve"` or `"reopen"` to atomically flip the parent comment\'s resolved state via the Drive API\'s Reply.action field \u2014 avoids the older workaround of deleting the comment (which destroys review-thread context). Everyone on the thread and anyone the reply +mentions is notified, so this reads the file and the comment and asks the MCP host to show the user a confirmation prompt with both and the reply first; nothing is posted unless they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
37336
- annotations: { destructiveHint: false },
37400
+ annotations: { destructiveHint: false, openWorldHint: true },
37337
37401
  inputSchema: external_exports.object({
37338
37402
  fileId: external_exports.string().describe("File ID"),
37339
37403
  commentId: external_exports.string().describe("Comment ID to reply to"),
@@ -37360,6 +37424,7 @@ function registerExtraDriveTools(server) {
37360
37424
  details: { file: file2, replyingTo, textPreview: bodyPreview(content), mentions, ...action ? { action } : {} },
37361
37425
  unsupportedNote: "Ask the user to reply from Google Drive.",
37362
37426
  fallback: {
37427
+ args: { fileId, commentId, content, action, account },
37363
37428
  tool: "gog_drive_comments_reply",
37364
37429
  account,
37365
37430
  confirmToken,
@@ -37374,7 +37439,7 @@ function registerExtraDriveTools(server) {
37374
37439
  });
37375
37440
  server.registerTool("gog_drive_comments_resolve", {
37376
37441
  description: 'Resolve a comment on a Drive file (mark as done) without posting a reply. To resolve while replying, use gog_drive_comments_reply with action: "resolve".',
37377
- annotations: { destructiveHint: true },
37442
+ annotations: { destructiveHint: true, openWorldHint: true },
37378
37443
  inputSchema: external_exports.object({
37379
37444
  fileId: external_exports.string().describe("File ID"),
37380
37445
  commentId: external_exports.string().describe("Comment ID to resolve"),
@@ -37385,7 +37450,7 @@ function registerExtraDriveTools(server) {
37385
37450
  });
37386
37451
  server.registerTool("gog_drive_comments_reopen", {
37387
37452
  description: 'Reopen a previously resolved comment on a Drive file. To reopen while replying, use gog_drive_comments_reply with action: "reopen".',
37388
- annotations: { destructiveHint: true },
37453
+ annotations: { destructiveHint: true, openWorldHint: true },
37389
37454
  inputSchema: external_exports.object({
37390
37455
  fileId: external_exports.string().describe("File ID"),
37391
37456
  commentId: external_exports.string().describe("Comment ID to reopen"),
@@ -37396,7 +37461,7 @@ function registerExtraDriveTools(server) {
37396
37461
  });
37397
37462
  server.registerTool("gog_drive_du", {
37398
37463
  description: "Summarize Drive folder sizes (disk-usage style) starting from a folder.",
37399
- annotations: { readOnlyHint: true },
37464
+ annotations: { readOnlyHint: true, openWorldHint: true },
37400
37465
  inputSchema: external_exports.object({
37401
37466
  parent: external_exports.string().optional().describe("Folder ID to start from (default: root)"),
37402
37467
  depth: external_exports.number().optional().describe("Depth for folder totals (default: 1)"),
@@ -37418,7 +37483,7 @@ function registerExtraDriveTools(server) {
37418
37483
  });
37419
37484
  server.registerTool("gog_drive_tree", {
37420
37485
  description: "Print a read-only folder tree starting from a folder.",
37421
- annotations: { readOnlyHint: true },
37486
+ annotations: { readOnlyHint: true, openWorldHint: true },
37422
37487
  inputSchema: external_exports.object({
37423
37488
  parent: external_exports.string().optional().describe("Folder ID to start from (default: root)"),
37424
37489
  depth: external_exports.number().optional().describe("Max depth (0 = unlimited; default: 2)"),
@@ -37436,7 +37501,7 @@ function registerExtraDriveTools(server) {
37436
37501
  });
37437
37502
  server.registerTool("gog_drive_changes_start_token", {
37438
37503
  description: "Get a Drive changes start page token \u2014 the cursor you pass to gog_drive_changes_list to enumerate changes since this point.",
37439
- annotations: { readOnlyHint: true },
37504
+ annotations: { readOnlyHint: true, openWorldHint: true },
37440
37505
  inputSchema: external_exports.object({
37441
37506
  drive: external_exports.string().optional().describe("Shared drive ID for a shared-drive change log"),
37442
37507
  account: accountParam
@@ -37448,7 +37513,7 @@ function registerExtraDriveTools(server) {
37448
37513
  });
37449
37514
  server.registerTool("gog_drive_changes_list", {
37450
37515
  description: "List Drive changes since a page token (for sync/automation). Get the initial token from gog_drive_changes_start_token; the response includes a newStartPageToken to persist for the next poll.",
37451
- annotations: { readOnlyHint: true },
37516
+ annotations: { readOnlyHint: true, openWorldHint: true },
37452
37517
  inputSchema: external_exports.object({
37453
37518
  token: external_exports.string().describe("Start page token or next page token"),
37454
37519
  max: external_exports.number().optional().describe("Max results (default: 100)"),
@@ -37471,7 +37536,7 @@ function registerExtraDriveTools(server) {
37471
37536
  });
37472
37537
  server.registerTool("gog_drive_revisions_list", {
37473
37538
  description: "List a file's revision history \u2014 paged revision metadata plus provider export links for each revision.",
37474
- annotations: { readOnlyHint: true },
37539
+ annotations: { readOnlyHint: true, openWorldHint: true },
37475
37540
  inputSchema: external_exports.object({
37476
37541
  fileId: external_exports.string().describe("File ID"),
37477
37542
  ...paginationParams,
@@ -37484,7 +37549,7 @@ function registerExtraDriveTools(server) {
37484
37549
  });
37485
37550
  server.registerTool("gog_drive_revisions_get", {
37486
37551
  description: "Get one revision of a Drive file by revision ID (metadata + export links). Find revision IDs with gog_drive_revisions_list.",
37487
- annotations: { readOnlyHint: true },
37552
+ annotations: { readOnlyHint: true, openWorldHint: true },
37488
37553
  inputSchema: external_exports.object({
37489
37554
  fileId: external_exports.string().describe("File ID"),
37490
37555
  revisionId: external_exports.string().describe("Revision ID"),
@@ -37495,7 +37560,7 @@ function registerExtraDriveTools(server) {
37495
37560
  });
37496
37561
  server.registerTool("gog_drive_shortcut_create", {
37497
37562
  description: "Create a Drive shortcut to a file or folder inside a destination folder. Shortcuts are classified distinctly in listing/tree output and are never followed by tree scans.",
37498
- annotations: { destructiveHint: false },
37563
+ annotations: { destructiveHint: false, openWorldHint: true },
37499
37564
  inputSchema: external_exports.object({
37500
37565
  targetId: external_exports.string().describe("File or folder ID the shortcut points to"),
37501
37566
  parent: external_exports.string().describe("Destination folder ID for the shortcut"),
@@ -37509,7 +37574,7 @@ function registerExtraDriveTools(server) {
37509
37574
  });
37510
37575
  server.registerTool("gog_drive_labels_list", {
37511
37576
  description: "List Drive label schemas available to the account.",
37512
- annotations: { readOnlyHint: true },
37577
+ annotations: { readOnlyHint: true, openWorldHint: true },
37513
37578
  inputSchema: external_exports.object({
37514
37579
  language: external_exports.string().optional().describe("BCP-47 language code"),
37515
37580
  view: external_exports.enum(["LABEL_VIEW_BASIC", "LABEL_VIEW_FULL"]).optional().describe("Label view (default: LABEL_VIEW_BASIC)"),
@@ -37535,7 +37600,7 @@ function registerExtraDriveTools(server) {
37535
37600
  });
37536
37601
  server.registerTool("gog_drive_labels_get", {
37537
37602
  description: 'Get a single Drive label schema by name (e.g. "labels/abc123").',
37538
- annotations: { readOnlyHint: true },
37603
+ annotations: { readOnlyHint: true, openWorldHint: true },
37539
37604
  inputSchema: external_exports.object({
37540
37605
  name: external_exports.string().describe("Label schema name (e.g. labels/abc123)"),
37541
37606
  language: external_exports.string().optional().describe("BCP-47 language code"),
@@ -37552,7 +37617,7 @@ function registerExtraDriveTools(server) {
37552
37617
  });
37553
37618
  server.registerTool("gog_drive_labels_file_list", {
37554
37619
  description: "List labels applied to a Drive file.",
37555
- annotations: { readOnlyHint: true },
37620
+ annotations: { readOnlyHint: true, openWorldHint: true },
37556
37621
  inputSchema: external_exports.object({
37557
37622
  fileId: external_exports.string().describe("File ID"),
37558
37623
  max: external_exports.number().optional().describe("Max results (default: 100)"),
@@ -37569,7 +37634,7 @@ function registerExtraDriveTools(server) {
37569
37634
  });
37570
37635
  server.registerTool("gog_drive_labels_file_apply", {
37571
37636
  description: 'Apply or update a label on a Drive file, optionally setting field values. Each field flag takes "fieldId=value" entries (repeatable). selection/integer/date/user values may be comma-separated within one entry for multi-valued fields.',
37572
- annotations: { destructiveHint: false },
37637
+ annotations: { destructiveHint: false, openWorldHint: true },
37573
37638
  inputSchema: external_exports.object({
37574
37639
  fileId: external_exports.string().describe("File ID"),
37575
37640
  labelId: external_exports.string().describe("Label ID to apply"),
@@ -37595,7 +37660,7 @@ function registerExtraDriveTools(server) {
37595
37660
  });
37596
37661
  server.registerTool("gog_drive_labels_file_remove", {
37597
37662
  description: "Remove a label from a Drive file.",
37598
- annotations: { destructiveHint: true },
37663
+ annotations: { destructiveHint: true, openWorldHint: true },
37599
37664
  inputSchema: external_exports.object({
37600
37665
  fileId: external_exports.string().describe("File ID"),
37601
37666
  labelId: external_exports.string().describe("Label ID to remove"),
@@ -37606,7 +37671,7 @@ function registerExtraDriveTools(server) {
37606
37671
  });
37607
37672
  server.registerTool("gog_drive_activity", {
37608
37673
  description: "Query the Drive Activity API for audit events (edits, creates, deletes, moves, shares, etc.) scoped to a file or folder and/or time range.",
37609
- annotations: { readOnlyHint: true },
37674
+ annotations: { readOnlyHint: true, openWorldHint: true },
37610
37675
  inputSchema: external_exports.object({
37611
37676
  file: external_exports.string().optional().describe("Drive file ID to query"),
37612
37677
  folder: external_exports.string().optional().describe("Drive folder ID; includes descendants"),
@@ -37638,7 +37703,7 @@ function registerExtraDriveTools(server) {
37638
37703
  });
37639
37704
  server.registerTool("gog_drive_audit_sharing", {
37640
37705
  description: "Audit Drive sharing without mutation \u2014 find public (anyone-with-link) or external permissions across a folder tree or a single file.",
37641
- annotations: { readOnlyHint: true },
37706
+ annotations: { readOnlyHint: true, openWorldHint: true },
37642
37707
  inputSchema: external_exports.object({
37643
37708
  file: external_exports.string().optional().describe("Audit one file ID instead of a folder tree"),
37644
37709
  parent: external_exports.string().optional().describe("Folder ID to scan (default: root)"),
@@ -37664,7 +37729,7 @@ function registerExtraDriveTools(server) {
37664
37729
  });
37665
37730
  server.registerTool("gog_drive_audit_user", {
37666
37731
  description: "Audit Drive sharing without mutation \u2014 find permissions granted to a specific user across a folder tree or a single file.",
37667
- annotations: { readOnlyHint: true },
37732
+ annotations: { readOnlyHint: true, openWorldHint: true },
37668
37733
  inputSchema: external_exports.object({
37669
37734
  user: external_exports.string().describe("User email to audit permissions for"),
37670
37735
  file: external_exports.string().optional().describe("Audit one file ID instead of a folder tree"),
package/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-drive",
5
5
  "display_name": "gogcli (Drive)",
6
- "version": "4.6.3",
6
+ "version": "4.7.1",
7
7
  "description": "Extended Google Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -39,7 +39,10 @@
39
39
  "env": {
40
40
  "GOG_ACCOUNT": "${user_config.gog_account}",
41
41
  "GOG_PATH": "${user_config.gog_path}",
42
- "GOG_FILE_ROOTS": "${user_config.gog_file_roots}"
42
+ "GOG_FILE_ROOTS": "${user_config.gog_file_roots}",
43
+ "DISPLAY_TZ": "${user_config.display_tz}",
44
+ "GOG_READONLY": "${user_config.gog_readonly}",
45
+ "GOG_TIMEZONE": "${user_config.gog_timezone}"
43
46
  }
44
47
  }
45
48
  },
@@ -61,6 +64,24 @@
61
64
  "title": "Allowed file directories",
62
65
  "description": "Directories tools may read files from (attachments, uploads) or write files to (exports, downloads), separated by ':' (';' on Windows). Optional — defaults to ~/gogcli-mcp-files",
63
66
  "required": false
67
+ },
68
+ "display_tz": {
69
+ "type": "string",
70
+ "title": "Display time zone",
71
+ "description": "zone for *Display fields and for interpreting naive gog values; defaults to America/New_York",
72
+ "required": false
73
+ },
74
+ "gog_readonly": {
75
+ "type": "string",
76
+ "title": "Read-only mode",
77
+ "description": "block all mutating gog API requests (injects gog's --readonly); set to 0/false/no/off (or unset) to allow writes",
78
+ "required": false
79
+ },
80
+ "gog_timezone": {
81
+ "type": "string",
82
+ "title": "gog time zone",
83
+ "description": "zone gog formats naive dates in; unset, runner.ts hands gog DISPLAY_TZ so the two cannot diverge",
84
+ "required": false
64
85
  }
65
86
  },
66
87
  "tools": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-drive",
3
- "version": "4.6.3",
3
+ "version": "4.7.1",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-drive",
5
5
  "description": "Extended Google Drive MCP server via gogcli — auth + full Drive support (upload/download/permissions/comments/shared drives)",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -24,7 +24,7 @@
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
- "@chrischall/mcp-utils": "^2.14.0",
27
+ "@chrischall/mcp-utils": "^3.0.0",
28
28
  "@modelcontextprotocol/server": "^2.3.0",
29
29
  "zod": "^4.6.1"
30
30
  },
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-drive"
9
9
  },
10
- "version": "4.6.3",
10
+ "version": "4.7.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-drive",
15
- "version": "4.6.3",
15
+ "version": "4.7.1",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
@@ -28,6 +28,24 @@
28
28
  "description": "Override path to the gogcli binary (auto-discovered otherwise)",
29
29
  "isRequired": false,
30
30
  "format": "string"
31
+ },
32
+ {
33
+ "name": "DISPLAY_TZ",
34
+ "description": "zone for *Display fields and for interpreting naive gog values; defaults to America/New_York",
35
+ "isRequired": false,
36
+ "format": "string"
37
+ },
38
+ {
39
+ "name": "GOG_READONLY",
40
+ "description": "block all mutating gog API requests (injects gog's --readonly); set to 0/false/no/off (or unset) to allow writes",
41
+ "isRequired": false,
42
+ "format": "string"
43
+ },
44
+ {
45
+ "name": "GOG_TIMEZONE",
46
+ "description": "zone gog formats naive dates in; unset, runner.ts hands gog DISPLAY_TZ so the two cannot diverge",
47
+ "isRequired": false,
48
+ "format": "string"
31
49
  }
32
50
  ]
33
51
  }
@@ -26,7 +26,7 @@ export function registerExtraDriveTools(server: McpServer): void {
26
26
  server.registerTool('gog_drive_download', {
27
27
  description: 'Download a Drive file to the local filesystem. For Google Docs formats, specify an export format (pdf, csv, xlsx, pptx, txt, png, docx, md).',
28
28
  // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
29
- annotations: { readOnlyHint: false, destructiveHint: true },
29
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
30
30
  inputSchema: z.object({
31
31
  fileId: z.string().describe('File ID to download'),
32
32
  out: z.string().optional().describe('Output file path (default: gogcli config dir)'),
@@ -55,7 +55,7 @@ export function registerExtraDriveTools(server: McpServer): void {
55
55
  'No read tool surfaces the version number: fetch it immediately before uploading with ' +
56
56
  'gog_drive_run { subcommand: "raw", args: ["<fileId>", "--fields=version"] } — it comes back as a JSON string, so pass it on as a number. ' +
57
57
  'Conditional replacement refuses Google Workspace files (Docs/Sheets/Slides), which have no replaceable binary content.',
58
- annotations: { destructiveHint: false },
58
+ annotations: { destructiveHint: false, openWorldHint: true },
59
59
  inputSchema: z.object({
60
60
  localPath: z.string().optional().describe('Path to the file to upload, resolved ON THE GOG SERVER\'s filesystem — NOT this client\'s. Only usable when gog runs on the same machine you do (local stdio); on a hosted deployment (e.g. mcp-host) this path does not exist and the call fails with "no such file or directory" — use content there. Exactly one of localPath / content is required. Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).'),
61
61
  content: z.string().optional().describe('The file\'s bytes, base64-encoded (standard alphabet, with padding) — upload a file you hold without it existing anywhere on the gog server. This is the only route that works when the caller and gog share no filesystem. Requires name (there is no path to take a filename from). Max 8 MiB; for anything larger use localPath from a local deployment. Exactly one of localPath / content is required — supplying both is an error, not a precedence rule.'),
@@ -117,7 +117,7 @@ export function registerExtraDriveTools(server: McpServer): void {
117
117
 
118
118
  server.registerTool('gog_drive_sync_push', {
119
119
  description: 'Recursively push a local directory\'s contents into an existing Drive folder — uploads new files, updates changed ones (matched by name + MD5), and creates missing subfolders. One-way and additive: it never deletes anything on Drive. Use dryRun to preview the planned actions first.',
120
- annotations: { destructiveHint: true },
120
+ annotations: { destructiveHint: true, openWorldHint: true },
121
121
  inputSchema: z.object({
122
122
  localPath: z.string().describe('Local directory to push (its contents are mirrored into the parent folder). Must be inside the server\'s GOG_FILE_ROOTS directories (default ~/gogcli-mcp-files).'),
123
123
  parent: z.string().describe('Existing destination Drive folder ID'),
@@ -135,7 +135,7 @@ export function registerExtraDriveTools(server: McpServer): void {
135
135
 
136
136
  server.registerTool('gog_drive_copy', {
137
137
  description: 'Copy a Drive file to a new file with the given name.',
138
- annotations: { destructiveHint: false },
138
+ annotations: { destructiveHint: false, openWorldHint: true },
139
139
  inputSchema: z.object({
140
140
  fileId: z.string().describe('File ID to copy'),
141
141
  name: z.string().describe('Name for the new copy'),
@@ -150,7 +150,7 @@ export function registerExtraDriveTools(server: McpServer): void {
150
150
 
151
151
  server.registerTool('gog_drive_url', {
152
152
  description: 'Print shareable web URLs for one or more Drive files.',
153
- annotations: { readOnlyHint: true },
153
+ annotations: { readOnlyHint: true, openWorldHint: true },
154
154
  inputSchema: z.object({
155
155
  fileIds: z.array(z.string()).min(1).describe('One or more file IDs to get URLs for'),
156
156
  account: accountParam,
@@ -161,7 +161,7 @@ export function registerExtraDriveTools(server: McpServer): void {
161
161
 
162
162
  server.registerTool('gog_drive_permissions', {
163
163
  description: 'List permissions on a Drive file.',
164
- annotations: { readOnlyHint: true },
164
+ annotations: { readOnlyHint: true, openWorldHint: true },
165
165
  inputSchema: z.object({
166
166
  fileId: z.string().describe('File ID'),
167
167
  max: z.number().optional().describe('Max results (default: 100)'),
@@ -179,7 +179,7 @@ export function registerExtraDriveTools(server: McpServer): void {
179
179
 
180
180
  server.registerTool('gog_drive_unshare', {
181
181
  description: 'Remove a permission from a Drive file. Get the permissionId from gog_drive_permissions.',
182
- annotations: { destructiveHint: true },
182
+ annotations: { destructiveHint: true, openWorldHint: true },
183
183
  inputSchema: z.object({
184
184
  fileId: z.string().describe('File ID'),
185
185
  permissionId: z.string().describe('Permission ID to remove'),
@@ -191,7 +191,7 @@ export function registerExtraDriveTools(server: McpServer): void {
191
191
 
192
192
  server.registerTool('gog_drive_drives_list', {
193
193
  description: 'List shared drives (Team Drives) accessible to the account.',
194
- annotations: { readOnlyHint: true },
194
+ annotations: { readOnlyHint: true, openWorldHint: true },
195
195
  inputSchema: z.object({
196
196
  max: z.number().optional().describe('Max results (default: 100, max allowed: 100)'),
197
197
  pageToken: pageTokenParam,
@@ -212,7 +212,7 @@ export function registerExtraDriveTools(server: McpServer): void {
212
212
 
213
213
  server.registerTool('gog_drive_comments_list', {
214
214
  description: 'List comments on a Drive file.',
215
- annotations: { readOnlyHint: true },
215
+ annotations: { readOnlyHint: true, openWorldHint: true },
216
216
  inputSchema: z.object({
217
217
  fileId: z.string().describe('File ID'),
218
218
  since: z.string().optional().describe('Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)'),
@@ -230,7 +230,7 @@ export function registerExtraDriveTools(server: McpServer): void {
230
230
 
231
231
  server.registerTool('gog_drive_comments_get', {
232
232
  description: 'Get a single comment on a Drive file by ID.',
233
- annotations: { readOnlyHint: true },
233
+ annotations: { readOnlyHint: true, openWorldHint: true },
234
234
  inputSchema: z.object({
235
235
  fileId: z.string().describe('File ID'),
236
236
  commentId: z.string().describe('Comment ID'),
@@ -244,7 +244,7 @@ export function registerExtraDriveTools(server: McpServer): void {
244
244
  description: 'Add a new comment to a Drive file. Drive notifies the file\'s owner and everyone the text +mentions, so '
245
245
  + 'this reads the file and asks the MCP host to show the user a confirmation prompt with the file, the text and who '
246
246
  + 'is mentioned first; nothing is posted unless they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
247
- annotations: { destructiveHint: false },
247
+ annotations: { destructiveHint: false, openWorldHint: true },
248
248
  inputSchema: z.object({
249
249
  fileId: z.string().describe('File ID'),
250
250
  content: z.string().describe('Comment text'),
@@ -264,6 +264,7 @@ export function registerExtraDriveTools(server: McpServer): void {
264
264
  details: { file, textPreview: bodyPreview(content), mentions },
265
265
  unsupportedNote: 'Ask the user to comment from Google Drive.',
266
266
  fallback: {
267
+ args: { fileId, content, account },
267
268
  tool: 'gog_drive_comments_add',
268
269
  account,
269
270
  confirmToken,
@@ -279,7 +280,7 @@ export function registerExtraDriveTools(server: McpServer): void {
279
280
 
280
281
  server.registerTool('gog_drive_comments_update', {
281
282
  description: 'Update the text of an existing comment.',
282
- annotations: { destructiveHint: false },
283
+ annotations: { destructiveHint: false, openWorldHint: true },
283
284
  inputSchema: z.object({
284
285
  fileId: z.string().describe('File ID'),
285
286
  commentId: z.string().describe('Comment ID to update'),
@@ -292,7 +293,7 @@ export function registerExtraDriveTools(server: McpServer): void {
292
293
 
293
294
  server.registerTool('gog_drive_comments_delete', {
294
295
  description: 'Delete a comment from a Drive file.',
295
- annotations: { destructiveHint: true },
296
+ annotations: { destructiveHint: true, openWorldHint: true },
296
297
  inputSchema: z.object({
297
298
  fileId: z.string().describe('File ID'),
298
299
  commentId: z.string().describe('Comment ID to delete'),
@@ -307,7 +308,7 @@ export function registerExtraDriveTools(server: McpServer): void {
307
308
  + 'Everyone on the thread and anyone the reply +mentions is notified, so this reads the file and the comment and '
308
309
  + 'asks the MCP host to show the user a confirmation prompt with both and the reply first; nothing is posted unless '
309
310
  + 'they accept.' + CONFIRM_FALLBACK_DESCRIPTION,
310
- annotations: { destructiveHint: false },
311
+ annotations: { destructiveHint: false, openWorldHint: true },
311
312
  inputSchema: z.object({
312
313
  fileId: z.string().describe('File ID'),
313
314
  commentId: z.string().describe('Comment ID to reply to'),
@@ -334,6 +335,7 @@ export function registerExtraDriveTools(server: McpServer): void {
334
335
  details: { file, replyingTo, textPreview: bodyPreview(content), mentions, ...(action ? { action } : {}) },
335
336
  unsupportedNote: 'Ask the user to reply from Google Drive.',
336
337
  fallback: {
338
+ args: { fileId, commentId, content, action, account },
337
339
  tool: 'gog_drive_comments_reply',
338
340
  account,
339
341
  confirmToken,
@@ -349,7 +351,7 @@ export function registerExtraDriveTools(server: McpServer): void {
349
351
 
350
352
  server.registerTool('gog_drive_comments_resolve', {
351
353
  description: 'Resolve a comment on a Drive file (mark as done) without posting a reply. To resolve while replying, use gog_drive_comments_reply with action: "resolve".',
352
- annotations: { destructiveHint: true },
354
+ annotations: { destructiveHint: true, openWorldHint: true },
353
355
  inputSchema: z.object({
354
356
  fileId: z.string().describe('File ID'),
355
357
  commentId: z.string().describe('Comment ID to resolve'),
@@ -361,7 +363,7 @@ export function registerExtraDriveTools(server: McpServer): void {
361
363
 
362
364
  server.registerTool('gog_drive_comments_reopen', {
363
365
  description: 'Reopen a previously resolved comment on a Drive file. To reopen while replying, use gog_drive_comments_reply with action: "reopen".',
364
- annotations: { destructiveHint: true },
366
+ annotations: { destructiveHint: true, openWorldHint: true },
365
367
  inputSchema: z.object({
366
368
  fileId: z.string().describe('File ID'),
367
369
  commentId: z.string().describe('Comment ID to reopen'),
@@ -375,7 +377,7 @@ export function registerExtraDriveTools(server: McpServer): void {
375
377
 
376
378
  server.registerTool('gog_drive_du', {
377
379
  description: 'Summarize Drive folder sizes (disk-usage style) starting from a folder.',
378
- annotations: { readOnlyHint: true },
380
+ annotations: { readOnlyHint: true, openWorldHint: true },
379
381
  inputSchema: z.object({
380
382
  parent: z.string().optional().describe('Folder ID to start from (default: root)'),
381
383
  depth: z.number().optional().describe('Depth for folder totals (default: 1)'),
@@ -398,7 +400,7 @@ export function registerExtraDriveTools(server: McpServer): void {
398
400
 
399
401
  server.registerTool('gog_drive_tree', {
400
402
  description: 'Print a read-only folder tree starting from a folder.',
401
- annotations: { readOnlyHint: true },
403
+ annotations: { readOnlyHint: true, openWorldHint: true },
402
404
  inputSchema: z.object({
403
405
  parent: z.string().optional().describe('Folder ID to start from (default: root)'),
404
406
  depth: z.number().optional().describe('Max depth (0 = unlimited; default: 2)'),
@@ -417,7 +419,7 @@ export function registerExtraDriveTools(server: McpServer): void {
417
419
 
418
420
  server.registerTool('gog_drive_changes_start_token', {
419
421
  description: 'Get a Drive changes start page token — the cursor you pass to gog_drive_changes_list to enumerate changes since this point.',
420
- annotations: { readOnlyHint: true },
422
+ annotations: { readOnlyHint: true, openWorldHint: true },
421
423
  inputSchema: z.object({
422
424
  drive: z.string().optional().describe('Shared drive ID for a shared-drive change log'),
423
425
  account: accountParam,
@@ -430,7 +432,7 @@ export function registerExtraDriveTools(server: McpServer): void {
430
432
 
431
433
  server.registerTool('gog_drive_changes_list', {
432
434
  description: 'List Drive changes since a page token (for sync/automation). Get the initial token from gog_drive_changes_start_token; the response includes a newStartPageToken to persist for the next poll.',
433
- annotations: { readOnlyHint: true },
435
+ annotations: { readOnlyHint: true, openWorldHint: true },
434
436
  inputSchema: z.object({
435
437
  token: z.string().describe('Start page token or next page token'),
436
438
  max: z.number().optional().describe('Max results (default: 100)'),
@@ -456,7 +458,7 @@ export function registerExtraDriveTools(server: McpServer): void {
456
458
 
457
459
  server.registerTool('gog_drive_revisions_list', {
458
460
  description: 'List a file\'s revision history — paged revision metadata plus provider export links for each revision.',
459
- annotations: { readOnlyHint: true },
461
+ annotations: { readOnlyHint: true, openWorldHint: true },
460
462
  inputSchema: z.object({
461
463
  fileId: z.string().describe('File ID'),
462
464
  ...paginationParams,
@@ -470,7 +472,7 @@ export function registerExtraDriveTools(server: McpServer): void {
470
472
 
471
473
  server.registerTool('gog_drive_revisions_get', {
472
474
  description: 'Get one revision of a Drive file by revision ID (metadata + export links). Find revision IDs with gog_drive_revisions_list.',
473
- annotations: { readOnlyHint: true },
475
+ annotations: { readOnlyHint: true, openWorldHint: true },
474
476
  inputSchema: z.object({
475
477
  fileId: z.string().describe('File ID'),
476
478
  revisionId: z.string().describe('Revision ID'),
@@ -482,7 +484,7 @@ export function registerExtraDriveTools(server: McpServer): void {
482
484
 
483
485
  server.registerTool('gog_drive_shortcut_create', {
484
486
  description: 'Create a Drive shortcut to a file or folder inside a destination folder. Shortcuts are classified distinctly in listing/tree output and are never followed by tree scans.',
485
- annotations: { destructiveHint: false },
487
+ annotations: { destructiveHint: false, openWorldHint: true },
486
488
  inputSchema: z.object({
487
489
  targetId: z.string().describe('File or folder ID the shortcut points to'),
488
490
  parent: z.string().describe('Destination folder ID for the shortcut'),
@@ -497,7 +499,7 @@ export function registerExtraDriveTools(server: McpServer): void {
497
499
 
498
500
  server.registerTool('gog_drive_labels_list', {
499
501
  description: 'List Drive label schemas available to the account.',
500
- annotations: { readOnlyHint: true },
502
+ annotations: { readOnlyHint: true, openWorldHint: true },
501
503
  inputSchema: z.object({
502
504
  language: z.string().optional().describe('BCP-47 language code'),
503
505
  view: z.enum(['LABEL_VIEW_BASIC', 'LABEL_VIEW_FULL']).optional().describe('Label view (default: LABEL_VIEW_BASIC)'),
@@ -524,7 +526,7 @@ export function registerExtraDriveTools(server: McpServer): void {
524
526
 
525
527
  server.registerTool('gog_drive_labels_get', {
526
528
  description: 'Get a single Drive label schema by name (e.g. "labels/abc123").',
527
- annotations: { readOnlyHint: true },
529
+ annotations: { readOnlyHint: true, openWorldHint: true },
528
530
  inputSchema: z.object({
529
531
  name: z.string().describe('Label schema name (e.g. labels/abc123)'),
530
532
  language: z.string().optional().describe('BCP-47 language code'),
@@ -542,7 +544,7 @@ export function registerExtraDriveTools(server: McpServer): void {
542
544
 
543
545
  server.registerTool('gog_drive_labels_file_list', {
544
546
  description: 'List labels applied to a Drive file.',
545
- annotations: { readOnlyHint: true },
547
+ annotations: { readOnlyHint: true, openWorldHint: true },
546
548
  inputSchema: z.object({
547
549
  fileId: z.string().describe('File ID'),
548
550
  max: z.number().optional().describe('Max results (default: 100)'),
@@ -560,7 +562,7 @@ export function registerExtraDriveTools(server: McpServer): void {
560
562
 
561
563
  server.registerTool('gog_drive_labels_file_apply', {
562
564
  description: 'Apply or update a label on a Drive file, optionally setting field values. Each field flag takes "fieldId=value" entries (repeatable). selection/integer/date/user values may be comma-separated within one entry for multi-valued fields.',
563
- annotations: { destructiveHint: false },
565
+ annotations: { destructiveHint: false, openWorldHint: true },
564
566
  inputSchema: z.object({
565
567
  fileId: z.string().describe('File ID'),
566
568
  labelId: z.string().describe('Label ID to apply'),
@@ -587,7 +589,7 @@ export function registerExtraDriveTools(server: McpServer): void {
587
589
 
588
590
  server.registerTool('gog_drive_labels_file_remove', {
589
591
  description: 'Remove a label from a Drive file.',
590
- annotations: { destructiveHint: true },
592
+ annotations: { destructiveHint: true, openWorldHint: true },
591
593
  inputSchema: z.object({
592
594
  fileId: z.string().describe('File ID'),
593
595
  labelId: z.string().describe('Label ID to remove'),
@@ -599,7 +601,7 @@ export function registerExtraDriveTools(server: McpServer): void {
599
601
 
600
602
  server.registerTool('gog_drive_activity', {
601
603
  description: 'Query the Drive Activity API for audit events (edits, creates, deletes, moves, shares, etc.) scoped to a file or folder and/or time range.',
602
- annotations: { readOnlyHint: true },
604
+ annotations: { readOnlyHint: true, openWorldHint: true },
603
605
  inputSchema: z.object({
604
606
  file: z.string().optional().describe('Drive file ID to query'),
605
607
  folder: z.string().optional().describe('Drive folder ID; includes descendants'),
@@ -632,7 +634,7 @@ export function registerExtraDriveTools(server: McpServer): void {
632
634
 
633
635
  server.registerTool('gog_drive_audit_sharing', {
634
636
  description: 'Audit Drive sharing without mutation — find public (anyone-with-link) or external permissions across a folder tree or a single file.',
635
- annotations: { readOnlyHint: true },
637
+ annotations: { readOnlyHint: true, openWorldHint: true },
636
638
  inputSchema: z.object({
637
639
  file: z.string().optional().describe('Audit one file ID instead of a folder tree'),
638
640
  parent: z.string().optional().describe('Folder ID to scan (default: root)'),
@@ -659,7 +661,7 @@ export function registerExtraDriveTools(server: McpServer): void {
659
661
 
660
662
  server.registerTool('gog_drive_audit_user', {
661
663
  description: 'Audit Drive sharing without mutation — find permissions granted to a specific user across a folder tree or a single file.',
662
- annotations: { readOnlyHint: true },
664
+ annotations: { readOnlyHint: true, openWorldHint: true },
663
665
  inputSchema: z.object({
664
666
  user: z.string().describe('User email to audit permissions for'),
665
667
  file: z.string().optional().describe('Audit one file ID instead of a folder tree'),
@@ -0,0 +1,10 @@
1
+ import { authToolsFor, registerDriveTools } from '../../gogcli-mcp/src/lib.js';
2
+ import { registerExtraDriveTools } from '../src/tools/drive-extra.js';
3
+ import { describeToolAnnotations, LOCAL_AUTH_TOOLS } from '../../gogcli-mcp/tests/support/tool-annotations.js';
4
+
5
+ // The same registrar list src/index.ts hands to runMcp.
6
+ describeToolAnnotations([authToolsFor('drive,driveactivity,drivelabels'), registerDriveTools, registerExtraDriveTools] as never, {
7
+ tools: 50,
8
+ destructive: 13,
9
+ local: [...LOCAL_AUTH_TOOLS],
10
+ });