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.
@@ -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.1"
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.1",
18
+ "version": "2.18.3",
19
19
  "author": {
20
20
  "name": "Chris Hall"
21
21
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
3
  "displayName": "gogcli",
4
- "version": "2.18.1",
4
+ "version": "2.18.3",
5
5
  "description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
6
6
  "author": {
7
7
  "name": "Chris Hall",
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
- { account }
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} (${mimeType ?? "application/octet-stream"}) \u2014 ${Buffer.from(blob, "base64").length} bytes, base64 resource below.` },
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: mimeType ?? "application/octet-stream",
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.1" : "0.0.0";
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
- { account }
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} (${mimeType ?? "application/octet-stream"}) \u2014 ${Buffer.from(blob, "base64").length} bytes, base64 resource below.` },
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: mimeType ?? "application/octet-stream",
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.1" : "0.0.0";
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.1",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp",
3
- "version": "2.18.1",
3
+ "version": "2.18.3",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp",
5
5
  "description": "MCP server wrapping gogcli for Google service access",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp"
9
9
  },
10
- "version": "2.18.1",
10
+ "version": "2.18.3",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp",
15
- "version": "2.18.1",
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
- throw new Error(redact((err as Error).message));
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
 
@@ -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
- { account },
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} (${mimeType ?? 'application/octet-stream'}) — ${Buffer.from(blob, 'base64').length} bytes, base64 resource below.` },
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: mimeType ?? 'application/octet-stream',
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.1'; // x-release-please-version
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.
@@ -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'));