gogcli-mcp-sheets 2.8.0 → 2.18.1

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/manifest.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifest_version": "0.3",
4
4
  "name": "gogcli-mcp-sheets",
5
5
  "display_name": "gogcli (Sheets)",
6
- "version": "2.8.0",
6
+ "version": "2.18.1",
7
7
  "description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
8
8
  "author": {
9
9
  "name": "Chris Hall",
@@ -60,6 +60,18 @@
60
60
  "name": "gog_auth_add",
61
61
  "description": "Authorize a Google account via browser-based OAuth"
62
62
  },
63
+ {
64
+ "name": "gog_auth_health",
65
+ "description": "Live per-account token health: validity, age, and pre-expiry warnings (detects invalid_grant)"
66
+ },
67
+ {
68
+ "name": "gog_auth_add_url",
69
+ "description": "Begin remote/headless OAuth (step 1): return a sign-in URL to open in any browser"
70
+ },
71
+ {
72
+ "name": "gog_auth_add_complete",
73
+ "description": "Complete remote/headless OAuth (step 2): exchange the pasted redirect URL and store the token"
74
+ },
63
75
  {
64
76
  "name": "gog_auth_list",
65
77
  "description": "List all Google accounts stored in gogcli"
@@ -292,13 +304,17 @@
292
304
  "name": "gog_sheets_banding_clear",
293
305
  "description": "Remove alternating-color banding by ID or for a whole sheet."
294
306
  },
307
+ {
308
+ "name": "gog_sheets_filter_set",
309
+ "description": "Set a basic filter on a range; replacing an existing filter requires replace=true."
310
+ },
295
311
  {
296
312
  "name": "gog_sheets_conditional_format_list",
297
313
  "description": "List conditional formatting rules."
298
314
  },
299
315
  {
300
316
  "name": "gog_sheets_conditional_format_add",
301
- "description": "Add a conditional formatting rule to a range."
317
+ "description": "Add a conditional formatting rule to a range (boolean or gradient)."
302
318
  },
303
319
  {
304
320
  "name": "gog_sheets_conditional_format_clear",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gogcli-mcp-sheets",
3
- "version": "2.8.0",
3
+ "version": "2.18.1",
4
4
  "mcpName": "io.github.chrischall/gogcli-mcp-sheets",
5
5
  "description": "Extended Google Sheets MCP server via gogcli — all base tools plus full Sheets support",
6
6
  "author": "Claude Code (AI) <https://www.anthropic.com/claude>",
@@ -19,11 +19,12 @@
19
19
  "build": "tsc --noEmit && npm run bundle",
20
20
  "bundle": "node ../../scripts/bundle.js src/index.ts dist/index.js",
21
21
  "typecheck": "tsc --noEmit",
22
- "test": "vitest run",
22
+ "test": "vitest run --coverage",
23
23
  "test:watch": "vitest",
24
24
  "test:coverage": "vitest run --coverage"
25
25
  },
26
26
  "dependencies": {
27
+ "@chrischall/mcp-utils": "^0.13.3",
27
28
  "@modelcontextprotocol/sdk": "^1.29.0",
28
29
  "zod": "^4.4.3"
29
30
  },
package/server.json CHANGED
@@ -7,12 +7,12 @@
7
7
  "source": "github",
8
8
  "subfolder": "packages/gogcli-mcp-sheets"
9
9
  },
10
- "version": "2.8.0",
10
+ "version": "2.18.1",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "identifier": "gogcli-mcp-sheets",
15
- "version": "2.8.0",
15
+ "version": "2.18.1",
16
16
  "transport": {
17
17
  "type": "stdio"
18
18
  },
package/src/index.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { createServer, registerAuthTools, registerSheetsTools } from '../../gogcli-mcp/src/lib.js';
2
+ import { runMcp } from '@chrischall/mcp-utils';
3
+ import { VERSION, authToolsFor, registerSheetsTools } from '../../gogcli-mcp/src/lib.js';
4
4
  import { registerExtraSheetsTools } from './tools/sheets-extra.js';
5
5
 
