gogcli-mcp 2.18.1 → 2.18.3
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 +9 -5
- package/dist/lib.js +9 -5
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/runner.ts +4 -1
- package/src/tools/drive.ts +15 -3
- package/src/worker.ts +1 -1
- package/tests/runner.test.ts +8 -0
- package/tests/tools/drive.test.ts +24 -2
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
10
|
-
"version": "2.18.
|
|
10
|
+
"version": "2.18.3"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
18
|
-
"version": "2.18.
|
|
18
|
+
"version": "2.18.3",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -31271,7 +31271,7 @@ async function run(args, options = {}) {
|
|
|
31271
31271
|
}
|
|
31272
31272
|
return redact(output);
|
|
31273
31273
|
} catch (err) {
|
|
31274
|
-
throw new Error(redact(err.message));
|
|
31274
|
+
throw new Error(redact(err instanceof Error ? err.message : String(err)));
|
|
31275
31275
|
}
|
|
31276
31276
|
}
|
|
31277
31277
|
async function runBinary(args, options = {}) {
|
|
@@ -32412,7 +32412,9 @@ function registerDriveTools(server) {
|
|
|
32412
32412
|
`--params=${params}`,
|
|
32413
32413
|
`--body=${body}`
|
|
32414
32414
|
],
|
|
32415
|
-
|
|
32415
|
+
// OCR on a large scanned PDF routinely outruns the default 30s.
|
|
32416
|
+
// Same precedent as the interactive auth flow in tools/auth.ts.
|
|
32417
|
+
{ account, timeout: 3e5 }
|
|
32416
32418
|
));
|
|
32417
32419
|
tempDocId = copied.id ?? copied.result?.id;
|
|
32418
32420
|
if (!tempDocId) throw new Error("OCR conversion did not return a document id");
|
|
@@ -32455,14 +32457,16 @@ function registerDriveTools(server) {
|
|
|
32455
32457
|
const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
|
|
32456
32458
|
const params = JSON.stringify({ fileId, alt: "media" });
|
|
32457
32459
|
const blob = await runBinary(["api", "call", "drive", "v3", "files.get", `--params=${params}`], { account });
|
|
32460
|
+
const type = mimeType ?? "application/octet-stream";
|
|
32461
|
+
const unrenderableHint = type.startsWith("image/") ? "" : ` If your client does not render a ${type} resource, gog_drive_extract_text returns the same file as text (PDFs included, via OCR).`;
|
|
32458
32462
|
return {
|
|
32459
32463
|
content: [
|
|
32460
|
-
{ type: "text", text: `${name ?? fileId} (${
|
|
32464
|
+
{ type: "text", text: `${name ?? fileId} (${type}) \u2014 ${Buffer.from(blob, "base64").length} bytes, base64 resource below.${unrenderableHint}` },
|
|
32461
32465
|
{
|
|
32462
32466
|
type: "resource",
|
|
32463
32467
|
resource: {
|
|
32464
32468
|
uri: `gogdrive://${fileId}/${encodeURIComponent(name ?? "file")}`,
|
|
32465
|
-
mimeType:
|
|
32469
|
+
mimeType: type,
|
|
32466
32470
|
blob
|
|
32467
32471
|
}
|
|
32468
32472
|
}
|
|
@@ -32855,7 +32859,7 @@ function registerTasksTools(server) {
|
|
|
32855
32859
|
}
|
|
32856
32860
|
|
|
32857
32861
|
// src/server.ts
|
|
32858
|
-
var VERSION = true ? "2.18.
|
|
32862
|
+
var VERSION = true ? "2.18.3" : "0.0.0";
|
|
32859
32863
|
var BASE_TOOL_REGISTRARS = [
|
|
32860
32864
|
registerApiTools,
|
|
32861
32865
|
registerAuthTools,
|
package/dist/lib.js
CHANGED
|
@@ -23169,7 +23169,7 @@ async function run(args, options = {}) {
|
|
|
23169
23169
|
}
|
|
23170
23170
|
return redact(output);
|
|
23171
23171
|
} catch (err) {
|
|
23172
|
-
throw new Error(redact(err.message));
|
|
23172
|
+
throw new Error(redact(err instanceof Error ? err.message : String(err)));
|
|
23173
23173
|
}
|
|
23174
23174
|
}
|
|
23175
23175
|
async function runBinary(args, options = {}) {
|
|
@@ -24318,7 +24318,9 @@ function registerDriveTools(server) {
|
|
|
24318
24318
|
`--params=${params}`,
|
|
24319
24319
|
`--body=${body}`
|
|
24320
24320
|
],
|
|
24321
|
-
|
|
24321
|
+
// OCR on a large scanned PDF routinely outruns the default 30s.
|
|
24322
|
+
// Same precedent as the interactive auth flow in tools/auth.ts.
|
|
24323
|
+
{ account, timeout: 3e5 }
|
|
24322
24324
|
));
|
|
24323
24325
|
tempDocId = copied.id ?? copied.result?.id;
|
|
24324
24326
|
if (!tempDocId) throw new Error("OCR conversion did not return a document id");
|
|
@@ -24361,14 +24363,16 @@ function registerDriveTools(server) {
|
|
|
24361
24363
|
const { name, mimeType } = fileMeta(await run(["drive", "get", fileId], { account }));
|
|
24362
24364
|
const params = JSON.stringify({ fileId, alt: "media" });
|
|
24363
24365
|
const blob = await runBinary(["api", "call", "drive", "v3", "files.get", `--params=${params}`], { account });
|
|
24366
|
+
const type = mimeType ?? "application/octet-stream";
|
|
24367
|
+
const unrenderableHint = type.startsWith("image/") ? "" : ` If your client does not render a ${type} resource, gog_drive_extract_text returns the same file as text (PDFs included, via OCR).`;
|
|
24364
24368
|
return {
|
|
24365
24369
|
content: [
|
|
24366
|
-
{ type: "text", text: `${name ?? fileId} (${
|
|
24370
|
+
{ type: "text", text: `${name ?? fileId} (${type}) \u2014 ${Buffer.from(blob, "base64").length} bytes, base64 resource below.${unrenderableHint}` },
|
|
24367
24371
|
{
|
|
24368
24372
|
type: "resource",
|
|
24369
24373
|
resource: {
|
|
24370
24374
|
uri: `gogdrive://${fileId}/${encodeURIComponent(name ?? "file")}`,
|
|
24371
|
-
mimeType:
|
|
24375
|
+
mimeType: type,
|
|
24372
24376
|
blob
|
|
24373
24377
|
}
|
|
24374
24378
|
}
|
|
@@ -24761,7 +24765,7 @@ function registerTasksTools(server) {
|
|
|
24761
24765
|
}
|
|
24762
24766
|
|
|
24763
24767
|
// src/server.ts
|
|
24764
|
-
var VERSION = true ? "2.18.
|
|
24768
|
+
var VERSION = true ? "2.18.3" : "0.0.0";
|
|
24765
24769
|
var BASE_TOOL_REGISTRARS = [
|
|
24766
24770
|
registerApiTools,
|
|
24767
24771
|
registerAuthTools,
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp",
|
|
5
5
|
"display_name": "gogcli",
|
|
6
|
-
"version": "2.18.
|
|
6
|
+
"version": "2.18.3",
|
|
7
7
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.18.
|
|
10
|
+
"version": "2.18.3",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp",
|
|
15
|
-
"version": "2.18.
|
|
15
|
+
"version": "2.18.3",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/runner.ts
CHANGED
|
@@ -340,7 +340,10 @@ export async function run(args: GogArg[], options: RunOptions = {}): Promise<str
|
|
|
340
340
|
}
|
|
341
341
|
return redact(output);
|
|
342
342
|
} catch (err) {
|
|
343
|
-
|
|
343
|
+
// A thrown non-Error would make `.message` undefined and redact() blow up
|
|
344
|
+
// with a TypeError, masking the real failure. Same instanceof guard the
|
|
345
|
+
// codebase already uses in errorText() (tools/utils.ts).
|
|
346
|
+
throw new Error(redact(err instanceof Error ? err.message : String(err)));
|
|
344
347
|
}
|
|
345
348
|
}
|
|
346
349
|
|
package/src/tools/drive.ts
CHANGED
|
@@ -168,7 +168,9 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
168
168
|
const copied = JSON.parse(await run(
|
|
169
169
|
['api', 'call', 'drive', 'v3', 'files.copy', '--allow-write', '--force',
|
|
170
170
|
`--params=${params}`, `--body=${body}`],
|
|
171
|
-
|
|
171
|
+
// OCR on a large scanned PDF routinely outruns the default 30s.
|
|
172
|
+
// Same precedent as the interactive auth flow in tools/auth.ts.
|
|
173
|
+
{ account, timeout: 300_000 },
|
|
172
174
|
)) as { id?: string; result?: { id?: string } };
|
|
173
175
|
tempDocId = copied.id ?? copied.result?.id;
|
|
174
176
|
if (!tempDocId) throw new Error('OCR conversion did not return a document id');
|
|
@@ -219,14 +221,24 @@ export function registerDriveTools(server: McpServer): void {
|
|
|
219
221
|
const { name, mimeType } = fileMeta(await run(['drive', 'get', fileId], { account }));
|
|
220
222
|
const params = JSON.stringify({ fileId, alt: 'media' });
|
|
221
223
|
const blob = await runBinary(['api', 'call', 'drive', 'v3', 'files.get', `--params=${params}`], { account });
|
|
224
|
+
const type = mimeType ?? 'application/octet-stream';
|
|
225
|
+
// Several hosts (claude.ai among them) render embedded IMAGE resources
|
|
226
|
+
// and reject every other type outright — "Resources of type
|
|
227
|
+
// 'application/pdf' are not currently supported". The fetch succeeded and
|
|
228
|
+
// the bytes are right here, but the caller sees only the text block, so
|
|
229
|
+
// that text has to carry the way out rather than end on "resource below".
|
|
230
|
+
const unrenderableHint = type.startsWith('image/')
|
|
231
|
+
? ''
|
|
232
|
+
: ` If your client does not render a ${type} resource, gog_drive_extract_text`
|
|
233
|
+
+ ' returns the same file as text (PDFs included, via OCR).';
|
|
222
234
|
return {
|
|
223
235
|
content: [
|
|
224
|
-
{ type: 'text', text: `${name ?? fileId} (${
|
|
236
|
+
{ type: 'text', text: `${name ?? fileId} (${type}) — ${Buffer.from(blob, 'base64').length} bytes, base64 resource below.${unrenderableHint}` },
|
|
225
237
|
{
|
|
226
238
|
type: 'resource',
|
|
227
239
|
resource: {
|
|
228
240
|
uri: `gogdrive://${fileId}/${encodeURIComponent(name ?? 'file')}`,
|
|
229
|
-
mimeType:
|
|
241
|
+
mimeType: type,
|
|
230
242
|
blob,
|
|
231
243
|
},
|
|
232
244
|
},
|
package/src/worker.ts
CHANGED
|
@@ -38,7 +38,7 @@ import { gogAuth, type GogProps } from './connector-auth.js';
|
|
|
38
38
|
// connector with all ~360 tools at once. Add whichever paths you want as separate
|
|
39
39
|
// connectors in claude.ai (each authorizes with the same connector key).
|
|
40
40
|
|
|
41
|
-
const VERSION = '2.18.
|
|
41
|
+
const VERSION = '2.18.3'; // x-release-please-version
|
|
42
42
|
|
|
43
43
|
// Build an McpAgent subclass whose init() registers `registrars` onto its server,
|
|
44
44
|
// each handler wrapped in the ALS scope carrying the per-session Fly executor.
|
package/tests/runner.test.ts
CHANGED
|
@@ -202,6 +202,14 @@ describe('run', () => {
|
|
|
202
202
|
.rejects.toThrow('gog exited with code 2');
|
|
203
203
|
});
|
|
204
204
|
|
|
205
|
+
it('surfaces a non-Error throw instead of masking it with a TypeError', async () => {
|
|
206
|
+
// A custom executor that throws a string used to reach `(err as Error).message`
|
|
207
|
+
// -> undefined -> redact(undefined) -> TypeError, hiding the real cause.
|
|
208
|
+
const spawner = vi.fn(() => { throw 'gog binary vanished'; }) as unknown as Spawner;
|
|
209
|
+
await expect(run(['sheets', 'get', 'x', 'A1'], { spawner }))
|
|
210
|
+
.rejects.toThrow('gog binary vanished');
|
|
211
|
+
});
|
|
212
|
+
|
|
205
213
|
it('rejects on spawn error', async () => {
|
|
206
214
|
const spawner = vi.fn(() => {
|
|
207
215
|
const proc = new EventEmitter() as ReturnType<Spawner>;
|
|
@@ -244,10 +244,13 @@ describe('gog_drive_extract_text', () => {
|
|
|
244
244
|
const j = JSON.parse(result.content[0].text as string);
|
|
245
245
|
expect(j).toMatchObject({ fileId: 'pdf1', name: 'Guest_Copy.pdf', mimeType: 'application/pdf', extractedVia: 'ocr-convert' });
|
|
246
246
|
expect(j.text).toBe('UTOPIA OF THE SEAS itinerary'); // BOM stripped
|
|
247
|
+
// The copy is the OCR pass itself and carries an extended timeout: a large
|
|
248
|
+
// scanned PDF routinely outruns the runner's 30s default, and the whole
|
|
249
|
+
// conversion is wasted when it does.
|
|
247
250
|
expect(runner.run).toHaveBeenCalledWith(
|
|
248
251
|
['api', 'call', 'drive', 'v3', 'files.copy', '--allow-write', '--force',
|
|
249
252
|
'--params={"fileId":"pdf1"}', '--body={"name":"gogcli-ocr-pdf1","mimeType":"application/vnd.google-apps.document"}'],
|
|
250
|
-
{ account: undefined },
|
|
253
|
+
{ account: undefined, timeout: 300_000 },
|
|
251
254
|
);
|
|
252
255
|
expect(runner.run).toHaveBeenCalledWith(
|
|
253
256
|
['api', 'call', 'drive', 'v3', 'files.export', '--params={"fileId":"tmpDoc1","mimeType":"text/plain"}'],
|
|
@@ -267,7 +270,7 @@ describe('gog_drive_extract_text', () => {
|
|
|
267
270
|
expect(runner.run).toHaveBeenCalledWith(
|
|
268
271
|
['api', 'call', 'drive', 'v3', 'files.copy', '--allow-write', '--force',
|
|
269
272
|
'--params={"fileId":"pdf1","ocrLanguage":"fr"}', '--body={"name":"gogcli-ocr-pdf1","mimeType":"application/vnd.google-apps.document"}'],
|
|
270
|
-
{ account: undefined },
|
|
273
|
+
{ account: undefined, timeout: 300_000 },
|
|
271
274
|
);
|
|
272
275
|
expect(runner.run).toHaveBeenCalledWith(['drive', 'delete', 'tmpDoc2', '--permanent', '--force'], { account: undefined });
|
|
273
276
|
});
|
|
@@ -368,6 +371,25 @@ describe('gog_drive_read_bytes', () => {
|
|
|
368
371
|
);
|
|
369
372
|
});
|
|
370
373
|
|
|
374
|
+
it('names the text fallback when the resource type is one hosts refuse to render', async () => {
|
|
375
|
+
// The bytes arrive fine; some hosts just drop a non-image embedded resource
|
|
376
|
+
// ("Resources of type 'application/pdf' are not currently supported"), and
|
|
377
|
+
// then the text block is all the caller gets.
|
|
378
|
+
vi.mocked(runner.run).mockResolvedValueOnce(JSON.stringify({ file: { name: 'a.pdf', mimeType: 'application/pdf' } }));
|
|
379
|
+
vi.mocked(runner.runBinary).mockResolvedValueOnce(Buffer.from('%PDF-1.4').toString('base64'));
|
|
380
|
+
const harness = await setupHandlers();
|
|
381
|
+
const result = await harness.callTool('gog_drive_read_bytes', { fileId: 'f1' });
|
|
382
|
+
expect(result.content[0].text).toContain('gog_drive_extract_text');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('does not add the fallback hint for an image, which hosts do render', async () => {
|
|
386
|
+
vi.mocked(runner.run).mockResolvedValueOnce(JSON.stringify({ file: { name: 'a.png', mimeType: 'image/png' } }));
|
|
387
|
+
vi.mocked(runner.runBinary).mockResolvedValueOnce(Buffer.from('x').toString('base64'));
|
|
388
|
+
const harness = await setupHandlers();
|
|
389
|
+
const result = await harness.callTool('gog_drive_read_bytes', { fileId: 'f3' });
|
|
390
|
+
expect(result.content[0].text).not.toContain('gog_drive_extract_text');
|
|
391
|
+
});
|
|
392
|
+
|
|
371
393
|
it('falls back to octet-stream and "file" when metadata lacks name/mime', async () => {
|
|
372
394
|
vi.mocked(runner.run).mockResolvedValueOnce('{}');
|
|
373
395
|
vi.mocked(runner.runBinary).mockResolvedValueOnce(Buffer.from('x').toString('base64'));
|