gogcli-mcp-drive 4.7.0 → 4.8.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 Drive for Claude via gogcli — auth + full Drive support (upload, download, permissions, comments, shared drives)",
10
- "version": "4.7.0"
10
+ "version": "4.8.0"
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.7.0",
18
+ "version": "4.8.0",
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.7.0",
4
+ "version": "4.8.0",
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
@@ -19790,7 +19790,7 @@ function date4(params) {
19790
19790
  return _coercedDate(ZodDate, params);
19791
19791
  }
19792
19792
 
19793
- // ../../node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
19793
+ // ../../node_modules/@modelcontextprotocol/server/node_modules/@modelcontextprotocol/core/dist/auth-BNDyLTqp.mjs
19794
19794
  var LATEST_PROTOCOL_VERSION = "2025-11-25";
19795
19795
  var SUPPORTED_PROTOCOL_VERSIONS = [
19796
19796
  LATEST_PROTOCOL_VERSION,
@@ -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"];
@@ -35048,7 +35063,7 @@ function readEnvVar(key, opts = {}) {
35048
35063
  if (typeof raw === "string") {
35049
35064
  const trimmed = raw.trim();
35050
35065
  if (trimmed.length > 0 && trimmed !== "undefined" && trimmed !== "null" && !PLACEHOLDER_RE.test(trimmed)) {
35051
- return trimmed;
35066
+ return opts.trim === false ? raw : trimmed;
35052
35067
  }
35053
35068
  }
35054
35069
  return opts.default;
@@ -35243,12 +35258,19 @@ function boundArgs(args) {
35243
35258
  }
35244
35259
  function confirmationFromEnv(options) {
35245
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
+ }
35246
35264
  const spent2 = callerSpent ?? spentTokenStoreFromEnv(env);
35247
35265
  const mode = readConfirmMode(env);
35248
35266
  const ttlSeconds = readConfirmTtl(env);
35249
35267
  const elicitationOff = readConfirmElicitation(env) === "off";
35250
- const bound = args === void 0 ? void 0 : boundArgs(args);
35251
- const bindingAdded = bound === void 0 || rest.binding ? rest : { ...rest, binding: { key: confirmKeyFromEnv(env), args: bound, ttlSeconds: ttlSeconds ?? DEFAULT_TTL_SECONDS2 } };
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
+ };
35252
35274
  const confirmation = elicitationOff ? { ...bindingAdded, elicitation: false } : bindingAdded;
35253
35275
  if (mode === "refuse" || ttlSeconds === void 0) {
35254
35276
  const reason = mode === "refuse" ? REFUSE_HINT : BAD_TTL_HINT;
@@ -35258,7 +35280,7 @@ function confirmationFromEnv(options) {
35258
35280
  unsupportedNote: confirmation.unsupportedNote ? `${confirmation.unsupportedNote} ${hint}` : hint
35259
35281
  };
35260
35282
  }
35261
- const boundSubject = bound === void 0 ? subject : async () => {
35283
+ const boundSubject = async () => {
35262
35284
  const read = await subject();
35263
35285
  if ("content" in read)
35264
35286
  return read;
@@ -35285,12 +35307,14 @@ var SERVER_PROTOCOL_VERSIONS = Object.freeze([
35285
35307
  ...SUPPORTED_PROTOCOL_VERSIONS
35286
35308
  ]);
35287
35309
  function hintResultOrRethrow(err) {
35310
+ if (err instanceof ProtocolError && err.code === ProtocolErrorCode.UrlElicitationRequired)
35311
+ throw err;
35288
35312
  if (err instanceof McpToolError && err.hint) {
35289
35313
  return errorResult(`${err.message}
35290
35314
 
35291
35315
  Hint: ${err.hint}`);
35292
35316
  }
35293
- throw err;
35317
+ return errorResult(messageOf(err));
35294
35318
  }
35295
35319
  function mcpRequestFrom(args) {
35296
35320
  const ctx = args.at(-1);
@@ -35524,7 +35548,7 @@ var PositiveInt = external_exports.number().int().positive();
35524
35548
  var NonNegInt = external_exports.number().int().nonnegative();
35525
35549
  var NonEmptyString = external_exports.string().min(1);
35526
35550
  var IsoDate = external_exports.iso.date();
35527
- 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"));
35528
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.");
35529
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.');
35530
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.");
@@ -36222,7 +36246,7 @@ function registerRunTool(server, options) {
36222
36246
  }
36223
36247
  server.registerTool(`gog_${service}_run`, {
36224
36248
  description,
36225
- annotations: { destructiveHint: true },
36249
+ annotations: { destructiveHint: true, openWorldHint: true },
36226
36250
  inputSchema: external_exports.object(inputSchema)
36227
36251
  }, async (rawArgs) => {
36228
36252
  const { subcommand, args, account } = rawArgs;
@@ -36439,6 +36463,7 @@ async function requireDispatchConfirmation(ctx, options) {
36439
36463
  ...confirmation,
36440
36464
  tool: fallback.tool,
36441
36465
  account: fallback.account ?? readEnvVar("GOG_ACCOUNT") ?? "",
36466
+ args: fallback.args,
36442
36467
  confirmToken: fallback.confirmToken,
36443
36468
  subject: fallback.subject,
36444
36469
  instruction: fallback.instruction ?? CONFIRM_ACTION_INSTRUCTION,
@@ -36451,13 +36476,13 @@ function resultText(result) {
36451
36476
  }
36452
36477
 
36453
36478
  // ../gogcli-mcp/src/tools/auth.ts
36454
- var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "remove", "alias"];
36479
+ var AUTH_RUN_SUBCOMMANDS = ["list", "status", "services", "alias"];
36455
36480
  function registerAuthToolsWith(server, defaultServices) {
36456
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.`;
36457
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.";
36458
36483
  server.registerTool("gog_auth_list", {
36459
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.",
36460
- annotations: { readOnlyHint: true },
36485
+ annotations: { readOnlyHint: true, openWorldHint: false },
36461
36486
  inputSchema: external_exports.object({})
36462
36487
  }, async () => {
36463
36488
  try {
@@ -36468,7 +36493,7 @@ function registerAuthToolsWith(server, defaultServices) {
36468
36493
  });
36469
36494
  server.registerTool("gog_auth_status", {
36470
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.",
36471
- annotations: { readOnlyHint: true },
36496
+ annotations: { readOnlyHint: true, openWorldHint: false },
36472
36497
  inputSchema: external_exports.object({})
36473
36498
  }, async () => {
36474
36499
  try {
@@ -36479,7 +36504,7 @@ function registerAuthToolsWith(server, defaultServices) {
36479
36504
  });
36480
36505
  server.registerTool("gog_auth_health", {
36481
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.`,
36482
- annotations: { readOnlyHint: true },
36507
+ annotations: { readOnlyHint: true, openWorldHint: true },
36483
36508
  inputSchema: external_exports.object({})
36484
36509
  }, async () => {
36485
36510
  try {
@@ -36490,7 +36515,7 @@ function registerAuthToolsWith(server, defaultServices) {
36490
36515
  });
36491
36516
  server.registerTool("gog_auth_services", {
36492
36517
  description: "List all Google services supported by gogcli and the OAuth scopes each requires.",
36493
- annotations: { readOnlyHint: true },
36518
+ annotations: { readOnlyHint: true, openWorldHint: false },
36494
36519
  inputSchema: external_exports.object({})
36495
36520
  }, async () => {
36496
36521
  try {
@@ -36501,7 +36526,7 @@ function registerAuthToolsWith(server, defaultServices) {
36501
36526
  });
36502
36527
  server.registerTool("gog_auth_add", {
36503
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.",
36504
- annotations: { destructiveHint: true },
36529
+ annotations: { destructiveHint: true, openWorldHint: true },
36505
36530
  inputSchema: external_exports.object({
36506
36531
  email: external_exports.string().describe("Google account email to authorize"),
36507
36532
  services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
@@ -36521,7 +36546,7 @@ function registerAuthToolsWith(server, defaultServices) {
36521
36546
  });
36522
36547
  server.registerTool("gog_auth_add_url", {
36523
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.",
36524
- annotations: { readOnlyHint: true },
36549
+ annotations: { readOnlyHint: true, openWorldHint: false },
36525
36550
  inputSchema: external_exports.object({
36526
36551
  email: external_exports.string().describe("Google account email to authorize"),
36527
36552
  services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
@@ -36538,7 +36563,7 @@ function registerAuthToolsWith(server, defaultServices) {
36538
36563
  });
36539
36564
  server.registerTool("gog_auth_add_complete", {
36540
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.",
36541
- annotations: { destructiveHint: true },
36566
+ annotations: { destructiveHint: true, openWorldHint: true },
36542
36567
  inputSchema: external_exports.object({
36543
36568
  email: external_exports.string().describe("Google account email being authorized (same as step 1)"),
36544
36569
  redirectUrl: external_exports.string().describe(
@@ -36574,7 +36599,7 @@ function registerAuthToolsWith(server, defaultServices) {
36574
36599
  });
36575
36600
  registerRunTool(server, {
36576
36601
  service: "auth",
36577
- examples: '"remove", "alias", "list"',
36602
+ examples: '"alias", "list", "status"',
36578
36603
  omitAccount: true,
36579
36604
  allowedSubcommands: AUTH_RUN_SUBCOMMANDS,
36580
36605
  note: "For browser-based authorization, use gog_auth_add instead."
@@ -36731,7 +36756,7 @@ var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime
36731
36756
  function registerDriveTools(server) {
36732
36757
  server.registerTool("gog_drive_ls", {
36733
36758
  description: "List files in a Google Drive folder (default: root).",
36734
- annotations: { readOnlyHint: true },
36759
+ annotations: { readOnlyHint: true, openWorldHint: true },
36735
36760
  inputSchema: external_exports.object({
36736
36761
  folderId: external_exports.string().optional().describe("Folder ID to list (default: root)"),
36737
36762
  max: external_exports.number().optional().describe("Max results (default: 20)"),
@@ -36760,7 +36785,7 @@ function registerDriveTools(server) {
36760
36785
  });
36761
36786
  server.registerTool("gog_drive_search", {
36762
36787
  description: "Search Google Drive files by full-text query.",
36763
- annotations: { readOnlyHint: true },
36788
+ annotations: { readOnlyHint: true, openWorldHint: true },
36764
36789
  inputSchema: external_exports.object({
36765
36790
  query: external_exports.string().describe("Search query"),
36766
36791
  // gog's `drive search` accepts no --fields mask, so unlike gog_drive_ls
@@ -36777,7 +36802,7 @@ function registerDriveTools(server) {
36777
36802
  });
36778
36803
  server.registerTool("gog_drive_get", {
36779
36804
  description: "Get metadata for a Google Drive file.",
36780
- annotations: { readOnlyHint: true },
36805
+ annotations: { readOnlyHint: true, openWorldHint: true },
36781
36806
  inputSchema: external_exports.object({
36782
36807
  fileId: external_exports.string().describe("File ID"),
36783
36808
  // A --fields mask saves only 7% here: the default set is already narrow,
@@ -36797,7 +36822,7 @@ function registerDriveTools(server) {
36797
36822
  });
36798
36823
  server.registerTool("gog_drive_mkdir", {
36799
36824
  description: "Create a new folder in Google Drive.",
36800
- annotations: { destructiveHint: false },
36825
+ annotations: { destructiveHint: false, openWorldHint: true },
36801
36826
  inputSchema: external_exports.object({
36802
36827
  name: external_exports.string().describe("Folder name"),
36803
36828
  account: accountParam
@@ -36807,7 +36832,7 @@ function registerDriveTools(server) {
36807
36832
  });
36808
36833
  server.registerTool("gog_drive_rename", {
36809
36834
  description: "Rename a file or folder in Google Drive.",
36810
- annotations: { destructiveHint: false },
36835
+ annotations: { destructiveHint: false, openWorldHint: true },
36811
36836
  inputSchema: external_exports.object({
36812
36837
  fileId: external_exports.string().describe("File or folder ID"),
36813
36838
  newName: external_exports.string().describe("New name"),
@@ -36818,7 +36843,7 @@ function registerDriveTools(server) {
36818
36843
  });
36819
36844
  server.registerTool("gog_drive_move", {
36820
36845
  description: "Move a file to a different folder in Google Drive.",
36821
- annotations: { destructiveHint: false },
36846
+ annotations: { destructiveHint: false, openWorldHint: true },
36822
36847
  inputSchema: external_exports.object({
36823
36848
  fileId: external_exports.string().describe("File ID to move"),
36824
36849
  parentId: external_exports.string().describe("Destination folder ID"),
@@ -36829,7 +36854,7 @@ function registerDriveTools(server) {
36829
36854
  });
36830
36855
  server.registerTool("gog_drive_delete", {
36831
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,
36832
- annotations: { destructiveHint: true },
36857
+ annotations: { destructiveHint: true, openWorldHint: true },
36833
36858
  inputSchema: external_exports.object({
36834
36859
  fileId: external_exports.string().describe("File ID to delete"),
36835
36860
  permanent: external_exports.boolean().optional().describe("Permanently delete instead of moving to trash (irreversible; asks the user first)"),
@@ -36849,6 +36874,7 @@ function registerDriveTools(server) {
36849
36874
  details: target,
36850
36875
  unsupportedNote: "Move it to the trash instead (permanent=false); the user can empty the trash from Google Drive.",
36851
36876
  fallback: {
36877
+ args: { fileId, permanent, account },
36852
36878
  tool: "gog_drive_delete",
36853
36879
  account,
36854
36880
  confirmToken,
@@ -36863,7 +36889,7 @@ function registerDriveTools(server) {
36863
36889
  });
36864
36890
  server.registerTool("gog_drive_share", {
36865
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,
36866
- annotations: { destructiveHint: true },
36892
+ annotations: { destructiveHint: true, openWorldHint: true },
36867
36893
  inputSchema: external_exports.object({
36868
36894
  fileId: external_exports.string().describe("File or folder ID"),
36869
36895
  to: external_exports.enum(["user", "anyone", "domain"]).describe("Share target type"),
@@ -36892,6 +36918,7 @@ function registerDriveTools(server) {
36892
36918
  details: grant,
36893
36919
  unsupportedNote: "Ask the user to share it themselves from Google Drive.",
36894
36920
  fallback: {
36921
+ args: { fileId, to, email: email3, domain: domain2, role, account },
36895
36922
  tool: "gog_drive_share",
36896
36923
  account,
36897
36924
  confirmToken,
@@ -36909,7 +36936,7 @@ function registerDriveTools(server) {
36909
36936
  server.registerTool("gog_drive_extract_text", {
36910
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.`,
36911
36938
  // Creates and deletes a temporary Doc for non-native files, so not read-only.
36912
- annotations: { destructiveHint: false },
36939
+ annotations: { destructiveHint: false, openWorldHint: true },
36913
36940
  inputSchema: external_exports.object({
36914
36941
  fileId: external_exports.string().describe("Drive file ID (e.g. the id returned by gog_gmail_attachment)"),
36915
36942
  ocrLanguage: external_exports.string().optional().describe(
@@ -36974,7 +37001,7 @@ function registerDriveTools(server) {
36974
37001
  });
36975
37002
  server.registerTool("gog_drive_read_bytes", {
36976
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.",
36977
- annotations: { readOnlyHint: true },
37004
+ annotations: { readOnlyHint: true, openWorldHint: true },
36978
37005
  inputSchema: external_exports.object({
36979
37006
  fileId: external_exports.string().describe("Drive file ID"),
36980
37007
  account: accountParam
@@ -37015,6 +37042,11 @@ function registerDriveTools(server) {
37015
37042
  }
37016
37043
 
37017
37044
  // ../gogcli-mcp/src/tools/gmail.ts
37045
+ var signatureSchema = {
37046
+ signature: external_exports.boolean().optional().describe("Append the Gmail signature from the active send-as address"),
37047
+ signatureFrom: external_exports.string().optional().describe("Append the Gmail signature from this send-as email address"),
37048
+ signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server. Must be inside GOG_FILE_ROOTS. Cannot be combined with signature/signatureFrom.")
37049
+ };
37018
37050
  var replySchema = {
37019
37051
  messageId: external_exports.string().describe("Gmail message ID to reply to \u2014 the short hex `id` from gog_gmail_get / _search (or gog_gmail_messages_search, gogcli-mcp-gmail only). NOT the threadId, NOT the RFC822 `<\u2026@host>` Message-Id header."),
37020
37052
  body: external_exports.string().optional().describe("Reply body (plain text; required unless bodyHtml or bodyHtmlFile is set). Any size \u2014 a large body is written to a temp file on the gog server rather than inlined into the command line. Note gog strips trailing newlines from a file-delivered body."),
@@ -37030,9 +37062,7 @@ var replySchema = {
37030
37062
  attachInline: attachInlineParam,
37031
37063
  from: external_exports.string().optional().describe("Send from this email address (must be a verified send-as alias)"),
37032
37064
  autoFromAddressedAlias: external_exports.boolean().optional().describe("When from is omitted, send from the verified send-as alias the original message was addressed TO, instead of the account's primary address \u2014 so a reply to mail sent to an alias goes back out from that alias. Ignored when from is set."),
37033
- signature: external_exports.boolean().optional().describe("Append the Gmail signature from the active send-as address"),
37034
- signatureFrom: external_exports.string().optional().describe("Append the Gmail signature from this send-as email address"),
37035
- signatureFile: external_exports.string().optional().describe("Append a local signature file (plain text or HTML), read on the gog server"),
37065
+ ...signatureSchema,
37036
37066
  account: accountParam
37037
37067
  };
37038
37068
  var sendReplySchema = external_exports.object({ ...replySchema, confirmToken: confirmTokenParam });
@@ -37047,7 +37077,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
37047
37077
  );
37048
37078
 
37049
37079
  // ../gogcli-mcp/src/server.ts
37050
- var VERSION = true ? "4.7.0" : "0.0.0";
37080
+ var VERSION = true ? "4.8.0" : "0.0.0";
37051
37081
 
37052
37082
  // ../gogcli-mcp/src/bootstrap-auth.ts
37053
37083
  import { join as join7 } from "node:path";
@@ -37126,7 +37156,7 @@ function registerExtraDriveTools(server) {
37126
37156
  server.registerTool("gog_drive_download", {
37127
37157
  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).",
37128
37158
  // Writes a file on the gog host and can overwrite one: not read-only (audit SEC-4).
37129
- annotations: { readOnlyHint: false, destructiveHint: true },
37159
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
37130
37160
  inputSchema: external_exports.object({
37131
37161
  fileId: external_exports.string().describe("File ID to download"),
37132
37162
  out: external_exports.string().optional().describe("Output file path (default: gogcli config dir)"),
@@ -37144,7 +37174,7 @@ function registerExtraDriveTools(server) {
37144
37174
  });
37145
37175
  server.registerTool("gog_drive_upload", {
37146
37176
  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.`,
37147
- annotations: { destructiveHint: false },
37177
+ annotations: { destructiveHint: false, openWorldHint: true },
37148
37178
  inputSchema: external_exports.object({
37149
37179
  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).`),
37150
37180
  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."),
@@ -37195,7 +37225,7 @@ function registerExtraDriveTools(server) {
37195
37225
  });
37196
37226
  server.registerTool("gog_drive_sync_push", {
37197
37227
  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.",
37198
- annotations: { destructiveHint: true },
37228
+ annotations: { destructiveHint: true, openWorldHint: true },
37199
37229
  inputSchema: external_exports.object({
37200
37230
  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)."),
37201
37231
  parent: external_exports.string().describe("Existing destination Drive folder ID"),
@@ -37212,7 +37242,7 @@ function registerExtraDriveTools(server) {
37212
37242
  });
37213
37243
  server.registerTool("gog_drive_copy", {
37214
37244
  description: "Copy a Drive file to a new file with the given name.",
37215
- annotations: { destructiveHint: false },
37245
+ annotations: { destructiveHint: false, openWorldHint: true },
37216
37246
  inputSchema: external_exports.object({
37217
37247
  fileId: external_exports.string().describe("File ID to copy"),
37218
37248
  name: external_exports.string().describe("Name for the new copy"),
@@ -37226,7 +37256,7 @@ function registerExtraDriveTools(server) {
37226
37256
  });
37227
37257
  server.registerTool("gog_drive_url", {
37228
37258
  description: "Print shareable web URLs for one or more Drive files.",
37229
- annotations: { readOnlyHint: true },
37259
+ annotations: { readOnlyHint: true, openWorldHint: true },
37230
37260
  inputSchema: external_exports.object({
37231
37261
  fileIds: external_exports.array(external_exports.string()).min(1).describe("One or more file IDs to get URLs for"),
37232
37262
  account: accountParam
@@ -37236,7 +37266,7 @@ function registerExtraDriveTools(server) {
37236
37266
  });
37237
37267
  server.registerTool("gog_drive_permissions", {
37238
37268
  description: "List permissions on a Drive file.",
37239
- annotations: { readOnlyHint: true },
37269
+ annotations: { readOnlyHint: true, openWorldHint: true },
37240
37270
  inputSchema: external_exports.object({
37241
37271
  fileId: external_exports.string().describe("File ID"),
37242
37272
  max: external_exports.number().optional().describe("Max results (default: 100)"),
@@ -37253,7 +37283,7 @@ function registerExtraDriveTools(server) {
37253
37283
  });
37254
37284
  server.registerTool("gog_drive_unshare", {
37255
37285
  description: "Remove a permission from a Drive file. Get the permissionId from gog_drive_permissions.",
37256
- annotations: { destructiveHint: true },
37286
+ annotations: { destructiveHint: true, openWorldHint: true },
37257
37287
  inputSchema: external_exports.object({
37258
37288
  fileId: external_exports.string().describe("File ID"),
37259
37289
  permissionId: external_exports.string().describe("Permission ID to remove"),
@@ -37264,7 +37294,7 @@ function registerExtraDriveTools(server) {
37264
37294
  });
37265
37295
  server.registerTool("gog_drive_drives_list", {
37266
37296
  description: "List shared drives (Team Drives) accessible to the account.",
37267
- annotations: { readOnlyHint: true },
37297
+ annotations: { readOnlyHint: true, openWorldHint: true },
37268
37298
  inputSchema: external_exports.object({
37269
37299
  max: external_exports.number().optional().describe("Max results (default: 100, max allowed: 100)"),
37270
37300
  pageToken: pageTokenParam,
@@ -37284,7 +37314,7 @@ function registerExtraDriveTools(server) {
37284
37314
  });
37285
37315
  server.registerTool("gog_drive_comments_list", {
37286
37316
  description: "List comments on a Drive file.",
37287
- annotations: { readOnlyHint: true },
37317
+ annotations: { readOnlyHint: true, openWorldHint: true },
37288
37318
  inputSchema: external_exports.object({
37289
37319
  fileId: external_exports.string().describe("File ID"),
37290
37320
  since: external_exports.string().optional().describe("Only return comments modified at or after this RFC3339 timestamp (e.g. 2026-06-01T00:00:00Z)"),
@@ -37301,7 +37331,7 @@ function registerExtraDriveTools(server) {
37301
37331
  });
37302
37332
  server.registerTool("gog_drive_comments_get", {
37303
37333
  description: "Get a single comment on a Drive file by ID.",
37304
- annotations: { readOnlyHint: true },
37334
+ annotations: { readOnlyHint: true, openWorldHint: true },
37305
37335
  inputSchema: external_exports.object({
37306
37336
  fileId: external_exports.string().describe("File ID"),
37307
37337
  commentId: external_exports.string().describe("Comment ID"),
@@ -37312,7 +37342,7 @@ function registerExtraDriveTools(server) {
37312
37342
  });
37313
37343
  server.registerTool("gog_drive_comments_add", {
37314
37344
  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,
37315
- annotations: { destructiveHint: false },
37345
+ annotations: { destructiveHint: false, openWorldHint: true },
37316
37346
  inputSchema: external_exports.object({
37317
37347
  fileId: external_exports.string().describe("File ID"),
37318
37348
  content: external_exports.string().describe("Comment text"),
@@ -37332,6 +37362,7 @@ function registerExtraDriveTools(server) {
37332
37362
  details: { file: file2, textPreview: bodyPreview(content), mentions },
37333
37363
  unsupportedNote: "Ask the user to comment from Google Drive.",
37334
37364
  fallback: {
37365
+ args: { fileId, content, account },
37335
37366
  tool: "gog_drive_comments_add",
37336
37367
  account,
37337
37368
  confirmToken,
@@ -37346,7 +37377,7 @@ function registerExtraDriveTools(server) {
37346
37377
  });
37347
37378
  server.registerTool("gog_drive_comments_update", {
37348
37379
  description: "Update the text of an existing comment.",
37349
- annotations: { destructiveHint: false },
37380
+ annotations: { destructiveHint: false, openWorldHint: true },
37350
37381
  inputSchema: external_exports.object({
37351
37382
  fileId: external_exports.string().describe("File ID"),
37352
37383
  commentId: external_exports.string().describe("Comment ID to update"),
@@ -37358,7 +37389,7 @@ function registerExtraDriveTools(server) {
37358
37389
  });
37359
37390
  server.registerTool("gog_drive_comments_delete", {
37360
37391
  description: "Delete a comment from a Drive file.",
37361
- annotations: { destructiveHint: true },
37392
+ annotations: { destructiveHint: true, openWorldHint: true },
37362
37393
  inputSchema: external_exports.object({
37363
37394
  fileId: external_exports.string().describe("File ID"),
37364
37395
  commentId: external_exports.string().describe("Comment ID to delete"),
@@ -37369,7 +37400,7 @@ function registerExtraDriveTools(server) {
37369
37400
  });
37370
37401
  server.registerTool("gog_drive_comments_reply", {
37371
37402
  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,
37372
- annotations: { destructiveHint: false },
37403
+ annotations: { destructiveHint: false, openWorldHint: true },
37373
37404
  inputSchema: external_exports.object({
37374
37405
  fileId: external_exports.string().describe("File ID"),
37375
37406
  commentId: external_exports.string().describe("Comment ID to reply to"),
@@ -37396,6 +37427,7 @@ function registerExtraDriveTools(server) {
37396
37427
  details: { file: file2, replyingTo, textPreview: bodyPreview(content), mentions, ...action ? { action } : {} },
37397
37428
  unsupportedNote: "Ask the user to reply from Google Drive.",
37398
37429
  fallback: {
37430
+ args: { fileId, commentId, content, action, account },
37399
37431
  tool: "gog_drive_comments_reply",
37400
37432
  account,
37401
37433
  confirmToken,
@@ -37410,7 +37442,7 @@ function registerExtraDriveTools(server) {
37410
37442
  });
37411
37443
  server.registerTool("gog_drive_comments_resolve", {
37412
37444
  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".',
37413
- annotations: { destructiveHint: true },
37445
+ annotations: { destructiveHint: true, openWorldHint: true },
37414
37446
  inputSchema: external_exports.object({
37415
37447
  fileId: external_exports.string().describe("File ID"),
37416
37448
  commentId: external_exports.string().describe("Comment ID to resolve"),
@@ -37421,7 +37453,7 @@ function registerExtraDriveTools(server) {
37421
37453
  });
37422
37454
  server.registerTool("gog_drive_comments_reopen", {
37423
37455
  description: 'Reopen a previously resolved comment on a Drive file. To reopen while replying, use gog_drive_comments_reply with action: "reopen".',
37424
- annotations: { destructiveHint: true },
37456
+ annotations: { destructiveHint: true, openWorldHint: true },
37425
37457
  inputSchema: external_exports.object({
37426
37458
  fileId: external_exports.string().describe("File ID"),
37427
37459
  commentId: external_exports.string().describe("Comment ID to reopen"),
@@ -37432,7 +37464,7 @@ function registerExtraDriveTools(server) {
37432
37464
  });
37433
37465
  server.registerTool("gog_drive_du", {
37434
37466
  description: "Summarize Drive folder sizes (disk-usage style) starting from a folder.",
37435
- annotations: { readOnlyHint: true },
37467
+ annotations: { readOnlyHint: true, openWorldHint: true },
37436
37468
  inputSchema: external_exports.object({
37437
37469
  parent: external_exports.string().optional().describe("Folder ID to start from (default: root)"),
37438
37470
  depth: external_exports.number().optional().describe("Depth for folder totals (default: 1)"),
@@ -37454,7 +37486,7 @@ function registerExtraDriveTools(server) {
37454
37486
  });
37455
37487
  server.registerTool("gog_drive_tree", {
37456
37488
  description: "Print a read-only folder tree starting from a folder.",
37457
- annotations: { readOnlyHint: true },
37489
+ annotations: { readOnlyHint: true, openWorldHint: true },
37458
37490
  inputSchema: external_exports.object({
37459
37491
  parent: external_exports.string().optional().describe("Folder ID to start from (default: root)"),
37460
37492
  depth: external_exports.number().optional().describe("Max depth (0 = unlimited; default: 2)"),
@@ -37472,7 +37504,7 @@ function registerExtraDriveTools(server) {
37472
37504
  });
37473
37505
  server.registerTool("gog_drive_changes_start_token", {
37474
37506
  description: "Get a Drive changes start page token \u2014 the cursor you pass to gog_drive_changes_list to enumerate changes since this point.",
37475
- annotations: { readOnlyHint: true },
37507
+ annotations: { readOnlyHint: true, openWorldHint: true },
37476
37508
  inputSchema: external_exports.object({
37477
37509
  drive: external_exports.string().optional().describe("Shared drive ID for a shared-drive change log"),
37478
37510
  account: accountParam
@@ -37484,7 +37516,7 @@ function registerExtraDriveTools(server) {
37484
37516
  });
37485
37517
  server.registerTool("gog_drive_changes_list", {
37486
37518
  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.",
37487
- annotations: { readOnlyHint: true },
37519
+ annotations: { readOnlyHint: true, openWorldHint: true },
37488
37520
  inputSchema: external_exports.object({
37489
37521
  token: external_exports.string().describe("Start page token or next page token"),
37490
37522
  max: external_exports.number().optional().describe("Max results (default: 100)"),
@@ -37507,7 +37539,7 @@ function registerExtraDriveTools(server) {
37507
37539
  });
37508
37540
  server.registerTool("gog_drive_revisions_list", {
37509
37541
  description: "List a file's revision history \u2014 paged revision metadata plus provider export links for each revision.",
37510
- annotations: { readOnlyHint: true },
37542
+ annotations: { readOnlyHint: true, openWorldHint: true },
37511
37543
  inputSchema: external_exports.object({
37512
37544
  fileId: external_exports.string().describe("File ID"),
37513
37545
  ...paginationParams,
@@ -37520,7 +37552,7 @@ function registerExtraDriveTools(server) {
37520
37552
  });
37521
37553
  server.registerTool("gog_drive_revisions_get", {
37522
37554
  description: "Get one revision of a Drive file by revision ID (metadata + export links). Find revision IDs with gog_drive_revisions_list.",
37523
- annotations: { readOnlyHint: true },
37555
+ annotations: { readOnlyHint: true, openWorldHint: true },
37524
37556
  inputSchema: external_exports.object({
37525
37557
  fileId: external_exports.string().describe("File ID"),
37526
37558
  revisionId: external_exports.string().describe("Revision ID"),
@@ -37531,7 +37563,7 @@ function registerExtraDriveTools(server) {
37531
37563
  });
37532
37564
  server.registerTool("gog_drive_shortcut_create", {
37533
37565
  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.",
37534
- annotations: { destructiveHint: false },
37566
+ annotations: { destructiveHint: false, openWorldHint: true },
37535
37567
  inputSchema: external_exports.object({
37536
37568
  targetId: external_exports.string().describe("File or folder ID the shortcut points to"),
37537
37569
  parent: external_exports.string().describe("Destination folder ID for the shortcut"),
@@ -37545,7 +37577,7 @@ function registerExtraDriveTools(server) {
37545
37577
  });
37546
37578
  server.registerTool("gog_drive_labels_list", {
37547
37579
  description: "List Drive label schemas available to the account.",
37548
- annotations: { readOnlyHint: true },
37580
+ annotations: { readOnlyHint: true, openWorldHint: true },
37549
37581
  inputSchema: external_exports.object({
37550
37582
  language: external_exports.string().optional().describe("BCP-47 language code"),
37551
37583
  view: external_exports.enum(["LABEL_VIEW_BASIC", "LABEL_VIEW_FULL"]).optional().describe("Label view (default: LABEL_VIEW_BASIC)"),
@@ -37571,7 +37603,7 @@ function registerExtraDriveTools(server) {
37571
37603
  });
37572
37604
  server.registerTool("gog_drive_labels_get", {
37573
37605
  description: 'Get a single Drive label schema by name (e.g. "labels/abc123").',
37574
- annotations: { readOnlyHint: true },
37606
+ annotations: { readOnlyHint: true, openWorldHint: true },
37575
37607
  inputSchema: external_exports.object({
37576
37608
  name: external_exports.string().describe("Label schema name (e.g. labels/abc123)"),
37577
37609
  language: external_exports.string().optional().describe("BCP-47 language code"),
@@ -37588,7 +37620,7 @@ function registerExtraDriveTools(server) {
37588
37620
  });
37589
37621
  server.registerTool("gog_drive_labels_file_list", {
37590
37622
  description: "List labels applied to a Drive file.",
37591
- annotations: { readOnlyHint: true },
37623
+ annotations: { readOnlyHint: true, openWorldHint: true },
37592
37624
  inputSchema: external_exports.object({
37593
37625
  fileId: external_exports.string().describe("File ID"),
37594
37626
  max: external_exports.number().optional().describe("Max results (default: 100)"),
@@ -37605,7 +37637,7 @@ function registerExtraDriveTools(server) {
37605
37637
  });
37606
37638
  server.registerTool("gog_drive_labels_file_apply", {
37607
37639
  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.',
37608
- annotations: { destructiveHint: false },
37640
+ annotations: { destructiveHint: false, openWorldHint: true },
37609
37641
  inputSchema: external_exports.object({
37610
37642
  fileId: external_exports.string().describe("File ID"),
37611
37643
  labelId: external_exports.string().describe("Label ID to apply"),
@@ -37631,7 +37663,7 @@ function registerExtraDriveTools(server) {
37631
37663
  });
37632
37664
  server.registerTool("gog_drive_labels_file_remove", {
37633
37665
  description: "Remove a label from a Drive file.",
37634
- annotations: { destructiveHint: true },
37666
+ annotations: { destructiveHint: true, openWorldHint: true },
37635
37667
  inputSchema: external_exports.object({
37636
37668
  fileId: external_exports.string().describe("File ID"),
37637
37669
  labelId: external_exports.string().describe("Label ID to remove"),
@@ -37642,7 +37674,7 @@ function registerExtraDriveTools(server) {
37642
37674
  });
37643
37675
  server.registerTool("gog_drive_activity", {
37644
37676
  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.",
37645
- annotations: { readOnlyHint: true },
37677
+ annotations: { readOnlyHint: true, openWorldHint: true },
37646
37678
  inputSchema: external_exports.object({
37647
37679
  file: external_exports.string().optional().describe("Drive file ID to query"),
37648
37680
  folder: external_exports.string().optional().describe("Drive folder ID; includes descendants"),
@@ -37674,7 +37706,7 @@ function registerExtraDriveTools(server) {
37674
37706
  });
37675
37707
  server.registerTool("gog_drive_audit_sharing", {
37676
37708
  description: "Audit Drive sharing without mutation \u2014 find public (anyone-with-link) or external permissions across a folder tree or a single file.",
37677
- annotations: { readOnlyHint: true },
37709
+ annotations: { readOnlyHint: true, openWorldHint: true },
37678
37710
  inputSchema: external_exports.object({
37679
37711
  file: external_exports.string().optional().describe("Audit one file ID instead of a folder tree"),
37680
37712
  parent: external_exports.string().optional().describe("Folder ID to scan (default: root)"),
@@ -37700,7 +37732,7 @@ function registerExtraDriveTools(server) {
37700
37732
  });
37701
37733
  server.registerTool("gog_drive_audit_user", {
37702
37734
  description: "Audit Drive sharing without mutation \u2014 find permissions granted to a specific user across a folder tree or a single file.",
37703
- annotations: { readOnlyHint: true },
37735
+ annotations: { readOnlyHint: true, openWorldHint: true },
37704
37736
  inputSchema: external_exports.object({
37705
37737
  user: external_exports.string().describe("User email to audit permissions for"),
37706
37738
  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.7.0",
6
+ "version": "4.8.0",
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/mint.yaml CHANGED
@@ -92,7 +92,7 @@ dependencies:
92
92
  # bumping these nine pins, and the pin stored on each mcp-host registration.
93
93
  - kind: github-release
94
94
  repo: openclaw/gogcli
95
- tag: v0.43.0
95
+ tag: v0.44.0
96
96
  asset: "gogcli_*_linux_amd64.tar.gz"
97
97
  bin: [gog]
98
98
  state:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-drive",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
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,8 +24,8 @@
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
- "@chrischall/mcp-utils": "^2.15.0",
28
- "@modelcontextprotocol/server": "^2.3.0",
27
+ "@chrischall/mcp-utils": "^3.0.0",
28
+ "@modelcontextprotocol/server": "^2.3.1",
29
29
  "zod": "^4.6.1"
30
30
  },
31
31
  "license": "MIT",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-drive"
9
9
  },
10
- "version": "4.7.0",
10
+ "version": "4.8.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-drive",
15
- "version": "4.7.0",
15
+ "version": "4.8.0",
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
+ });