gogcli-mcp-drive 2.27.1 → 2.29.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": "2.27.1"
10
+ "version": "2.29.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": "2.27.1",
18
+ "version": "2.29.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": "2.27.1",
4
+ "version": "2.29.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",
package/dist/index.js CHANGED
@@ -31323,6 +31323,77 @@ function redactSecrets(text) {
31323
31323
  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(QUERY_SECRET_RE, "$1[REDACTED]").replace(AWS_SIGV4_RE, "$1[REDACTED]").replace(JSON_SECRET_DQ_RE, "$1[REDACTED]$2").replace(JSON_SECRET_SQ_RE, "$1[REDACTED]$2").replace(JWT_RE, "[REDACTED]");
31324
31324
  }
31325
31325
 
31326
+ // ../../node_modules/@chrischall/mcp-utils/dist/response/view.js
31327
+ var VIEWS = ["compact", "full", "raw"];
31328
+ var DEFAULT_VIEW = "compact";
31329
+ var BLURB = {
31330
+ compact: '"compact" (default) drops fields the response already carries elsewhere',
31331
+ full: '"full" returns every field this server understands',
31332
+ raw: '"raw" returns the upstream payload unprojected'
31333
+ };
31334
+ function viewParam(honoured, opts = {}) {
31335
+ if (honoured.length < 2) {
31336
+ throw new Error("viewParam needs at least two rungs: a parameter offering one value decides nothing");
31337
+ }
31338
+ if (!honoured.includes("compact")) {
31339
+ throw new Error('viewParam must offer "compact": a tool with no cheap rung has nothing to default to');
31340
+ }
31341
+ const ordered = VIEWS.filter((v) => honoured.includes(v));
31342
+ const sentence = `Response shape: ${ordered.map((v) => BLURB[v]).join("; ")}.`;
31343
+ return external_exports.enum(Object.fromEntries(ordered.map((v) => [v, v]))).optional().describe(opts.note ? `${sentence} ${opts.note}` : sentence);
31344
+ }
31345
+ function resolveView(value, honoured) {
31346
+ return value !== void 0 && honoured.includes(value) ? value : DEFAULT_VIEW;
31347
+ }
31348
+ function minifiedResult(data) {
31349
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
31350
+ }
31351
+
31352
+ // ../../node_modules/@chrischall/mcp-utils/dist/response/media.js
31353
+ var MEDIA_NOUN = "(?:avatar|tall_avatar|cover_photo|cover_image|picture|photo|thumbnail|thumb|image|icon|banner|profile_pic(?:ture)?|logo)";
31354
+ var MEDIA_KEY = new RegExp(`^${MEDIA_NOUN}s?(?:(?:link|uri|url)s?)?$`, "i");
31355
+ var MEDIA_URL = /^https?:\/\/[^\s]+?\.(png|jpe?g|gif|webp|svg|avif|bmp|ico)([?#]|$)/i;
31356
+ function stripMediaUrls(value, opts = {}) {
31357
+ const keep = new Set((opts.keep ?? []).map((k) => k.toLowerCase()));
31358
+ const drop = (opts.drop ?? []).map((rule) => typeof rule === "string" ? rule.toLowerCase() : new RegExp(rule.source, rule.flags));
31359
+ return walk(value, keep, drop);
31360
+ }
31361
+ function alsoDrop(key, drop) {
31362
+ const lower = key.toLowerCase();
31363
+ for (const rule of drop) {
31364
+ if (typeof rule === "string") {
31365
+ if (rule === lower)
31366
+ return true;
31367
+ continue;
31368
+ }
31369
+ rule.lastIndex = 0;
31370
+ if (rule.test(key))
31371
+ return true;
31372
+ }
31373
+ return false;
31374
+ }
31375
+ function walk(value, keep, drop) {
31376
+ if (Array.isArray(value))
31377
+ return value.map((v) => walk(v, keep, drop));
31378
+ if (value === null || typeof value !== "object")
31379
+ return value;
31380
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
31381
+ return value;
31382
+ const out = {};
31383
+ for (const [key, v] of Object.entries(value)) {
31384
+ if (keep.has(key.toLowerCase())) {
31385
+ out[key] = v;
31386
+ continue;
31387
+ }
31388
+ if (MEDIA_KEY.test(key) || alsoDrop(key, drop))
31389
+ continue;
31390
+ if (typeof v === "string" && MEDIA_URL.test(v))
31391
+ continue;
31392
+ out[key] = walk(v, keep, drop);
31393
+ }
31394
+ return out;
31395
+ }
31396
+
31326
31397
  // ../../node_modules/@chrischall/mcp-utils/dist/response/index.js
31327
31398
  function rawTextResult(text) {
31328
31399
  return { content: [{ type: "text", text }] };
@@ -31852,11 +31923,11 @@ function parseTimestampValue(key, value, assumeNaiveIn) {
31852
31923
  }
31853
31924
  return null;
31854
31925
  }
31855
- function walk(node, tz, naiveTz) {
31926
+ function walk2(node, tz, naiveTz) {
31856
31927
  let changed = false;
31857
31928
  if (Array.isArray(node)) {
31858
31929
  for (const item of node) {
31859
- if (walk(item, tz, naiveTz)) changed = true;
31930
+ if (walk2(item, tz, naiveTz)) changed = true;
31860
31931
  }
31861
31932
  return changed;
31862
31933
  }
@@ -31865,7 +31936,7 @@ function walk(node, tz, naiveTz) {
31865
31936
  for (const key of Object.keys(obj)) {
31866
31937
  const value = obj[key];
31867
31938
  if (value !== null && typeof value === "object") {
31868
- if (walk(value, tz, naiveTz)) changed = true;
31939
+ if (walk2(value, tz, naiveTz)) changed = true;
31869
31940
  continue;
31870
31941
  }
31871
31942
  if (ZONE_NAME_KEYS.has(key) || !TIMESTAMP_KEYS.has(key)) continue;
@@ -31891,7 +31962,7 @@ function normalizeTimestamps(text, tz = displayTimeZone(), naiveTz = naiveSource
31891
31962
  } catch {
31892
31963
  return text;
31893
31964
  }
31894
- if (!walk(parsed, tz, naiveTz)) return text;
31965
+ if (!walk2(parsed, tz, naiveTz)) return text;
31895
31966
  return JSON.stringify(parsed, null, detectIndent(text));
31896
31967
  }
31897
31968
 
@@ -32033,10 +32104,38 @@ ${accounts || "(none)"}${hint}`);
32033
32104
  return errorResult(`${errText}${hint}`);
32034
32105
  }
32035
32106
  }
32107
+ function minifiedOrRawText(text, stripMedia = false) {
32108
+ const trimmed = text.trim();
32109
+ if (trimmed === "" || !/^[[{]/.test(trimmed)) return rawTextResult(text);
32110
+ try {
32111
+ const parsed = JSON.parse(trimmed);
32112
+ return minifiedResult(stripMedia ? stripMediaUrls(parsed) : parsed);
32113
+ } catch {
32114
+ return rawTextResult(text);
32115
+ }
32116
+ }
32117
+ function isRejectedFieldMask(err) {
32118
+ return /invalidParameter|invalid field selection/i.test(String(err));
32119
+ }
32120
+ async function runProjected(args, options) {
32121
+ const { fieldsMask } = options;
32122
+ if (!fieldsMask) return run(args, options);
32123
+ try {
32124
+ return await run([...args, `--fields=${fieldsMask}`], options);
32125
+ } catch (err) {
32126
+ if (!isRejectedFieldMask(err)) throw err;
32127
+ process.stderr.write(
32128
+ `gogcli-mcp: Google rejected the compact field mask (${fieldsMask}); retrying unprojected
32129
+ `
32130
+ );
32131
+ return run(args, options);
32132
+ }
32133
+ }
32036
32134
  async function runOrDiagnose(args, options) {
32037
32135
  try {
32038
- const raw = await run(args, options);
32039
- return rawTextResult(options.lossless ? raw : stripConsumedPageToken(normalizeTimestamps(raw)));
32136
+ const raw = await runProjected(args, options);
32137
+ if (options.lossless) return rawTextResult(raw);
32138
+ return minifiedOrRawText(stripConsumedPageToken(normalizeTimestamps(raw)), options.stripMedia);
32040
32139
  } catch (err) {
32041
32140
  return diagnose(err);
32042
32141
  }
@@ -32286,6 +32385,7 @@ function fileMeta(raw) {
32286
32385
  const f = parsed.file ?? parsed;
32287
32386
  return { name: f.name, mimeType: f.mimeType };
32288
32387
  }
32388
+ var DRIVE_LS_COMPACT_FIELDS = "nextPageToken,files(id,name,mimeType,modifiedTime,size,webViewLink)";
32289
32389
  function registerDriveTools(server) {
32290
32390
  server.registerTool("gog_drive_ls", {
32291
32391
  description: "List files in a Google Drive folder (default: root).",
@@ -32297,9 +32397,12 @@ function registerDriveTools(server) {
32297
32397
  page: pageAliasParam,
32298
32398
  query: external_exports.string().optional().describe(`Drive query filter (e.g. "name contains 'budget'")`),
32299
32399
  allDrives: external_exports.boolean().optional().describe("Include shared drives (default: true). Set false for My Drive only."),
32400
+ view: viewParam(["compact", "full"], {
32401
+ note: "compact (the default) drops owners, parents, thumbnailLink and hasThumbnail \u2014 near-constant across a listing and 48% of its bytes. Ask for full to get them."
32402
+ }),
32300
32403
  account: accountParam
32301
32404
  }
32302
- }, async ({ folderId, max, pageToken, page, query, allDrives, account }) => {
32405
+ }, async ({ folderId, max, pageToken, page, query, allDrives, view, account }) => {
32303
32406
  const args = ["drive", "ls"];
32304
32407
  if (folderId) args.push(`--parent=${folderId}`);
32305
32408
  if (max !== void 0) args.push(`--max=${max}`);
@@ -32307,27 +32410,48 @@ function registerDriveTools(server) {
32307
32410
  if (token) args.push(`--page=${token}`);
32308
32411
  if (query) args.push(`--query=${query}`);
32309
32412
  if (allDrives === false) args.push("--no-all-drives");
32310
- return runOrDiagnose(args, { account });
32413
+ const rung = resolveView(view, ["compact", "full"]);
32414
+ return runOrDiagnose(args, {
32415
+ account,
32416
+ fieldsMask: rung === "compact" ? DRIVE_LS_COMPACT_FIELDS : void 0
32417
+ });
32311
32418
  });
32312
32419
  server.registerTool("gog_drive_search", {
32313
32420
  description: "Search Google Drive files by full-text query.",
32314
32421
  annotations: { readOnlyHint: true },
32315
32422
  inputSchema: {
32316
32423
  query: external_exports.string().describe("Search query"),
32424
+ // gog's `drive search` accepts no --fields mask, so unlike gog_drive_ls
32425
+ // this tool's compact rung is a LOCAL projection. Same vocabulary either
32426
+ // way: a caller does not need to know which lever is being pulled.
32427
+ view: viewParam(["compact", "full"], { note: "compact (the default) drops thumbnailLink \u2014 a URL a model cannot see, and 30%+ of a Drive file record. Ask for full to get it back." }),
32317
32428
  account: accountParam
32318
32429
  }
32319
- }, async ({ query, account }) => {
32320
- return runOrDiagnose(["drive", "search", query], { account });
32430
+ }, async ({ query, view, account }) => {
32431
+ return runOrDiagnose(["drive", "search", query], {
32432
+ account,
32433
+ stripMedia: resolveView(view, ["compact", "full"]) === "compact"
32434
+ });
32321
32435
  });
32322
32436
  server.registerTool("gog_drive_get", {
32323
32437
  description: "Get metadata for a Google Drive file.",
32324
32438
  annotations: { readOnlyHint: true },
32325
32439
  inputSchema: {
32326
32440
  fileId: external_exports.string().describe("File ID"),
32441
+ // A --fields mask saves only 7% here: the default set is already narrow,
32442
+ // which is why this tool takes no mask. The media strip saves 27.5% of
32443
+ // the tool's actual output, measured end to end over stdio, which is why
32444
+ // it takes a view after all. (An earlier note said 32.9%; that was a
32445
+ // minified-vs-stripped comparison of the raw gog payload rather than of
32446
+ // what the tool returns. The end-to-end figure is the one a caller sees.)
32447
+ view: viewParam(["compact", "full"], { note: "compact (the default) drops thumbnailLink \u2014 a URL a model cannot see, and 30%+ of a Drive file record. Ask for full to get it back." }),
32327
32448
  account: accountParam
32328
32449
  }
32329
- }, async ({ fileId, account }) => {
32330
- return runOrDiagnose(["drive", "get", fileId], { account });
32450
+ }, async ({ fileId, view, account }) => {
32451
+ return runOrDiagnose(["drive", "get", fileId], {
32452
+ account,
32453
+ stripMedia: resolveView(view, ["compact", "full"]) === "compact"
32454
+ });
32331
32455
  });
32332
32456
  server.registerTool("gog_drive_mkdir", {
32333
32457
  description: "Create a new folder in Google Drive.",
@@ -32526,7 +32650,7 @@ var failIfNotEmptyParam = external_exports.boolean().optional().describe(
32526
32650
  );
32527
32651
 
32528
32652
  // ../gogcli-mcp/src/server.ts
32529
- var VERSION = true ? "2.27.1" : "0.0.0";
32653
+ var VERSION = true ? "2.29.0" : "0.0.0";
32530
32654
 
32531
32655
  // ../gogcli-mcp/src/auth-log.ts
32532
32656
  var FAILURES = /* @__PURE__ */ new Set([
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": "2.27.1",
6
+ "version": "2.29.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",
@@ -91,11 +91,11 @@
91
91
  },
92
92
  {
93
93
  "name": "gog_drive_ls",
94
- "description": "List files in a Google Drive folder (with pagination and query filters)"
94
+ "description": "List files in a Google Drive folder; compact by default (view=full for owners, parents and thumbnails), with pagination and query filters"
95
95
  },
96
96
  {
97
97
  "name": "gog_drive_search",
98
- "description": "Full-text search across Drive"
98
+ "description": "Full-text search across Drive; compact by default (view=full for thumbnails)"
99
99
  },
100
100
  {
101
101
  "name": "gog_drive_extract_text",
@@ -107,7 +107,7 @@
107
107
  },
108
108
  {
109
109
  "name": "gog_drive_get",
110
- "description": "Get file metadata"
110
+ "description": "Get file metadata; compact by default (view=full for thumbnails)"
111
111
  },
112
112
  {
113
113
  "name": "gog_drive_mkdir",
package/mint.yaml CHANGED
@@ -87,7 +87,7 @@ dependencies:
87
87
  # nine pins too.
88
88
  - kind: github-release
89
89
  repo: openclaw/gogcli
90
- tag: v0.38.3
90
+ tag: v0.39.0
91
91
  asset: "gogcli_*_linux_amd64.tar.gz"
92
92
  bin: [gog]
93
93
  state:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-drive",
3
- "version": "2.27.1",
3
+ "version": "2.29.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,7 +24,7 @@
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
- "@chrischall/mcp-utils": "^0.17.1",
27
+ "@chrischall/mcp-utils": "^0.23.0",
28
28
  "@modelcontextprotocol/sdk": "^1.30.0",
29
29
  "zod": "^4.4.3"
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": "2.27.1",
10
+ "version": "2.29.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-drive",
15
- "version": "2.27.1",
15
+ "version": "2.29.0",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },