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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +19992 -19655
- package/manifest.json +18 -2
- package/package.json +3 -2
- package/server.json +2 -2
- package/src/index.ts +7 -9
- package/src/tools/sheets-extra.ts +58 -17
- package/tests/tools/sheets-extra.test.ts +506 -412
- package/tsconfig.json +1 -1
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.
|
|
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.
|
|
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.
|
|
10
|
+
"version": "2.18.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-sheets",
|
|
15
|
-
"version": "2.
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
`${
|
|
858
|
+
`${errorText(err)}`,
|
|
840
859
|
);
|
|
841
860
|
}
|
|
842
861
|
}
|
|
843
862
|
|
|
844
|
-
return
|
|
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
|
|
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('
|
|
923
|
-
formatJson: z.string().describe('CellFormat JSON to apply when
|
|
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
|
|
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
|
|