6
- const server = createServer({ name: 'gogcli-sheets' });
7
- registerAuthTools(server);
8
- registerSheetsTools(server);
9
- registerExtraSheetsTools(server);
10
-
11
- const transport = new StdioServerTransport();
12
- await server.connect(transport);
6
+ await runMcp({
7
+ name: 'gogcli-sheets',
8
+ version: VERSION,
9
+ tools: [authToolsFor('sheets'), registerSheetsTools, registerExtraSheetsTools],
10
+ });
@@ -1,6 +1,15 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
- import { accountParam, runOrDiagnose, run, diagnose, toText, toError } from '../../../gogcli-mcp/src/lib.js';
3
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
4
+ import { rawTextResult } from '@chrischall/mcp-utils';
5
+ import { accountParam, runOrDiagnose, run, diagnose, errorText, payloadArg } from '../../../gogcli-mcp/src/lib.js';
6
+
7
+ // Pull the text out of a single-text-block tool result; undefined for any
8
+ // other shape (error results are still text blocks, so they parse below).
9
+ function resultText(result: CallToolResult): string | undefined {
10
+ const first = result.content[0];
11
+ return first?.type === 'text' ? first.text : undefined;
12
+ }
4
13
 
5
14
  // Convert a CSS-style hex color ("#FFF5D9", "#FD9", "FFF5D9") to the
6
15
  // {red, green, blue} 0-1 float triple that Sheets API CellFormat expects.
@@ -40,7 +49,7 @@ async function checkDateFormatTarget(
40
49
  );
41
50
  let parsed: { values?: unknown[][] };
42
51
  try {
43
- parsed = JSON.parse(peek.content[0].text) as { values?: unknown[][] };
52
+ parsed = JSON.parse(resultText(peek) ?? '') as { values?: unknown[][] };
44
53
  } catch {
45
54
  return null;
46
55
  }
@@ -104,7 +113,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
104
113
  account: accountParam,
105
114
  },
106
115
  }, async ({ spreadsheetId, tabName, account }) => {
107
- return runOrDiagnose(['sheets', 'delete-tab', spreadsheetId, tabName], { account });
116
+ return runOrDiagnose(['sheets', 'delete-tab', spreadsheetId, tabName, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
108
117
  });
109
118
 
110
119
  server.registerTool('gog_sheets_rename_tab', {
@@ -141,12 +150,14 @@ export function registerExtraSheetsTools(server: McpServer): void {
141
150
  spreadsheetId: z.string().describe('Spreadsheet ID'),
142
151
  format: z.string().optional().describe('Export format: csv, tsv, pdf (default: csv)'),
143
152
  out: z.string().optional().describe('Output file path'),
153
+ overwrite: z.boolean().optional().describe('Overwrite the output file if it already exists (gog refuses otherwise)'),
144
154
  account: accountParam,
145
155
  },
146
- }, async ({ spreadsheetId, format, out, account }) => {
156
+ }, async ({ spreadsheetId, format, out, overwrite, account }) => {
147
157
  const args = ['sheets', 'export', spreadsheetId];
148
158
  if (format) args.push(`--format=${format}`);
149
159
  if (out) args.push(`--out=${out}`);
160
+ if (overwrite) args.push('--overwrite');
150
161
  return runOrDiagnose(args, { account });
151
162
  });
152
163
 
@@ -350,7 +361,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
350
361
  if (pattern) args.push(`--pattern=${pattern}`);
351
362
  const result = await runOrDiagnose(args, { account });
352
363
  if (warning) {
353
- return { content: [{ type: 'text' as const, text: `${warning}\n\n${result.content[0].text}` }] };
364
+ return { ...result, content: [{ type: 'text' as const, text: `${warning}\n\n${resultText(result) ?? ''}` }] };
354
365
  }
355
366
  return result;
356
367
  });
@@ -422,11 +433,18 @@ export function registerExtraSheetsTools(server: McpServer): void {
422
433
  inputSchema: {
423
434
  spreadsheetId: z.string().describe('Spreadsheet ID'),
424
435
  range: z.string().describe('Cell or range to set the note on (e.g. Sheet1!A1)'),
425
- note: z.string().describe('Note text (empty string clears the note)'),
436
+ note: z.string().describe('Note text (empty string clears the note). Large notes are written to a temp file and passed to gog as --note-file automatically.'),
426
437
  account: accountParam,
427
438
  },
428
439
  }, async ({ spreadsheetId, range, note, account }) => {
429
- return runOrDiagnose(['sheets', 'update-note', spreadsheetId, range, `--note=${note}`], { account });
440
+ // payloadArg keeps normal notes on `--note=` and spills a large one to a
441
+ // temp file passed as `--note-file`, so it can't blow the argv size cap.
442
+ // The wrapper exposes no noteFile param, so there is no both-flags case to
443
+ // guard here (gog would accept both anyway, letting the file win).
444
+ return runOrDiagnose(
445
+ ['sheets', 'update-note', spreadsheetId, range, payloadArg('note', 'note-file', note)],
446
+ { account },
447
+ );
430
448
  });
