gogcli-mcp-drive 2.28.0 → 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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +137 -13
- package/manifest.json +4 -4
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (!
|
|
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
|
|
32039
|
-
|
|
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
|
-
|
|
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], {
|
|
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], {
|
|
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.
|
|
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.
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-drive",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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.
|
|
10
|
+
"version": "2.29.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-drive",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.29.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|