gogcli-mcp-sheets 2.23.2 → 2.24.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/README.md +35 -4
- package/SKILL.md +34 -3
- package/dist/index.js +95 -26
- package/manifest.json +21 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/sheets-extra.ts +105 -0
- package/tests/tools/sheets-extra.test.ts +99 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
|
|
10
|
-
"version": "2.
|
|
10
|
+
"version": "2.24.0"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli (Sheets)",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
|
|
18
|
-
"version": "2.
|
|
18
|
+
"version": "2.24.0",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> [!WARNING]
|
|
4
4
|
> **AI-developed project.** This codebase was built and is actively maintained by [Claude Code](https://www.anthropic.com/claude). Review all code and tool permissions before use.
|
|
5
5
|
|
|
6
|
-
Extended Google Sheets [MCP](https://modelcontextprotocol.io) server via [gogcli](https://github.com/openclaw/gogcli). Includes auth tools plus
|
|
6
|
+
Extended Google Sheets [MCP](https://modelcontextprotocol.io) server via [gogcli](https://github.com/openclaw/gogcli). Includes auth tools plus 55 additional dedicated Sheets tools for tab management, formatting, named ranges, Connected Sheets, and more.
|
|
7
7
|
|
|
8
8
|
## Requirements
|
|
9
9
|
|
|
@@ -44,9 +44,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
|
44
44
|
claude mcp add gogcli-sheets -- gogcli-mcp-sheets
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
## Extra Sheets Tools (
|
|
47
|
+
## Extra Sheets Tools (55)
|
|
48
48
|
|
|
49
|
-
Plus
|
|
49
|
+
Plus 8 auth tools and 8 base Sheets tools (get, update, append, clear, metadata, create, find-replace, run) — 71 in all.
|
|
50
50
|
|
|
51
51
|
| Tool | Description |
|
|
52
52
|
|------|-------------|
|
|
@@ -72,8 +72,39 @@ Plus 5 auth tools and 8 base Sheets tools (get, update, append, clear, metadata,
|
|
|
72
72
|
| `gog_sheets_notes` | Read cell notes |
|
|
73
73
|
| `gog_sheets_update_note` | Add or update cell notes |
|
|
74
74
|
| `gog_sheets_links` | List hyperlinks in a range |
|
|
75
|
-
| `
|
|
75
|
+
| `gog_sheets_links_set` | Set =HYPERLINK() cells in one call (batch) |
|
|
76
76
|
| `gog_sheets_snapshot` | Back up a whole spreadsheet before a risky edit |
|
|
77
|
+
| `gog_sheets_datasource_list` | List Connected Sheets data sources (BigQuery / Looker) with sheet + execution status |
|
|
78
|
+
| `gog_sheets_datasource_describe` | Full data-source spec, including its query, status and refresh schedules |
|
|
79
|
+
| `gog_sheets_datasource_table_list` | List anchored data-source tables (extracts) and their A1 anchors |
|
|
80
|
+
| `gog_sheets_datasource_table_describe` | Describe the extract anchored at an A1 cell |
|
|
81
|
+
| `gog_sheets_datasource_table_read` | Read a bounded number of rows out of an extract |
|
|
82
|
+
| `gog_sheets_list_tabs` | List tabs in a spreadsheet (sheetId, title, index, gridProperties) |
|
|
83
|
+
| `gog_sheets_copy_paste` | Copy a range's values/formulas/format to another range (tiles to fill down/across). |
|
|
84
|
+
| `gog_sheets_validation_get` | Read data-validation rules (dropdowns, checkboxes, conditions) on a range. |
|
|
85
|
+
| `gog_sheets_validation_set` | Set a data-validation rule on a range: dropdowns, checkboxes, number/date conditions, or custom formulas. |
|
|
86
|
+
| `gog_sheets_validation_clear` | Remove all data-validation rules from a range. |
|
|
87
|
+
| `gog_sheets_delete_dimension` | Delete a row or column span, table-aware: intersecting tables are shrunk and their remaining data preserved. |
|
|
88
|
+
| `gog_sheets_batch_update` | Update values in multiple ranges atomically with one Sheets API request. |
|
|
89
|
+
| `gog_sheets_reorder_tab` | Move a tab to a specific 0-based position. |
|
|
90
|
+
| `gog_sheets_chart_list` | List embedded charts in a spreadsheet. |
|
|
91
|
+
| `gog_sheets_chart_get` | Get a chart's full definition (spec + position) by chart ID. |
|
|
92
|
+
| `gog_sheets_chart_create` | Create an embedded chart from a JSON ChartSpec. |
|
|
93
|
+
| `gog_sheets_chart_update` | Replace a chart's spec by chart ID. |
|
|
94
|
+
| `gog_sheets_chart_delete` | Delete a chart by chart ID. |
|
|
95
|
+
| `gog_sheets_table_list` | List Google Sheets tables in a spreadsheet. |
|
|
96
|
+
| `gog_sheets_table_get` | Get a Google Sheets table by table ID. |
|
|
97
|
+
| `gog_sheets_table_create` | Create a Google Sheets table over a range with typed columns. |
|
|
98
|
+
| `gog_sheets_table_append` | Append data rows to a table. |
|
|
99
|
+
| `gog_sheets_table_clear` | Clear all data rows from a table. |
|
|
100
|
+
| `gog_sheets_table_delete` | Delete a Google Sheets table. By default preserves the table's cell values and formulas (emulates "Convert to range"); set keep_data=false to also wipe the data. |
|
|
101
|
+
| `gog_sheets_banding_list` | List alternating-color banded ranges. |
|
|
102
|
+
| `gog_sheets_banding_set` | Apply alternating colors to a range. |
|
|
103
|
+
| `gog_sheets_banding_clear` | Remove alternating-color banding by ID or for a whole sheet. |
|
|
104
|
+
| `gog_sheets_filter_set` | Set a basic filter on a range; replacing an existing filter requires replace=true. |
|
|
105
|
+
| `gog_sheets_conditional_format_list` | List conditional formatting rules. |
|
|
106
|
+
| `gog_sheets_conditional_format_add` | Add a conditional formatting rule to a range (boolean or gradient). |
|
|
107
|
+
| `gog_sheets_conditional_format_clear` | Remove conditional formatting rules from a sheet. |
|
|
77
108
|
|
|
78
109
|
## License
|
|
79
110
|
|
package/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: Use when the user asks to read, write, format, or manage Google She
|
|
|
5
5
|
|
|
6
6
|
# gogcli-mcp-sheets
|
|
7
7
|
|
|
8
|
-
Extended Google Sheets MCP server via [gogcli](https://github.com/openclaw/gogcli) —
|
|
8
|
+
Extended Google Sheets MCP server via [gogcli](https://github.com/openclaw/gogcli) — 71 tools: 8 auth + 8 base Sheets + 55 extra dedicated Sheets tools.
|
|
9
9
|
|
|
10
10
|
- **Source:** [github.com/chrischall/gogcli-mcp](https://github.com/chrischall/gogcli-mcp)
|
|
11
11
|
|
|
@@ -51,12 +51,43 @@ Extended Google Sheets MCP server via [gogcli](https://github.com/openclaw/gogcl
|
|
|
51
51
|
| `gog_sheets_notes` | Read cell notes |
|
|
52
52
|
| `gog_sheets_update_note` | Add or update cell notes |
|
|
53
53
|
| `gog_sheets_links` | List hyperlinks in a range |
|
|
54
|
-
| `
|
|
54
|
+
| `gog_sheets_links_set` | Set =HYPERLINK() cells in one call (batch) |
|
|
55
55
|
| `gog_sheets_snapshot` | Back up a whole spreadsheet before a risky edit |
|
|
56
|
+
| `gog_sheets_datasource_list` | List Connected Sheets data sources (BigQuery / Looker) with sheet + execution status |
|
|
57
|
+
| `gog_sheets_datasource_describe` | Full data-source spec, including its query, status and refresh schedules |
|
|
58
|
+
| `gog_sheets_datasource_table_list` | List anchored data-source tables (extracts) and their A1 anchors |
|
|
59
|
+
| `gog_sheets_datasource_table_describe` | Describe the extract anchored at an A1 cell |
|
|
60
|
+
| `gog_sheets_datasource_table_read` | Read a bounded number of rows out of an extract |
|
|
56
61
|
| `gog_sheets_named_ranges_list` | List named ranges |
|
|
57
62
|
| `gog_sheets_named_ranges_get` | Get a named range |
|
|
58
63
|
| `gog_sheets_named_ranges_add` | Create a named range |
|
|
59
64
|
| `gog_sheets_named_ranges_update` | Update a named range |
|
|
60
65
|
| `gog_sheets_named_ranges_delete` | Delete a named range |
|
|
66
|
+
| `gog_sheets_list_tabs` | List tabs in a spreadsheet (sheetId, title, index, gridProperties) |
|
|
67
|
+
| `gog_sheets_copy_paste` | Copy a range's values/formulas/format to another range (tiles to fill down/across). |
|
|
68
|
+
| `gog_sheets_validation_get` | Read data-validation rules (dropdowns, checkboxes, conditions) on a range. |
|
|
69
|
+
| `gog_sheets_validation_set` | Set a data-validation rule on a range: dropdowns, checkboxes, number/date conditions, or custom formulas. |
|
|
70
|
+
| `gog_sheets_validation_clear` | Remove all data-validation rules from a range. |
|
|
71
|
+
| `gog_sheets_delete_dimension` | Delete a row or column span, table-aware: intersecting tables are shrunk and their remaining data preserved. |
|
|
72
|
+
| `gog_sheets_batch_update` | Update values in multiple ranges atomically with one Sheets API request. |
|
|
73
|
+
| `gog_sheets_reorder_tab` | Move a tab to a specific 0-based position. |
|
|
74
|
+
| `gog_sheets_chart_list` | List embedded charts in a spreadsheet. |
|
|
75
|
+
| `gog_sheets_chart_get` | Get a chart's full definition (spec + position) by chart ID. |
|
|
76
|
+
| `gog_sheets_chart_create` | Create an embedded chart from a JSON ChartSpec. |
|
|
77
|
+
| `gog_sheets_chart_update` | Replace a chart's spec by chart ID. |
|
|
78
|
+
| `gog_sheets_chart_delete` | Delete a chart by chart ID. |
|
|
79
|
+
| `gog_sheets_table_list` | List Google Sheets tables in a spreadsheet. |
|
|
80
|
+
| `gog_sheets_table_get` | Get a Google Sheets table by table ID. |
|
|
81
|
+
| `gog_sheets_table_create` | Create a Google Sheets table over a range with typed columns. |
|
|
82
|
+
| `gog_sheets_table_append` | Append data rows to a table. |
|
|
83
|
+
| `gog_sheets_table_clear` | Clear all data rows from a table. |
|
|
84
|
+
| `gog_sheets_table_delete` | Delete a Google Sheets table. By default preserves the table's cell values and formulas (emulates "Convert to range"); set keep_data=false to also wipe the data. |
|
|
85
|
+
| `gog_sheets_banding_list` | List alternating-color banded ranges. |
|
|
86
|
+
| `gog_sheets_banding_set` | Apply alternating colors to a range. |
|
|
87
|
+
| `gog_sheets_banding_clear` | Remove alternating-color banding by ID or for a whole sheet. |
|
|
88
|
+
| `gog_sheets_filter_set` | Set a basic filter on a range; replacing an existing filter requires replace=true. |
|
|
89
|
+
| `gog_sheets_conditional_format_list` | List conditional formatting rules. |
|
|
90
|
+
| `gog_sheets_conditional_format_add` | Add a conditional formatting rule to a range (boolean or gradient). |
|
|
91
|
+
| `gog_sheets_conditional_format_clear` | Remove conditional formatting rules from a sheet. |
|
|
61
92
|
|
|
62
|
-
Plus
|
|
93
|
+
Plus 8 auth tools and 8 base Sheets tools.
|
package/dist/index.js
CHANGED
|
@@ -31731,6 +31731,7 @@ function formatAuthHealth(raw, now) {
|
|
|
31731
31731
|
// ../gogcli-mcp/src/tools/auth.ts
|
|
31732
31732
|
function registerAuthToolsWith(server, defaultServices) {
|
|
31733
31733
|
const servicesDescribe = `Services to authorize: "all" or comma-separated list (e.g. "sheets,gmail,calendar"). Default: "${defaultServices}". Prefer the narrowest set you need \u2014 requesting a service whose Google API is not enabled on the OAuth client's project makes Google reject the WHOLE request with invalid_scope.`;
|
|
31734
|
+
const extraScopesDescribe = "Additional raw OAuth scope URIs to request, comma-separated, on top of the ones `services` implies. Use for scopes no service covers \u2014 e.g. https://www.googleapis.com/auth/bigquery.readonly, required before gog_sheets_datasource_* can read BigQuery-backed Connected Sheets. Leave unset otherwise: an extra scope whose API is not enabled on the OAuth client project makes Google reject the WHOLE authorization with invalid_scope.";
|
|
31734
31735
|
server.registerTool("gog_auth_list", {
|
|
31735
31736
|
description: "List the Google accounts stored in gogcli, with their scopes. This reads local configuration only \u2014 it does not contact Google and does NOT tell you whether an account still works: a signed-out account whose refresh token expired or was revoked is listed here exactly like a healthy one, scopes and all. Use gog_auth_health to check whether an account can actually authenticate.",
|
|
31736
31737
|
annotations: { readOnlyHint: true },
|
|
@@ -31780,11 +31781,14 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31780
31781
|
annotations: { destructiveHint: true },
|
|
31781
31782
|
inputSchema: {
|
|
31782
31783
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
31783
|
-
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
|
|
31784
|
+
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
31785
|
+
extraScopes: external_exports.string().optional().describe(extraScopesDescribe)
|
|
31784
31786
|
}
|
|
31785
|
-
}, async ({ email: email3, services = defaultServices }) => {
|
|
31787
|
+
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
31786
31788
|
try {
|
|
31787
|
-
|
|
31789
|
+
const args = ["auth", "add", email3, "--services", services];
|
|
31790
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`, "--force-consent");
|
|
31791
|
+
return rawTextResult(await run(args, {
|
|
31788
31792
|
interactive: true,
|
|
31789
31793
|
timeout: 3e5
|
|
31790
31794
|
}));
|
|
@@ -31796,14 +31800,14 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31796
31800
|
description: "Begin REMOTE/headless Google authorization (step 1 of 2). Returns a sign-in URL to open in any browser \u2014 no local server or terminal on the gogcli host is needed, so this works over the hosted connector where the interactive gog_auth_add cannot. Hand the URL to the user; after they sign in, the browser is redirected to a localhost URL that fails to load \u2014 that is expected. They copy that full redirected URL (from the address bar) and you pass it to gog_auth_add_complete. The link is valid for 10 minutes. If you pass a custom `services` here, pass the SAME value to gog_auth_add_complete or the second step will not match this one.",
|
|
31797
31801
|
inputSchema: {
|
|
31798
31802
|
email: external_exports.string().describe("Google account email to authorize"),
|
|
31799
|
-
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe)
|
|
31803
|
+
services: external_exports.string().optional().default(defaultServices).describe(servicesDescribe),
|
|
31804
|
+
extraScopes: external_exports.string().optional().describe(`${extraScopesDescribe} Pass the SAME value to gog_auth_add_complete.`)
|
|
31800
31805
|
}
|
|
31801
|
-
}, async ({ email: email3, services = defaultServices }) => {
|
|
31806
|
+
}, async ({ email: email3, services = defaultServices, extraScopes }) => {
|
|
31802
31807
|
try {
|
|
31803
|
-
|
|
31804
|
-
|
|
31805
|
-
|
|
31806
|
-
));
|
|
31808
|
+
const args = ["auth", "add", email3, "--remote", "--step", "1", "--services", services, "--force-consent"];
|
|
31809
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
31810
|
+
return rawTextResult(await run(args, { redactMode: "tokens" }));
|
|
31807
31811
|
} catch (err) {
|
|
31808
31812
|
return errorResult(errorText(err));
|
|
31809
31813
|
}
|
|
@@ -31818,25 +31822,28 @@ function registerAuthToolsWith(server, defaultServices) {
|
|
|
31818
31822
|
),
|
|
31819
31823
|
services: external_exports.string().optional().default(defaultServices).describe(
|
|
31820
31824
|
`Services authorized \u2014 MUST match the value passed to gog_auth_add_url. Default: "${defaultServices}".`
|
|
31825
|
+
),
|
|
31826
|
+
extraScopes: external_exports.string().optional().describe(
|
|
31827
|
+
"Extra OAuth scope URIs \u2014 MUST match the value passed to gog_auth_add_url, for the same reason `services` must: the two steps have to describe the same grant."
|
|
31821
31828
|
)
|
|
31822
31829
|
}
|
|
31823
|
-
}, async ({ email: email3, redirectUrl, services = defaultServices }) => {
|
|
31830
|
+
}, async ({ email: email3, redirectUrl, services = defaultServices, extraScopes }) => {
|
|
31824
31831
|
try {
|
|
31825
|
-
|
|
31826
|
-
|
|
31827
|
-
|
|
31828
|
-
|
|
31829
|
-
|
|
31830
|
-
|
|
31831
|
-
|
|
31832
|
-
|
|
31833
|
-
|
|
31834
|
-
|
|
31835
|
-
|
|
31836
|
-
|
|
31837
|
-
|
|
31838
|
-
|
|
31839
|
-
));
|
|
31832
|
+
const args = [
|
|
31833
|
+
"auth",
|
|
31834
|
+
"add",
|
|
31835
|
+
email3,
|
|
31836
|
+
"--remote",
|
|
31837
|
+
"--step",
|
|
31838
|
+
"2",
|
|
31839
|
+
"--auth-url",
|
|
31840
|
+
redirectUrl,
|
|
31841
|
+
"--services",
|
|
31842
|
+
services,
|
|
31843
|
+
"--force-consent"
|
|
31844
|
+
];
|
|
31845
|
+
if (extraScopes) args.push(`--extra-scopes=${extraScopes}`);
|
|
31846
|
+
return rawTextResult(await run(args));
|
|
31840
31847
|
} catch (err) {
|
|
31841
31848
|
return errorResult(errorText(err));
|
|
31842
31849
|
}
|
|
@@ -32020,7 +32027,7 @@ function registerSheetsTools(server) {
|
|
|
32020
32027
|
}
|
|
32021
32028
|
|
|
32022
32029
|
// ../gogcli-mcp/src/server.ts
|
|
32023
|
-
var VERSION = true ? "2.
|
|
32030
|
+
var VERSION = true ? "2.24.0" : "0.0.0";
|
|
32024
32031
|
|
|
32025
32032
|
// ../gogcli-mcp/src/auth-log.ts
|
|
32026
32033
|
var FAILURES = /* @__PURE__ */ new Set([
|
|
@@ -33393,6 +33400,68 @@ ${errorText(err)}`
|
|
|
33393
33400
|
if (parent) args.push(`--parent=${parent}`);
|
|
33394
33401
|
return runOrDiagnose(args, { account });
|
|
33395
33402
|
});
|
|
33403
|
+
const bigQueryScopeNote = " BIGQUERY-BACKED SOURCES NEED AN EXTRA SCOPE: ordinary sheets authorization does not request https://www.googleapis.com/auth/bigquery.readonly, and Google refuses the whole response without it. A permission error here means the token is missing that scope, NOT that the spreadsheet has no data sources \u2014 re-authorize with gog_auth_add_url/gog_auth_add_complete (or gog_auth_add), keeping the account's EXISTING services selection and passing extraScopes=https://www.googleapis.com/auth/bigquery.readonly with forceConsent. Looker sources reuse the account's existing Looker link and need nothing extra.";
|
|
33404
|
+
server.registerTool("gog_sheets_datasource_list", {
|
|
33405
|
+
description: "List the Connected Sheets data sources in a spreadsheet (BigQuery or Looker), each joined with its DATA_SOURCE sheet and current execution status. This is the discovery call \u2014 start here to get the dataSourceId the describe tool wants. Deliberately does not print custom SQL; use gog_sheets_datasource_describe for that." + bigQueryScopeNote,
|
|
33406
|
+
annotations: { readOnlyHint: true },
|
|
33407
|
+
inputSchema: {
|
|
33408
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
33409
|
+
account: accountParam
|
|
33410
|
+
}
|
|
33411
|
+
}, async ({ spreadsheetId, account }) => {
|
|
33412
|
+
return runOrDiagnose(["sheets", "datasource", "list", spreadsheetId], { account });
|
|
33413
|
+
});
|
|
33414
|
+
server.registerTool("gog_sheets_datasource_describe", {
|
|
33415
|
+
description: "Describe one Connected Sheets data source in full: the complete API DataSource spec (including the BigQuery raw query, which gog_sheets_datasource_list withholds), its sheet properties, its DataExecutionStatus and any refresh schedules. Refreshes are asynchronous and this tool cannot start one \u2014 poll it until state is SUCCEEDED or FAILED, and read the status error text when it is FAILED." + bigQueryScopeNote,
|
|
33416
|
+
annotations: { readOnlyHint: true },
|
|
33417
|
+
inputSchema: {
|
|
33418
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
33419
|
+
dataSourceId: external_exports.string().describe("Data source ID, as reported by gog_sheets_datasource_list"),
|
|
33420
|
+
account: accountParam
|
|
33421
|
+
}
|
|
33422
|
+
}, async ({ spreadsheetId, dataSourceId, account }) => {
|
|
33423
|
+
return runOrDiagnose(["sheets", "datasource", "describe", spreadsheetId, dataSourceId], { account });
|
|
33424
|
+
});
|
|
33425
|
+
server.registerTool("gog_sheets_datasource_table_list", {
|
|
33426
|
+
description: `List the data-source tables \u2014 "extracts" in the Sheets editor \u2014 anchored in a spreadsheet. A data-source table has NO id of its own in the Sheets API: its definition lives on its top-left cell, so every extract is identified by a sheet-qualified A1 anchor (e.g. "Extracts!B3"). That anchor is what the describe and read tools take. Filter with dataSourceId to see only one source's extracts.` + bigQueryScopeNote,
|
|
33427
|
+
annotations: { readOnlyHint: true },
|
|
33428
|
+
inputSchema: {
|
|
33429
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
33430
|
+
dataSourceId: external_exports.string().optional().describe("Only list tables belonging to this data source ID"),
|
|
33431
|
+
account: accountParam
|
|
33432
|
+
}
|
|
33433
|
+
}, async ({ spreadsheetId, dataSourceId, account }) => {
|
|
33434
|
+
const args = ["sheets", "datasource", "table", "list", spreadsheetId];
|
|
33435
|
+
if (dataSourceId) args.push(`--data-source-id=${dataSourceId}`);
|
|
33436
|
+
return runOrDiagnose(args, { account });
|
|
33437
|
+
});
|
|
33438
|
+
server.registerTool("gog_sheets_datasource_table_describe", {
|
|
33439
|
+
description: "Describe the data-source table (extract) anchored at an A1 cell: its source, configured columns, sort/filter spec and row limit. Read this before gog_sheets_datasource_table_read when you need to know what the columns MEAN \u2014 the read returns values, not the extract's definition." + bigQueryScopeNote,
|
|
33440
|
+
annotations: { readOnlyHint: true },
|
|
33441
|
+
inputSchema: {
|
|
33442
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
33443
|
+
anchor: external_exports.string().describe(`Sheet-qualified A1 anchor of the extract's top-left cell, e.g. "Extracts!B3". Get it from gog_sheets_datasource_table_list \u2014 an extract has no other identifier.`),
|
|
33444
|
+
account: accountParam
|
|
33445
|
+
}
|
|
33446
|
+
}, async ({ spreadsheetId, anchor, account }) => {
|
|
33447
|
+
return runOrDiagnose(["sheets", "datasource", "table", "describe", spreadsheetId, anchor], { account });
|
|
33448
|
+
});
|
|
33449
|
+
server.registerTool("gog_sheets_datasource_table_read", {
|
|
33450
|
+
description: 'Read the values out of the data-source table (extract) anchored at an A1 cell. Bounded on purpose: at most maxRows data rows (gog default 1000) plus the header row, which is returned separately. The JSON reports "truncated": true when the configured extract can hold more rows than were returned \u2014 treat that exactly like a truncated search, i.e. do not conclude a value is absent from it; raise maxRows or narrow the extract instead. Use render=UNFORMATTED_VALUE for arithmetic (FORMATTED_VALUE returns display strings) or render=FORMULA to see the cell formulas.' + bigQueryScopeNote,
|
|
33451
|
+
annotations: { readOnlyHint: true },
|
|
33452
|
+
inputSchema: {
|
|
33453
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
33454
|
+
anchor: external_exports.string().describe(`Sheet-qualified A1 anchor of the extract's top-left cell, e.g. "Extracts!B3" (from gog_sheets_datasource_table_list)`),
|
|
33455
|
+
maxRows: external_exports.number().int().positive().optional().describe("Maximum data rows to read (gog default: 1000). The header row is returned separately and does not count against this."),
|
|
33456
|
+
render: external_exports.enum(["FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA"]).optional().describe("How cell values are rendered (gog default: FORMATTED_VALUE)"),
|
|
33457
|
+
account: accountParam
|
|
33458
|
+
}
|
|
33459
|
+
}, async ({ spreadsheetId, anchor, maxRows, render, account }) => {
|
|
33460
|
+
const args = ["sheets", "datasource", "table", "read", spreadsheetId, anchor];
|
|
33461
|
+
if (maxRows !== void 0) args.push(`--max-rows=${maxRows}`);
|
|
33462
|
+
if (render) args.push(`--render=${render}`);
|
|
33463
|
+
return runOrDiagnose(args, { account });
|
|
33464
|
+
});
|
|
33396
33465
|
}
|
|
33397
33466
|
|
|
33398
33467
|
// src/index.ts
|
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.24.0",
|
|
7
7
|
"description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -319,6 +319,26 @@
|
|
|
319
319
|
{
|
|
320
320
|
"name": "gog_sheets_conditional_format_clear",
|
|
321
321
|
"description": "Remove conditional formatting rules from a sheet."
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"name": "gog_sheets_datasource_list",
|
|
325
|
+
"description": "List Connected Sheets data sources (BigQuery/Looker) with their sheet and execution status"
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
"name": "gog_sheets_datasource_describe",
|
|
329
|
+
"description": "Describe one Connected Sheets data source in full, including its query, status and refresh schedules"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
"name": "gog_sheets_datasource_table_list",
|
|
333
|
+
"description": "List anchored data-source tables (extracts) and the A1 anchors that identify them"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"name": "gog_sheets_datasource_table_describe",
|
|
337
|
+
"description": "Describe the data-source table (extract) anchored at an A1 cell"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"name": "gog_sheets_datasource_table_read",
|
|
341
|
+
"description": "Read a bounded number of rows out of a data-source table (extract)"
|
|
322
342
|
}
|
|
323
343
|
],
|
|
324
344
|
"compatibility": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-sheets",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.24.0",
|
|
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>",
|
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.24.0",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp-sheets",
|
|
15
|
-
"version": "2.
|
|
15
|
+
"version": "2.24.0",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
|
@@ -1006,4 +1006,109 @@ export function registerExtraSheetsTools(server: McpServer): void {
|
|
|
1006
1006
|
return runOrDiagnose(args, { account });
|
|
1007
1007
|
});
|
|
1008
1008
|
|
|
1009
|
+
// Connected Sheets (gog >= 0.37.0, openclaw/gogcli#938). Read-only by
|
|
1010
|
+
// construction — gog exposes no create/update/refresh/delete here, so none of
|
|
1011
|
+
// these can change a data source or trigger a query.
|
|
1012
|
+
//
|
|
1013
|
+
// The scope is the thing to know before calling any of them: Google requires
|
|
1014
|
+
// https://www.googleapis.com/auth/bigquery.readonly whenever a Sheets
|
|
1015
|
+
// response CONTAINS BigQuery Connected Sheets data, and ordinary `sheets`
|
|
1016
|
+
// authorization deliberately does not ask for it. So an account that reads
|
|
1017
|
+
// every other part of this spreadsheet still gets a permission error here,
|
|
1018
|
+
// and re-authorizing is the only fix. gog's error names the scope and the
|
|
1019
|
+
// exact command; the note below is so the model does not first conclude the
|
|
1020
|
+
// spreadsheet has no data sources.
|
|
1021
|
+
const bigQueryScopeNote =
|
|
1022
|
+
' BIGQUERY-BACKED SOURCES NEED AN EXTRA SCOPE: ordinary sheets authorization does not request ' +
|
|
1023
|
+
'https://www.googleapis.com/auth/bigquery.readonly, and Google refuses the whole response without it. A permission ' +
|
|
1024
|
+
'error here means the token is missing that scope, NOT that the spreadsheet has no data sources — re-authorize with ' +
|
|
1025
|
+
'gog_auth_add_url/gog_auth_add_complete (or gog_auth_add), keeping the account\'s EXISTING services selection and ' +
|
|
1026
|
+
'passing extraScopes=https://www.googleapis.com/auth/bigquery.readonly with forceConsent. Looker sources reuse the ' +
|
|
1027
|
+
'account\'s existing Looker link and need nothing extra.';
|
|
1028
|
+
|
|
1029
|
+
server.registerTool('gog_sheets_datasource_list', {
|
|
1030
|
+
description:
|
|
1031
|
+
'List the Connected Sheets data sources in a spreadsheet (BigQuery or Looker), each joined with its DATA_SOURCE sheet ' +
|
|
1032
|
+
'and current execution status. This is the discovery call — start here to get the dataSourceId the describe tool wants. ' +
|
|
1033
|
+
'Deliberately does not print custom SQL; use gog_sheets_datasource_describe for that.' + bigQueryScopeNote,
|
|
1034
|
+
annotations: { readOnlyHint: true },
|
|
1035
|
+
inputSchema: {
|
|
1036
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1037
|
+
account: accountParam,
|
|
1038
|
+
},
|
|
1039
|
+
}, async ({ spreadsheetId, account }) => {
|
|
1040
|
+
return runOrDiagnose(['sheets', 'datasource', 'list', spreadsheetId], { account });
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
server.registerTool('gog_sheets_datasource_describe', {
|
|
1044
|
+
description:
|
|
1045
|
+
'Describe one Connected Sheets data source in full: the complete API DataSource spec (including the BigQuery raw query, ' +
|
|
1046
|
+
'which gog_sheets_datasource_list withholds), its sheet properties, its DataExecutionStatus and any refresh schedules. ' +
|
|
1047
|
+
'Refreshes are asynchronous and this tool cannot start one — poll it until state is SUCCEEDED or FAILED, and read the ' +
|
|
1048
|
+
'status error text when it is FAILED.' + bigQueryScopeNote,
|
|
1049
|
+
annotations: { readOnlyHint: true },
|
|
1050
|
+
inputSchema: {
|
|
1051
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1052
|
+
dataSourceId: z.string().describe('Data source ID, as reported by gog_sheets_datasource_list'),
|
|
1053
|
+
account: accountParam,
|
|
1054
|
+
},
|
|
1055
|
+
}, async ({ spreadsheetId, dataSourceId, account }) => {
|
|
1056
|
+
return runOrDiagnose(['sheets', 'datasource', 'describe', spreadsheetId, dataSourceId], { account });
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
server.registerTool('gog_sheets_datasource_table_list', {
|
|
1060
|
+
description:
|
|
1061
|
+
'List the data-source tables — "extracts" in the Sheets editor — anchored in a spreadsheet. A data-source table has NO ' +
|
|
1062
|
+
'id of its own in the Sheets API: its definition lives on its top-left cell, so every extract is identified by a ' +
|
|
1063
|
+
'sheet-qualified A1 anchor (e.g. "Extracts!B3"). That anchor is what the describe and read tools take. Filter with ' +
|
|
1064
|
+
'dataSourceId to see only one source\'s extracts.' + bigQueryScopeNote,
|
|
1065
|
+
annotations: { readOnlyHint: true },
|
|
1066
|
+
inputSchema: {
|
|
1067
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1068
|
+
dataSourceId: z.string().optional().describe('Only list tables belonging to this data source ID'),
|
|
1069
|
+
account: accountParam,
|
|
1070
|
+
},
|
|
1071
|
+
}, async ({ spreadsheetId, dataSourceId, account }) => {
|
|
1072
|
+
const args = ['sheets', 'datasource', 'table', 'list', spreadsheetId];
|
|
1073
|
+
if (dataSourceId) args.push(`--data-source-id=${dataSourceId}`);
|
|
1074
|
+
return runOrDiagnose(args, { account });
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1077
|
+
server.registerTool('gog_sheets_datasource_table_describe', {
|
|
1078
|
+
description:
|
|
1079
|
+
'Describe the data-source table (extract) anchored at an A1 cell: its source, configured columns, sort/filter spec and ' +
|
|
1080
|
+
'row limit. Read this before gog_sheets_datasource_table_read when you need to know what the columns MEAN — the read ' +
|
|
1081
|
+
'returns values, not the extract\'s definition.' + bigQueryScopeNote,
|
|
1082
|
+
annotations: { readOnlyHint: true },
|
|
1083
|
+
inputSchema: {
|
|
1084
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1085
|
+
anchor: z.string().describe('Sheet-qualified A1 anchor of the extract\'s top-left cell, e.g. "Extracts!B3". Get it from gog_sheets_datasource_table_list — an extract has no other identifier.'),
|
|
1086
|
+
account: accountParam,
|
|
1087
|
+
},
|
|
1088
|
+
}, async ({ spreadsheetId, anchor, account }) => {
|
|
1089
|
+
return runOrDiagnose(['sheets', 'datasource', 'table', 'describe', spreadsheetId, anchor], { account });
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
server.registerTool('gog_sheets_datasource_table_read', {
|
|
1093
|
+
description:
|
|
1094
|
+
'Read the values out of the data-source table (extract) anchored at an A1 cell. Bounded on purpose: at most maxRows data ' +
|
|
1095
|
+
'rows (gog default 1000) plus the header row, which is returned separately. The JSON reports "truncated": true when the ' +
|
|
1096
|
+
'configured extract can hold more rows than were returned — treat that exactly like a truncated search, i.e. do not ' +
|
|
1097
|
+
'conclude a value is absent from it; raise maxRows or narrow the extract instead. Use render=UNFORMATTED_VALUE for ' +
|
|
1098
|
+
'arithmetic (FORMATTED_VALUE returns display strings) or render=FORMULA to see the cell formulas.' + bigQueryScopeNote,
|
|
1099
|
+
annotations: { readOnlyHint: true },
|
|
1100
|
+
inputSchema: {
|
|
1101
|
+
spreadsheetId: z.string().describe('Spreadsheet ID'),
|
|
1102
|
+
anchor: z.string().describe('Sheet-qualified A1 anchor of the extract\'s top-left cell, e.g. "Extracts!B3" (from gog_sheets_datasource_table_list)'),
|
|
1103
|
+
maxRows: z.number().int().positive().optional().describe('Maximum data rows to read (gog default: 1000). The header row is returned separately and does not count against this.'),
|
|
1104
|
+
render: z.enum(['FORMATTED_VALUE', 'UNFORMATTED_VALUE', 'FORMULA']).optional().describe('How cell values are rendered (gog default: FORMATTED_VALUE)'),
|
|
1105
|
+
account: accountParam,
|
|
1106
|
+
},
|
|
1107
|
+
}, async ({ spreadsheetId, anchor, maxRows, render, account }) => {
|
|
1108
|
+
const args = ['sheets', 'datasource', 'table', 'read', spreadsheetId, anchor];
|
|
1109
|
+
if (maxRows !== undefined) args.push(`--max-rows=${maxRows}`);
|
|
1110
|
+
if (render) args.push(`--render=${render}`);
|
|
1111
|
+
return runOrDiagnose(args, { account });
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1009
1114
|
}
|
|
@@ -1512,3 +1512,102 @@ describe('non-text result passthrough', () => {
|
|
|
1512
1512
|
expect(result.content[0].text).toMatch(/warning/i);
|
|
1513
1513
|
});
|
|
1514
1514
|
});
|
|
1515
|
+
|
|
1516
|
+
// Connected Sheets (gog >= 0.37.0, openclaw/gogcli#938). Read-only discovery
|
|
1517
|
+
// and bounded extract reads. An extract has no id of its own in the Sheets API,
|
|
1518
|
+
// so the anchor is a POSITIONAL argument, not a flag — getting that wrong makes
|
|
1519
|
+
// gog reject the call, which is why the arg order is pinned here.
|
|
1520
|
+
describe('gog_sheets_datasource_list', () => {
|
|
1521
|
+
it('calls runOrDiagnose with the spreadsheet id', async () => {
|
|
1522
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1523
|
+
const harness = await setupHandlers();
|
|
1524
|
+
await harness.callTool('gog_sheets_datasource_list', { spreadsheetId: 'sid' });
|
|
1525
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'datasource', 'list', 'sid'], { account: undefined });
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it('forwards account', async () => {
|
|
1529
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1530
|
+
const harness = await setupHandlers();
|
|
1531
|
+
await harness.callTool('gog_sheets_datasource_list', { spreadsheetId: 'sid', account: 'me@x.com' });
|
|
1532
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(['sheets', 'datasource', 'list', 'sid'], { account: 'me@x.com' });
|
|
1533
|
+
});
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
describe('gog_sheets_datasource_describe', () => {
|
|
1537
|
+
it('passes the data source id positionally after the spreadsheet id', async () => {
|
|
1538
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1539
|
+
const harness = await setupHandlers();
|
|
1540
|
+
await harness.callTool('gog_sheets_datasource_describe', { spreadsheetId: 'sid', dataSourceId: 'ds1' });
|
|
1541
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1542
|
+
['sheets', 'datasource', 'describe', 'sid', 'ds1'],
|
|
1543
|
+
{ account: undefined },
|
|
1544
|
+
);
|
|
1545
|
+
});
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
describe('gog_sheets_datasource_table_list', () => {
|
|
1549
|
+
it('omits --data-source-id when no filter is given', async () => {
|
|
1550
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1551
|
+
const harness = await setupHandlers();
|
|
1552
|
+
await harness.callTool('gog_sheets_datasource_table_list', { spreadsheetId: 'sid' });
|
|
1553
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1554
|
+
['sheets', 'datasource', 'table', 'list', 'sid'],
|
|
1555
|
+
{ account: undefined },
|
|
1556
|
+
);
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
it('filters by data source id when given', async () => {
|
|
1560
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1561
|
+
const harness = await setupHandlers();
|
|
1562
|
+
await harness.callTool('gog_sheets_datasource_table_list', { spreadsheetId: 'sid', dataSourceId: 'ds1' });
|
|
1563
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1564
|
+
['sheets', 'datasource', 'table', 'list', 'sid', '--data-source-id=ds1'],
|
|
1565
|
+
{ account: undefined },
|
|
1566
|
+
);
|
|
1567
|
+
});
|
|
1568
|
+
});
|
|
1569
|
+
|
|
1570
|
+
describe('gog_sheets_datasource_table_describe', () => {
|
|
1571
|
+
it('passes the A1 anchor positionally', async () => {
|
|
1572
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1573
|
+
const harness = await setupHandlers();
|
|
1574
|
+
await harness.callTool('gog_sheets_datasource_table_describe', { spreadsheetId: 'sid', anchor: 'Extracts!B3' });
|
|
1575
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1576
|
+
['sheets', 'datasource', 'table', 'describe', 'sid', 'Extracts!B3'],
|
|
1577
|
+
{ account: undefined },
|
|
1578
|
+
);
|
|
1579
|
+
});
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
describe('gog_sheets_datasource_table_read', () => {
|
|
1583
|
+
it('reads with gog defaults when no bounds are given', async () => {
|
|
1584
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1585
|
+
const harness = await setupHandlers();
|
|
1586
|
+
await harness.callTool('gog_sheets_datasource_table_read', { spreadsheetId: 'sid', anchor: 'Extracts!B3' });
|
|
1587
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1588
|
+
['sheets', 'datasource', 'table', 'read', 'sid', 'Extracts!B3'],
|
|
1589
|
+
{ account: undefined },
|
|
1590
|
+
);
|
|
1591
|
+
});
|
|
1592
|
+
|
|
1593
|
+
it('passes --max-rows and --render', async () => {
|
|
1594
|
+
vi.mocked(lib.runOrDiagnose).mockResolvedValue(rawTextResult('{}'));
|
|
1595
|
+
const harness = await setupHandlers();
|
|
1596
|
+
await harness.callTool('gog_sheets_datasource_table_read', {
|
|
1597
|
+
spreadsheetId: 'sid', anchor: 'Extracts!B3', maxRows: 250, render: 'UNFORMATTED_VALUE',
|
|
1598
|
+
});
|
|
1599
|
+
expect(lib.runOrDiagnose).toHaveBeenCalledWith(
|
|
1600
|
+
['sheets', 'datasource', 'table', 'read', 'sid', 'Extracts!B3', '--max-rows=250', '--render=UNFORMATTED_VALUE'],
|
|
1601
|
+
{ account: undefined },
|
|
1602
|
+
);
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
it('rejects a render mode gog does not accept', async () => {
|
|
1606
|
+
const harness = await setupHandlers();
|
|
1607
|
+
const result = await harness.callTool('gog_sheets_datasource_table_read', {
|
|
1608
|
+
spreadsheetId: 'sid', anchor: 'Extracts!B3', render: 'RAW',
|
|
1609
|
+
});
|
|
1610
|
+
expect(result.isError).toBe(true);
|
|
1611
|
+
expect(lib.runOrDiagnose).not.toHaveBeenCalled();
|
|
1612
|
+
});
|
|
1613
|
+
});
|