431
449
 
432
450
  server.registerTool('gog_sheets_links', {
@@ -532,6 +550,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
532
550
  const args = ['sheets', 'delete-dimension', spreadsheetId, rangeOrSheet, `--dimension=${dimension}`];
533
551
  if (start !== undefined) args.push(`--start=${start}`);
534
552
  if (end !== undefined) args.push(`--end=${end}`);
553
+ args.push('--force'); // gog gates this op; without --force the runner's --no-input makes it refuse
535
554
  return runOrDiagnose(args, { account });
536
555
  });
537
556
 
@@ -706,7 +725,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
706
725
  account: accountParam,
707
726
  },
708
727
  }, async ({ spreadsheetId, chartId, account }) => {
709
- return runOrDiagnose(['sheets', 'chart', 'delete', spreadsheetId, chartId], { account });
728
+ return runOrDiagnose(['sheets', 'chart', 'delete', spreadsheetId, chartId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
710
729
  });
711
730
 
712
731
  // ---- Tables (gog 0.19.0) ----
@@ -774,7 +793,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
774
793
  account: accountParam,
775
794
  },
776
795
  }, async ({ spreadsheetId, tableId, account }) => {
777
- return runOrDiagnose(['sheets', 'table', 'clear', spreadsheetId, tableId], { account });
796
+ return runOrDiagnose(['sheets', 'table', 'clear', spreadsheetId, tableId, '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
778
797
  });
779
798
 
780
799
  server.registerTool('gog_sheets_table_delete', {
@@ -833,15 +852,15 @@ export function registerExtraSheetsTools(server: McpServer): void {
833
852
  // The table is already gone but the restore write failed. Hand the
834
853
  // read-back data straight back so it can be re-applied manually rather
835
854
  // than silently lost.
836
- return toText(
855
+ return rawTextResult(
837
856
  `Table ${tableId} was deleted, but restoring its data FAILED — re-apply the values below manually.\n\n` +
838
857
  `Range: ${range}\nValues: ${JSON.stringify(values)}\n\n` +
839
- `${toError(err).content[0].text}`,
858
+ `${errorText(err)}`,
840
859
  );
841
860
  }
842
861
  }
843
862
 
844
- return toText(`Deleted table ${tableId} and preserved ${values.length} row(s) of data (values + formulas) in ${range}.`);
863
+ return rawTextResult(`Deleted table ${tableId} and preserved ${values.length} row(s) of data (values + formulas) in ${range}.`);
845
864
  });
846
865
 
847
866
  // ---- Banding / alternating colors (gog 0.19.0) ----
@@ -891,6 +910,23 @@ export function registerExtraSheetsTools(server: McpServer): void {
891
910
  if (id !== undefined) args.push(`--id=${id}`);
892
911
  if (all) args.push('--all');
893
912
  if (sheet) args.push(`--sheet=${sheet}`);
913
+ args.push('--force'); // gog gates this op; without --force the runner's --no-input makes it refuse
914
+ return runOrDiagnose(args, { account });
915
+ });
916
+
917
+ // ---- Basic filters (gog 0.33.0) ----
918
+
919
+ server.registerTool('gog_sheets_filter_set', {
920
+ description: 'Set a basic filter on a range (the filter/sort header Sheets shows on a data range). A sheet can hold one basic filter; replacing an existing one requires replace=true — without it, gog refuses rather than silently overwriting.',
921
+ inputSchema: {
922
+ spreadsheetId: z.string().describe('Spreadsheet ID'),
923
+ range: z.string().describe('Range to filter (A1 notation with sheet name, e.g. Sheet1!A1:C100, or a named range name)'),
924
+ replace: z.boolean().optional().describe('Replace the sheet\'s existing basic filter if one is set (appends --force)'),
925
+ account: accountParam,
926
+ },
927
+ }, async ({ spreadsheetId, range, replace, account }) => {
928
+ const args = ['sheets', 'filter', 'set', spreadsheetId, range];
929
+ if (replace) args.push('--force');
894
930
  return runOrDiagnose(args, { account });
895
931
  });
896
932
 
@@ -911,7 +947,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
911
947
  });
912
948
 
913
949
  server.registerTool('gog_sheets_conditional_format_add', {
914
- description: 'Add a conditional formatting rule to a range. type picks the condition; expr is its value/formula (omit for blank/not-blank). formatJson is the CellFormat to apply when the condition matches (inline or @file). Use formatFields to force-send zero/false fields (e.g. backgroundColor,textFormat.bold).',
950
+ description: 'Add a conditional formatting rule to a range. Boolean rules: type picks the condition, expr is its value/formula (omit for blank/not-blank), formatJson is the CellFormat to apply when the condition matches (inline or @file); use formatFields to force-send zero/false fields (e.g. backgroundColor,textFormat.bold). Gradient rules (color scales): pass gradientRuleJson instead — a GradientRule JSON with minpoint/maxpoint (and optional midpoint), each {"color":{...},"type":"MIN|MAX|NUMBER|PERCENT|PERCENTILE","value":"..."}. The two modes are mutually exclusive.',
915
951
  inputSchema: {
916
952
  spreadsheetId: z.string().describe('Spreadsheet ID'),
917
953
  range: z.string().describe('Range the rule applies to (e.g. Sheet1!A1:A100)'),
@@ -919,16 +955,20 @@ export function registerExtraSheetsTools(server: McpServer): void {
919
955
  'text-eq', 'text-contains', 'text-starts-with', 'text-ends-with',
920
956
  'number-eq', 'number-gt', 'number-gte', 'number-lt', 'number-lte',
921
957
  'blank', 'not-blank', 'custom-formula',
922
- ]).describe('Rule type'),
923
- formatJson: z.string().describe('CellFormat JSON to apply when the condition matches (inline or @file)'),
958
+ ]).optional().describe('Boolean rule type (required unless gradientRuleJson is used)'),
959
+ formatJson: z.string().optional().describe('CellFormat JSON to apply when a boolean rule matches (inline or @file; required with type)'),
924
960
  expr: z.string().optional().describe('Expression value or custom formula (omit for blank/not-blank)'),
925
961
  formatFields: z.string().optional().describe('Format field mask for force-sending zero/false fields (e.g. backgroundColor,textFormat.bold)'),
962
+ gradientRuleJson: z.string().optional().describe('GradientRule JSON for gradient conditional formats (inline or @file; must include minpoint and maxpoint)'),
926
963
  account: accountParam,
927
964
  },
928
- }, async ({ spreadsheetId, range, type, formatJson, expr, formatFields, account }) => {
929
- const args = ['sheets', 'conditional-format', 'add', spreadsheetId, range, `--type=${type}`, `--format-json=${formatJson}`];
965
+ }, async ({ spreadsheetId, range, type, formatJson, expr, formatFields, gradientRuleJson, account }) => {
966
+ const args = ['sheets', 'conditional-format', 'add', spreadsheetId, range];
967
+ if (type) args.push(`--type=${type}`);
968
+ if (formatJson !== undefined) args.push(`--format-json=${formatJson}`);
930
969
  if (expr !== undefined) args.push(`--expr=${expr}`);
931
970
  if (formatFields) args.push(`--format-fields=${formatFields}`);
971
+ if (gradientRuleJson !== undefined) args.push(`--gradient-rule-json=${gradientRuleJson}`);
932
972
  return runOrDiagnose(args, { account });
933
973
  });
934
974
 
@@ -946,6 +986,7 @@ export function registerExtraSheetsTools(server: McpServer): void {
946
986
  const args = ['sheets', 'conditional-format', 'clear', spreadsheetId, `--sheet=${sheet}`];
947
987
  if (index !== undefined) args.push(`--index=${index}`);
948
988
  if (all) args.push('--all');
989
+ args.push('--force'); // gog gates this op; without --force the runner's --no-input makes it refuse
949
990
  return runOrDiagnose(args, { account });
950
991
  });
951
992