mcp-gsheets 1.8.1 → 1.10.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/README.md +65 -0
- package/dist/index.js +641 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -312,6 +312,62 @@ cp .env.example .env
|
|
|
312
312
|
npm run dev # Watch mode with auto-reload
|
|
313
313
|
```
|
|
314
314
|
|
|
315
|
+
## 🎚️ Reducing context cost with toolsets
|
|
316
|
+
|
|
317
|
+
All 44 tools together cost about **9,900 tokens of context in every session**,
|
|
318
|
+
before the model does anything. Most workflows need a fraction of that.
|
|
319
|
+
`GSHEETS_TOOLSETS` limits which tools the server exposes:
|
|
320
|
+
|
|
321
|
+
```json
|
|
322
|
+
{
|
|
323
|
+
"mcpServers": {
|
|
324
|
+
"gsheets": {
|
|
325
|
+
"command": "npx",
|
|
326
|
+
"args": ["mcp-gsheets"],
|
|
327
|
+
"env": {
|
|
328
|
+
"GOOGLE_PROJECT_ID": "your-project-id",
|
|
329
|
+
"GOOGLE_APPLICATION_CREDENTIALS": "/path/to/key.json",
|
|
330
|
+
"GSHEETS_TOOLSETS": "core,charts"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
| Toolset | Tools | What it covers |
|
|
338
|
+
|---|---|---|
|
|
339
|
+
| `core` | 11 | Read/write values, metadata, sheet structure, create spreadsheet |
|
|
340
|
+
| `sheets` | 9 | Sheet lifecycle, rows and columns |
|
|
341
|
+
| `formatting` | 15 | Colours, borders, merges, conditional rules, links, dates |
|
|
342
|
+
| `charts` | 3 | Create, update, delete charts |
|
|
343
|
+
| `tables` | 4 | Native tables |
|
|
344
|
+
| `analysis` | 2 | Full-sheet snapshot, range comparison |
|
|
345
|
+
|
|
346
|
+
Measured `tools/list` cost:
|
|
347
|
+
|
|
348
|
+
| Configuration | Tools | ≈ Tokens |
|
|
349
|
+
|---|---|---|
|
|
350
|
+
| _unset_ (default, all toolsets) | 44 | 9,875 |
|
|
351
|
+
| `GSHEETS_TOOLSETS=core` | 11 | 1,986 |
|
|
352
|
+
| `GSHEETS_TOOLSETS=core,sheets` | 20 | 3,548 |
|
|
353
|
+
| `GSHEETS_READ_ONLY=true` | 16 | 3,599 |
|
|
354
|
+
| `GSHEETS_TOOLSETS=core` + read-only | 6 | 912 |
|
|
355
|
+
|
|
356
|
+
Notes:
|
|
357
|
+
|
|
358
|
+
- **The default is unchanged** — leave `GSHEETS_TOOLSETS` unset and you get
|
|
359
|
+
every tool, exactly as before.
|
|
360
|
+
- **`core` is always included.** `GSHEETS_TOOLSETS=charts` means "charts as
|
|
361
|
+
well as core", not "charts only" — without core the server cannot read a cell.
|
|
362
|
+
- **A typo is a startup error**, not a silently smaller tool list.
|
|
363
|
+
- `GSHEETS_READ_ONLY=true` drops every writing tool and can be combined with
|
|
364
|
+
`GSHEETS_TOOLSETS`. It is enforced when a tool is called, not just when the
|
|
365
|
+
list is built, so a client cannot write by naming a hidden tool.
|
|
366
|
+
|
|
367
|
+
All tools also carry MCP annotations (`readOnlyHint`, `destructiveHint`,
|
|
368
|
+
`idempotentHint`), so clients can skip confirmation prompts on reads and warn
|
|
369
|
+
before destructive operations.
|
|
370
|
+
|
|
315
371
|
## 📋 Available Tools
|
|
316
372
|
|
|
317
373
|
### Reading Data
|
|
@@ -360,6 +416,15 @@ npm run dev # Watch mode with auto-reload
|
|
|
360
416
|
| `sheets_unmerge_cells` | Unmerge previously merged cells in a range | `spreadsheetId`, `range` |
|
|
361
417
|
| `sheets_add_conditional_formatting` | Add a conditional formatting rule (gradient or boolean) to a range | `spreadsheetId`, `range`, `rule` |
|
|
362
418
|
|
|
419
|
+
### Native Tables
|
|
420
|
+
|
|
421
|
+
| Tool | Description | Key Parameters |
|
|
422
|
+
|------|-------------|----------------|
|
|
423
|
+
| `sheets_add_table` | Create a native Google Sheets table with typed columns and optional dropdown values | `spreadsheetId`, `sheetName`, `range`, `name`, `columns` |
|
|
424
|
+
| `sheets_update_table` | Update an existing native Google Sheets table by table ID using an explicit field mask | `spreadsheetId`, `tableId`, `fields`, `name`, `range`, `columns` |
|
|
425
|
+
| `sheets_delete_table` | Delete a native Google Sheets table by table ID | `spreadsheetId`, `tableId` |
|
|
426
|
+
| `sheets_get_tables` | Read native tables for a spreadsheet or a specific sheet | `spreadsheetId`, `sheetName` |
|
|
427
|
+
|
|
363
428
|
### Charts
|
|
364
429
|
|
|
365
430
|
| Tool | Description | Key Parameters |
|
package/dist/index.js
CHANGED
|
@@ -18036,6 +18036,141 @@ function validateAuth() {
|
|
|
18036
18036
|
}
|
|
18037
18037
|
}
|
|
18038
18038
|
|
|
18039
|
+
// src/config/toolsets.ts
|
|
18040
|
+
var TOOLSET_NAMES = [
|
|
18041
|
+
"core",
|
|
18042
|
+
"sheets",
|
|
18043
|
+
"formatting",
|
|
18044
|
+
"charts",
|
|
18045
|
+
"tables",
|
|
18046
|
+
"analysis"
|
|
18047
|
+
];
|
|
18048
|
+
var TOOLSETS = {
|
|
18049
|
+
/** Reading and writing cells, plus the metadata needed to do it safely. */
|
|
18050
|
+
core: [
|
|
18051
|
+
"sheets_check_access",
|
|
18052
|
+
"sheets_get_metadata",
|
|
18053
|
+
"sheets_get_sheet_structure",
|
|
18054
|
+
"sheets_get_sheet_dimensions",
|
|
18055
|
+
"sheets_get_values",
|
|
18056
|
+
"sheets_batch_get_values",
|
|
18057
|
+
"sheets_update_values",
|
|
18058
|
+
"sheets_batch_update_values",
|
|
18059
|
+
"sheets_append_values",
|
|
18060
|
+
"sheets_clear_values",
|
|
18061
|
+
"sheets_create_spreadsheet"
|
|
18062
|
+
],
|
|
18063
|
+
/** Sheet lifecycle and row/column structure. */
|
|
18064
|
+
sheets: [
|
|
18065
|
+
"sheets_insert_sheet",
|
|
18066
|
+
"sheets_delete_sheet",
|
|
18067
|
+
"sheets_duplicate_sheet",
|
|
18068
|
+
"sheets_copy_to",
|
|
18069
|
+
"sheets_update_sheet_properties",
|
|
18070
|
+
"sheets_batch_delete_sheets",
|
|
18071
|
+
"sheets_insert_rows",
|
|
18072
|
+
"sheets_delete_rows",
|
|
18073
|
+
"sheets_delete_columns"
|
|
18074
|
+
],
|
|
18075
|
+
/** Cell appearance: colours, borders, merges, conditional rules, links. */
|
|
18076
|
+
formatting: [
|
|
18077
|
+
"sheets_format_cells",
|
|
18078
|
+
"sheets_batch_format_cells",
|
|
18079
|
+
"sheets_update_borders",
|
|
18080
|
+
"sheets_get_border_map",
|
|
18081
|
+
"sheets_merge_cells",
|
|
18082
|
+
"sheets_unmerge_cells",
|
|
18083
|
+
"sheets_get_merged_cells",
|
|
18084
|
+
"sheets_get_sheet_formatting",
|
|
18085
|
+
"sheets_get_formatting_compact",
|
|
18086
|
+
"sheets_add_conditional_formatting",
|
|
18087
|
+
"sheets_get_conditional_formatting",
|
|
18088
|
+
"sheets_get_data_validation",
|
|
18089
|
+
"sheets_get_basic_filter",
|
|
18090
|
+
"sheets_insert_link",
|
|
18091
|
+
"sheets_insert_date"
|
|
18092
|
+
],
|
|
18093
|
+
charts: ["sheets_create_chart", "sheets_update_chart", "sheets_delete_chart"],
|
|
18094
|
+
tables: ["sheets_add_table", "sheets_update_table", "sheets_delete_table", "sheets_get_tables"],
|
|
18095
|
+
/** Whole-sheet reads and diffing — powerful, but the heaviest schemas. */
|
|
18096
|
+
analysis: ["sheets_get_full_sheet_snapshot", "sheets_compare_ranges"]
|
|
18097
|
+
};
|
|
18098
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
|
|
18099
|
+
"sheets_check_access",
|
|
18100
|
+
"sheets_get_metadata",
|
|
18101
|
+
"sheets_get_sheet_structure",
|
|
18102
|
+
"sheets_get_sheet_dimensions",
|
|
18103
|
+
"sheets_get_values",
|
|
18104
|
+
"sheets_batch_get_values",
|
|
18105
|
+
"sheets_get_border_map",
|
|
18106
|
+
"sheets_get_merged_cells",
|
|
18107
|
+
"sheets_get_sheet_formatting",
|
|
18108
|
+
"sheets_get_formatting_compact",
|
|
18109
|
+
"sheets_get_conditional_formatting",
|
|
18110
|
+
"sheets_get_data_validation",
|
|
18111
|
+
"sheets_get_basic_filter",
|
|
18112
|
+
"sheets_get_tables",
|
|
18113
|
+
"sheets_get_full_sheet_snapshot",
|
|
18114
|
+
"sheets_compare_ranges"
|
|
18115
|
+
]);
|
|
18116
|
+
var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
18117
|
+
"sheets_clear_values",
|
|
18118
|
+
"sheets_delete_sheet",
|
|
18119
|
+
"sheets_batch_delete_sheets",
|
|
18120
|
+
"sheets_delete_rows",
|
|
18121
|
+
"sheets_delete_columns",
|
|
18122
|
+
"sheets_delete_chart",
|
|
18123
|
+
"sheets_delete_table",
|
|
18124
|
+
"sheets_unmerge_cells"
|
|
18125
|
+
]);
|
|
18126
|
+
var ToolsetConfigError = class extends Error {
|
|
18127
|
+
};
|
|
18128
|
+
function parseList(raw) {
|
|
18129
|
+
return raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
18130
|
+
}
|
|
18131
|
+
function resolveToolsets(env = process.env) {
|
|
18132
|
+
const raw = env.GSHEETS_TOOLSETS?.trim();
|
|
18133
|
+
const readOnly = env.GSHEETS_READ_ONLY?.trim().toLowerCase() === "true";
|
|
18134
|
+
let enabled;
|
|
18135
|
+
if (!raw || raw.toLowerCase() === "all") {
|
|
18136
|
+
enabled = [...TOOLSET_NAMES];
|
|
18137
|
+
} else {
|
|
18138
|
+
const requested = parseList(raw);
|
|
18139
|
+
const unknown2 = requested.filter((n) => !TOOLSET_NAMES.includes(n));
|
|
18140
|
+
if (unknown2.length > 0) {
|
|
18141
|
+
throw new ToolsetConfigError(
|
|
18142
|
+
`Unknown toolset${unknown2.length > 1 ? "s" : ""}: ${unknown2.join(", ")}. Valid toolsets: ${TOOLSET_NAMES.join(", ")}, all.`
|
|
18143
|
+
);
|
|
18144
|
+
}
|
|
18145
|
+
const set = new Set(requested);
|
|
18146
|
+
set.add("core");
|
|
18147
|
+
enabled = TOOLSET_NAMES.filter((n) => set.has(n));
|
|
18148
|
+
}
|
|
18149
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
18150
|
+
for (const name of enabled) {
|
|
18151
|
+
for (const tool of TOOLSETS[name]) {
|
|
18152
|
+
if (readOnly && !READ_ONLY_TOOLS.has(tool)) {
|
|
18153
|
+
continue;
|
|
18154
|
+
}
|
|
18155
|
+
allowed.add(tool);
|
|
18156
|
+
}
|
|
18157
|
+
}
|
|
18158
|
+
return { enabled, readOnly, allowed };
|
|
18159
|
+
}
|
|
18160
|
+
function annotationsFor(toolName) {
|
|
18161
|
+
if (READ_ONLY_TOOLS.has(toolName)) {
|
|
18162
|
+
return { readOnlyHint: true, idempotentHint: true };
|
|
18163
|
+
}
|
|
18164
|
+
const annotations = {};
|
|
18165
|
+
if (!DESTRUCTIVE_TOOLS.has(toolName)) {
|
|
18166
|
+
annotations.destructiveHint = false;
|
|
18167
|
+
}
|
|
18168
|
+
if (toolName !== "sheets_append_values") {
|
|
18169
|
+
annotations.idempotentHint = true;
|
|
18170
|
+
}
|
|
18171
|
+
return annotations;
|
|
18172
|
+
}
|
|
18173
|
+
|
|
18039
18174
|
// src/utils/error-handler.ts
|
|
18040
18175
|
function handleError(error2) {
|
|
18041
18176
|
console.error("Error in Google Sheets operation:", error2);
|
|
@@ -18319,7 +18454,7 @@ async function getSheetId(sheets, spreadsheetId, sheetName) {
|
|
|
18319
18454
|
const sheetsData = response.data.sheets || [];
|
|
18320
18455
|
if (sheetName) {
|
|
18321
18456
|
const sheet = sheetsData.find((s) => s.properties?.title === sheetName);
|
|
18322
|
-
if (
|
|
18457
|
+
if (sheet?.properties?.sheetId === void 0 || sheet.properties.sheetId === null) {
|
|
18323
18458
|
const availableSheets = sheetsData.map((s) => s.properties?.title).filter((title) => title).join(", ");
|
|
18324
18459
|
throw new Error(`Sheet "${sheetName}" not found. Available sheets: ${availableSheets}`);
|
|
18325
18460
|
}
|
|
@@ -20130,6 +20265,480 @@ async function addConditionalFormattingHandler(input) {
|
|
|
20130
20265
|
}
|
|
20131
20266
|
}
|
|
20132
20267
|
|
|
20268
|
+
// src/utils/table-helpers.ts
|
|
20269
|
+
var TABLE_COLUMN_TYPES = [
|
|
20270
|
+
"COLUMN_TYPE_UNSPECIFIED",
|
|
20271
|
+
"DOUBLE",
|
|
20272
|
+
"CURRENCY",
|
|
20273
|
+
"PERCENT",
|
|
20274
|
+
"DATE",
|
|
20275
|
+
"TIME",
|
|
20276
|
+
"DATE_TIME",
|
|
20277
|
+
"TEXT",
|
|
20278
|
+
"BOOLEAN",
|
|
20279
|
+
"DROPDOWN",
|
|
20280
|
+
"FILES_CHIP",
|
|
20281
|
+
"PEOPLE_CHIP",
|
|
20282
|
+
"FINANCE_CHIP",
|
|
20283
|
+
"PLACE_CHIP",
|
|
20284
|
+
"RATINGS_CHIP"
|
|
20285
|
+
];
|
|
20286
|
+
function resolveTableRangeInput(sheetName, range) {
|
|
20287
|
+
const extracted = extractSheetName(range);
|
|
20288
|
+
if (extracted.sheetName && extracted.sheetName !== sheetName) {
|
|
20289
|
+
throw new Error(`Range sheet "${extracted.sheetName}" does not match sheetName "${sheetName}"`);
|
|
20290
|
+
}
|
|
20291
|
+
return {
|
|
20292
|
+
sheetName: extracted.sheetName ?? sheetName,
|
|
20293
|
+
range: extracted.range
|
|
20294
|
+
};
|
|
20295
|
+
}
|
|
20296
|
+
function buildTableColumnProperties(columns) {
|
|
20297
|
+
return columns.map((column, index) => {
|
|
20298
|
+
const columnProperties = {
|
|
20299
|
+
columnIndex: index,
|
|
20300
|
+
columnName: column.name,
|
|
20301
|
+
columnType: column.columnType
|
|
20302
|
+
};
|
|
20303
|
+
if (column.dropdownValues && column.dropdownValues.length > 0) {
|
|
20304
|
+
columnProperties.dataValidationRule = {
|
|
20305
|
+
condition: {
|
|
20306
|
+
type: "ONE_OF_LIST",
|
|
20307
|
+
values: column.dropdownValues.map((value) => ({ userEnteredValue: value }))
|
|
20308
|
+
}
|
|
20309
|
+
};
|
|
20310
|
+
}
|
|
20311
|
+
return columnProperties;
|
|
20312
|
+
});
|
|
20313
|
+
}
|
|
20314
|
+
function getRangeColumnCount(range) {
|
|
20315
|
+
if (range.startColumnIndex === void 0 || range.startColumnIndex === null || range.endColumnIndex === void 0 || range.endColumnIndex === null) {
|
|
20316
|
+
return void 0;
|
|
20317
|
+
}
|
|
20318
|
+
return range.endColumnIndex - range.startColumnIndex;
|
|
20319
|
+
}
|
|
20320
|
+
function validateTableGridRange(range, rangeText) {
|
|
20321
|
+
const startRow = range.startRowIndex;
|
|
20322
|
+
const endRow = range.endRowIndex;
|
|
20323
|
+
const startColumn = range.startColumnIndex;
|
|
20324
|
+
const endColumn = range.endColumnIndex;
|
|
20325
|
+
if (startRow === void 0 || startRow === null || endRow === void 0 || endRow === null || startColumn === void 0 || startColumn === null || endColumn === void 0 || endColumn === null || startRow < 0 || startColumn < 0 || endRow <= startRow || endColumn <= startColumn) {
|
|
20326
|
+
throw new Error(`Invalid table range: ${rangeText}. Use a rectangular A1 range like "A1:D20".`);
|
|
20327
|
+
}
|
|
20328
|
+
}
|
|
20329
|
+
function validateColumnCount(range, columns, rangeText) {
|
|
20330
|
+
const columnCount = getRangeColumnCount(range);
|
|
20331
|
+
if (columnCount !== void 0 && columnCount !== columns.length) {
|
|
20332
|
+
throw new Error(
|
|
20333
|
+
`Column count mismatch: range ${rangeText} spans ${columnCount} column(s), but ${columns.length} column definition(s) were provided`
|
|
20334
|
+
);
|
|
20335
|
+
}
|
|
20336
|
+
}
|
|
20337
|
+
function rangesOverlap(first, second) {
|
|
20338
|
+
if (first.sheetId !== void 0 && first.sheetId !== null && second.sheetId !== void 0 && second.sheetId !== null && first.sheetId !== second.sheetId) {
|
|
20339
|
+
return false;
|
|
20340
|
+
}
|
|
20341
|
+
const firstStartRow = first.startRowIndex ?? 0;
|
|
20342
|
+
const firstEndRow = first.endRowIndex ?? Number.POSITIVE_INFINITY;
|
|
20343
|
+
const firstStartColumn = first.startColumnIndex ?? 0;
|
|
20344
|
+
const firstEndColumn = first.endColumnIndex ?? Number.POSITIVE_INFINITY;
|
|
20345
|
+
const secondStartRow = second.startRowIndex ?? 0;
|
|
20346
|
+
const secondEndRow = second.endRowIndex ?? Number.POSITIVE_INFINITY;
|
|
20347
|
+
const secondStartColumn = second.startColumnIndex ?? 0;
|
|
20348
|
+
const secondEndColumn = second.endColumnIndex ?? Number.POSITIVE_INFINITY;
|
|
20349
|
+
return firstStartRow < secondEndRow && firstEndRow > secondStartRow && firstStartColumn < secondEndColumn && firstEndColumn > secondStartColumn;
|
|
20350
|
+
}
|
|
20351
|
+
function ensureNoOverlappingTable(tables, range, sheetName, excludeTableId) {
|
|
20352
|
+
const overlappingTable = tables.find(
|
|
20353
|
+
(table) => table.tableId !== excludeTableId && table.range && rangesOverlap(table.range, range)
|
|
20354
|
+
);
|
|
20355
|
+
if (!overlappingTable) {
|
|
20356
|
+
return;
|
|
20357
|
+
}
|
|
20358
|
+
const existingRange = overlappingTable.range ? gridRangeToA1(overlappingTable.range) : "unknown";
|
|
20359
|
+
const requestedRange = gridRangeToA1(range);
|
|
20360
|
+
const tableLabel = overlappingTable.name || overlappingTable.tableId || "unnamed table";
|
|
20361
|
+
throw new Error(
|
|
20362
|
+
`Table range ${sheetName}!${requestedRange} overlaps existing table "${tableLabel}" (${sheetName}!${existingRange}). Choose a non-overlapping range or update/delete the existing table.`
|
|
20363
|
+
);
|
|
20364
|
+
}
|
|
20365
|
+
function formatTableForResponse(table, sheetName) {
|
|
20366
|
+
const range = table.range ? gridRangeToA1(table.range) : null;
|
|
20367
|
+
return {
|
|
20368
|
+
tableId: table.tableId ?? null,
|
|
20369
|
+
name: table.name ?? null,
|
|
20370
|
+
range: range && sheetName ? `${sheetName}!${range}` : range,
|
|
20371
|
+
gridRange: table.range ?? null,
|
|
20372
|
+
columnProperties: table.columnProperties ?? []
|
|
20373
|
+
};
|
|
20374
|
+
}
|
|
20375
|
+
|
|
20376
|
+
// src/tools/add-table.ts
|
|
20377
|
+
var columnSchema = external_exports.object({
|
|
20378
|
+
name: external_exports.string().min(1),
|
|
20379
|
+
columnType: external_exports.enum(TABLE_COLUMN_TYPES),
|
|
20380
|
+
dropdownValues: external_exports.array(external_exports.string()).optional()
|
|
20381
|
+
});
|
|
20382
|
+
var addTableInputSchema = external_exports.object({
|
|
20383
|
+
spreadsheetId: external_exports.string().min(1),
|
|
20384
|
+
sheetName: external_exports.string().min(1),
|
|
20385
|
+
range: external_exports.string().min(1),
|
|
20386
|
+
name: external_exports.string().min(1),
|
|
20387
|
+
columns: external_exports.array(columnSchema).min(1)
|
|
20388
|
+
});
|
|
20389
|
+
var addTableTool = {
|
|
20390
|
+
name: "sheets_add_table",
|
|
20391
|
+
description: "Create a native Google Sheets table with typed columns and optional dropdown values",
|
|
20392
|
+
inputSchema: {
|
|
20393
|
+
type: "object",
|
|
20394
|
+
properties: {
|
|
20395
|
+
spreadsheetId: {
|
|
20396
|
+
type: "string",
|
|
20397
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
20398
|
+
},
|
|
20399
|
+
sheetName: {
|
|
20400
|
+
type: "string",
|
|
20401
|
+
description: "Name of the sheet (tab) where the table will be created"
|
|
20402
|
+
},
|
|
20403
|
+
range: {
|
|
20404
|
+
type: "string",
|
|
20405
|
+
description: 'A1 notation range for the table, e.g. "A1:D20" or "Sheet1!A1:D20"'
|
|
20406
|
+
},
|
|
20407
|
+
name: {
|
|
20408
|
+
type: "string",
|
|
20409
|
+
description: "Unique table name within the spreadsheet"
|
|
20410
|
+
},
|
|
20411
|
+
columns: {
|
|
20412
|
+
type: "array",
|
|
20413
|
+
description: "Column definitions for the table",
|
|
20414
|
+
items: {
|
|
20415
|
+
type: "object",
|
|
20416
|
+
properties: {
|
|
20417
|
+
name: { type: "string", description: "Column name" },
|
|
20418
|
+
columnType: {
|
|
20419
|
+
type: "string",
|
|
20420
|
+
enum: [...TABLE_COLUMN_TYPES],
|
|
20421
|
+
description: "Google Sheets table column type"
|
|
20422
|
+
},
|
|
20423
|
+
dropdownValues: {
|
|
20424
|
+
type: "array",
|
|
20425
|
+
items: { type: "string" },
|
|
20426
|
+
description: "Allowed values for DROPDOWN columns"
|
|
20427
|
+
}
|
|
20428
|
+
},
|
|
20429
|
+
required: ["name", "columnType"]
|
|
20430
|
+
}
|
|
20431
|
+
}
|
|
20432
|
+
},
|
|
20433
|
+
required: ["spreadsheetId", "sheetName", "range", "name", "columns"]
|
|
20434
|
+
}
|
|
20435
|
+
};
|
|
20436
|
+
async function addTableHandler(input) {
|
|
20437
|
+
try {
|
|
20438
|
+
const validatedInput = addTableInputSchema.parse(input);
|
|
20439
|
+
const sheets = await getAuthenticatedClient();
|
|
20440
|
+
const { sheetName, range } = resolveTableRangeInput(
|
|
20441
|
+
validatedInput.sheetName,
|
|
20442
|
+
validatedInput.range
|
|
20443
|
+
);
|
|
20444
|
+
const sheetId = await getSheetId(sheets, validatedInput.spreadsheetId, sheetName);
|
|
20445
|
+
const gridRange = parseRange(range, sheetId);
|
|
20446
|
+
validateTableGridRange(gridRange, `${sheetName}!${range}`);
|
|
20447
|
+
validateColumnCount(gridRange, validatedInput.columns, `${sheetName}!${range}`);
|
|
20448
|
+
const metadataResponse = await sheets.spreadsheets.get({
|
|
20449
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20450
|
+
fields: "sheets.properties.title,sheets.properties.sheetId,sheets.tables"
|
|
20451
|
+
});
|
|
20452
|
+
const sheetData = findSheetOrThrow(metadataResponse.data.sheets ?? [], sheetName);
|
|
20453
|
+
ensureNoOverlappingTable(sheetData.tables ?? [], gridRange, sheetName);
|
|
20454
|
+
const table = {
|
|
20455
|
+
name: validatedInput.name,
|
|
20456
|
+
range: gridRange,
|
|
20457
|
+
columnProperties: buildTableColumnProperties(validatedInput.columns)
|
|
20458
|
+
};
|
|
20459
|
+
const response = await sheets.spreadsheets.batchUpdate({
|
|
20460
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20461
|
+
requestBody: {
|
|
20462
|
+
requests: [
|
|
20463
|
+
{
|
|
20464
|
+
addTable: {
|
|
20465
|
+
table
|
|
20466
|
+
}
|
|
20467
|
+
}
|
|
20468
|
+
]
|
|
20469
|
+
}
|
|
20470
|
+
});
|
|
20471
|
+
const addedTable = response.data.replies?.[0]?.addTable?.table ?? table;
|
|
20472
|
+
return formatToolResponse(`Successfully created table "${validatedInput.name}"`, {
|
|
20473
|
+
spreadsheetId: response.data.spreadsheetId,
|
|
20474
|
+
table: formatTableForResponse(addedTable, sheetName)
|
|
20475
|
+
});
|
|
20476
|
+
} catch (error2) {
|
|
20477
|
+
return handleError(error2);
|
|
20478
|
+
}
|
|
20479
|
+
}
|
|
20480
|
+
|
|
20481
|
+
// src/tools/update-table.ts
|
|
20482
|
+
var columnSchema2 = external_exports.object({
|
|
20483
|
+
name: external_exports.string().min(1),
|
|
20484
|
+
columnType: external_exports.enum(TABLE_COLUMN_TYPES),
|
|
20485
|
+
dropdownValues: external_exports.array(external_exports.string()).optional()
|
|
20486
|
+
});
|
|
20487
|
+
var updateTableInputSchema = external_exports.object({
|
|
20488
|
+
spreadsheetId: external_exports.string().min(1),
|
|
20489
|
+
tableId: external_exports.string().min(1),
|
|
20490
|
+
fields: external_exports.string().min(1),
|
|
20491
|
+
sheetName: external_exports.string().min(1).optional(),
|
|
20492
|
+
range: external_exports.string().min(1).optional(),
|
|
20493
|
+
name: external_exports.string().min(1).optional(),
|
|
20494
|
+
columns: external_exports.array(columnSchema2).min(1).optional()
|
|
20495
|
+
}).refine((value) => value.name || value.range || value.columns, {
|
|
20496
|
+
message: "At least one of name, range, or columns must be provided"
|
|
20497
|
+
});
|
|
20498
|
+
var updateTableTool = {
|
|
20499
|
+
name: "sheets_update_table",
|
|
20500
|
+
description: "Update an existing native Google Sheets table by tableId",
|
|
20501
|
+
inputSchema: {
|
|
20502
|
+
type: "object",
|
|
20503
|
+
properties: {
|
|
20504
|
+
spreadsheetId: {
|
|
20505
|
+
type: "string",
|
|
20506
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
20507
|
+
},
|
|
20508
|
+
tableId: {
|
|
20509
|
+
type: "string",
|
|
20510
|
+
description: "The ID of the table to update"
|
|
20511
|
+
},
|
|
20512
|
+
fields: {
|
|
20513
|
+
type: "string",
|
|
20514
|
+
description: 'Required field mask for the table update, e.g. "name", "range", "columnProperties", or "name,range"'
|
|
20515
|
+
},
|
|
20516
|
+
sheetName: {
|
|
20517
|
+
type: "string",
|
|
20518
|
+
description: "Name of the target sheet when updating the table range"
|
|
20519
|
+
},
|
|
20520
|
+
range: {
|
|
20521
|
+
type: "string",
|
|
20522
|
+
description: 'Optional new A1 notation range for the table, e.g. "A1:D20"'
|
|
20523
|
+
},
|
|
20524
|
+
name: {
|
|
20525
|
+
type: "string",
|
|
20526
|
+
description: "Optional new table name"
|
|
20527
|
+
},
|
|
20528
|
+
columns: {
|
|
20529
|
+
type: "array",
|
|
20530
|
+
description: "Optional replacement column definitions for the table",
|
|
20531
|
+
items: {
|
|
20532
|
+
type: "object",
|
|
20533
|
+
properties: {
|
|
20534
|
+
name: { type: "string", description: "Column name" },
|
|
20535
|
+
columnType: {
|
|
20536
|
+
type: "string",
|
|
20537
|
+
enum: [...TABLE_COLUMN_TYPES],
|
|
20538
|
+
description: "Google Sheets table column type"
|
|
20539
|
+
},
|
|
20540
|
+
dropdownValues: {
|
|
20541
|
+
type: "array",
|
|
20542
|
+
items: { type: "string" },
|
|
20543
|
+
description: "Allowed values for DROPDOWN columns"
|
|
20544
|
+
}
|
|
20545
|
+
},
|
|
20546
|
+
required: ["name", "columnType"]
|
|
20547
|
+
}
|
|
20548
|
+
}
|
|
20549
|
+
},
|
|
20550
|
+
required: ["spreadsheetId", "tableId", "fields"]
|
|
20551
|
+
}
|
|
20552
|
+
};
|
|
20553
|
+
async function updateTableHandler(input) {
|
|
20554
|
+
try {
|
|
20555
|
+
const validatedInput = updateTableInputSchema.parse(input);
|
|
20556
|
+
const sheets = await getAuthenticatedClient();
|
|
20557
|
+
const metadataResponse = await sheets.spreadsheets.get({
|
|
20558
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20559
|
+
fields: "sheets.properties.title,sheets.properties.sheetId,sheets.tables"
|
|
20560
|
+
});
|
|
20561
|
+
const allSheets = metadataResponse.data.sheets ?? [];
|
|
20562
|
+
const existingTable = allSheets.flatMap((sheet) => sheet.tables ?? []).find((table2) => table2.tableId === validatedInput.tableId);
|
|
20563
|
+
if (!existingTable) {
|
|
20564
|
+
throw new Error(`Table "${validatedInput.tableId}" not found`);
|
|
20565
|
+
}
|
|
20566
|
+
const table = {
|
|
20567
|
+
tableId: validatedInput.tableId
|
|
20568
|
+
};
|
|
20569
|
+
if (validatedInput.name) {
|
|
20570
|
+
table.name = validatedInput.name;
|
|
20571
|
+
}
|
|
20572
|
+
if (validatedInput.columns) {
|
|
20573
|
+
table.columnProperties = buildTableColumnProperties(validatedInput.columns);
|
|
20574
|
+
}
|
|
20575
|
+
let responseSheetName;
|
|
20576
|
+
if (validatedInput.range) {
|
|
20577
|
+
const extracted = extractSheetName(validatedInput.range);
|
|
20578
|
+
const sheetName = validatedInput.sheetName ?? extracted.sheetName;
|
|
20579
|
+
if (!sheetName) {
|
|
20580
|
+
throw new Error("sheetName is required when updating a table range");
|
|
20581
|
+
}
|
|
20582
|
+
const resolvedRange = resolveTableRangeInput(sheetName, validatedInput.range);
|
|
20583
|
+
responseSheetName = resolvedRange.sheetName;
|
|
20584
|
+
const sheetId = await getSheetId(
|
|
20585
|
+
sheets,
|
|
20586
|
+
validatedInput.spreadsheetId,
|
|
20587
|
+
resolvedRange.sheetName
|
|
20588
|
+
);
|
|
20589
|
+
const gridRange = parseRange(resolvedRange.range, sheetId);
|
|
20590
|
+
validateTableGridRange(gridRange, `${resolvedRange.sheetName}!${resolvedRange.range}`);
|
|
20591
|
+
if (validatedInput.columns) {
|
|
20592
|
+
validateColumnCount(
|
|
20593
|
+
gridRange,
|
|
20594
|
+
validatedInput.columns,
|
|
20595
|
+
`${resolvedRange.sheetName}!${resolvedRange.range}`
|
|
20596
|
+
);
|
|
20597
|
+
}
|
|
20598
|
+
const sheetData = findSheetOrThrow(allSheets, resolvedRange.sheetName);
|
|
20599
|
+
ensureNoOverlappingTable(
|
|
20600
|
+
sheetData.tables ?? [],
|
|
20601
|
+
gridRange,
|
|
20602
|
+
resolvedRange.sheetName,
|
|
20603
|
+
validatedInput.tableId
|
|
20604
|
+
);
|
|
20605
|
+
table.range = gridRange;
|
|
20606
|
+
}
|
|
20607
|
+
const response = await sheets.spreadsheets.batchUpdate({
|
|
20608
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20609
|
+
requestBody: {
|
|
20610
|
+
requests: [
|
|
20611
|
+
{
|
|
20612
|
+
updateTable: {
|
|
20613
|
+
table,
|
|
20614
|
+
fields: validatedInput.fields
|
|
20615
|
+
}
|
|
20616
|
+
}
|
|
20617
|
+
]
|
|
20618
|
+
}
|
|
20619
|
+
});
|
|
20620
|
+
return formatToolResponse(`Successfully updated table "${validatedInput.tableId}"`, {
|
|
20621
|
+
spreadsheetId: response.data.spreadsheetId,
|
|
20622
|
+
table: formatTableForResponse({ ...existingTable, ...table }, responseSheetName),
|
|
20623
|
+
fields: validatedInput.fields
|
|
20624
|
+
});
|
|
20625
|
+
} catch (error2) {
|
|
20626
|
+
return handleError(error2);
|
|
20627
|
+
}
|
|
20628
|
+
}
|
|
20629
|
+
|
|
20630
|
+
// src/tools/delete-table.ts
|
|
20631
|
+
var deleteTableInputSchema = external_exports.object({
|
|
20632
|
+
spreadsheetId: external_exports.string().min(1),
|
|
20633
|
+
tableId: external_exports.string().min(1)
|
|
20634
|
+
});
|
|
20635
|
+
var deleteTableTool = {
|
|
20636
|
+
name: "sheets_delete_table",
|
|
20637
|
+
description: "Delete a native Google Sheets table by tableId",
|
|
20638
|
+
inputSchema: {
|
|
20639
|
+
type: "object",
|
|
20640
|
+
properties: {
|
|
20641
|
+
spreadsheetId: {
|
|
20642
|
+
type: "string",
|
|
20643
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
20644
|
+
},
|
|
20645
|
+
tableId: {
|
|
20646
|
+
type: "string",
|
|
20647
|
+
description: "The ID of the table to delete"
|
|
20648
|
+
}
|
|
20649
|
+
},
|
|
20650
|
+
required: ["spreadsheetId", "tableId"]
|
|
20651
|
+
}
|
|
20652
|
+
};
|
|
20653
|
+
async function deleteTableHandler(input) {
|
|
20654
|
+
try {
|
|
20655
|
+
const validatedInput = deleteTableInputSchema.parse(input);
|
|
20656
|
+
const sheets = await getAuthenticatedClient();
|
|
20657
|
+
const metadataResponse = await sheets.spreadsheets.get({
|
|
20658
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20659
|
+
fields: "sheets.tables.tableId"
|
|
20660
|
+
});
|
|
20661
|
+
const tableExists = (metadataResponse.data.sheets ?? []).flatMap((sheet) => sheet.tables ?? []).some((table) => table.tableId === validatedInput.tableId);
|
|
20662
|
+
if (!tableExists) {
|
|
20663
|
+
throw new Error(`Table "${validatedInput.tableId}" not found`);
|
|
20664
|
+
}
|
|
20665
|
+
const response = await sheets.spreadsheets.batchUpdate({
|
|
20666
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20667
|
+
requestBody: {
|
|
20668
|
+
requests: [
|
|
20669
|
+
{
|
|
20670
|
+
deleteTable: {
|
|
20671
|
+
tableId: validatedInput.tableId
|
|
20672
|
+
}
|
|
20673
|
+
}
|
|
20674
|
+
]
|
|
20675
|
+
}
|
|
20676
|
+
});
|
|
20677
|
+
return formatToolResponse(`Successfully deleted table "${validatedInput.tableId}"`, {
|
|
20678
|
+
spreadsheetId: response.data.spreadsheetId,
|
|
20679
|
+
tableId: validatedInput.tableId
|
|
20680
|
+
});
|
|
20681
|
+
} catch (error2) {
|
|
20682
|
+
return handleError(error2);
|
|
20683
|
+
}
|
|
20684
|
+
}
|
|
20685
|
+
|
|
20686
|
+
// src/tools/get-tables.ts
|
|
20687
|
+
var getTablesInputSchema = external_exports.object({
|
|
20688
|
+
spreadsheetId: external_exports.string().min(1),
|
|
20689
|
+
sheetName: external_exports.string().min(1).optional()
|
|
20690
|
+
});
|
|
20691
|
+
var getTablesTool = {
|
|
20692
|
+
name: "sheets_get_tables",
|
|
20693
|
+
description: "Read native Google Sheets tables for a spreadsheet or a specific sheet",
|
|
20694
|
+
inputSchema: {
|
|
20695
|
+
type: "object",
|
|
20696
|
+
properties: {
|
|
20697
|
+
spreadsheetId: {
|
|
20698
|
+
type: "string",
|
|
20699
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
20700
|
+
},
|
|
20701
|
+
sheetName: {
|
|
20702
|
+
type: "string",
|
|
20703
|
+
description: "Optional name of the sheet (tab) to inspect"
|
|
20704
|
+
}
|
|
20705
|
+
},
|
|
20706
|
+
required: ["spreadsheetId"]
|
|
20707
|
+
}
|
|
20708
|
+
};
|
|
20709
|
+
async function getTablesHandler(input) {
|
|
20710
|
+
try {
|
|
20711
|
+
const validatedInput = getTablesInputSchema.parse(input);
|
|
20712
|
+
const sheets = await getAuthenticatedClient();
|
|
20713
|
+
const response = await sheets.spreadsheets.get({
|
|
20714
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20715
|
+
fields: "sheets.properties.title,sheets.properties.sheetId,sheets.tables"
|
|
20716
|
+
});
|
|
20717
|
+
const spreadsheetSheets = response.data.sheets ?? [];
|
|
20718
|
+
const targetSheets = validatedInput.sheetName ? [findSheetOrThrow(spreadsheetSheets, validatedInput.sheetName)] : spreadsheetSheets;
|
|
20719
|
+
const tables = targetSheets.flatMap((sheet) => {
|
|
20720
|
+
const sheetName = sheet.properties?.title ?? void 0;
|
|
20721
|
+
return (sheet.tables ?? []).map((table) => ({
|
|
20722
|
+
sheetName: sheetName ?? null,
|
|
20723
|
+
sheetId: sheet.properties?.sheetId ?? null,
|
|
20724
|
+
...formatTableForResponse(table, sheetName)
|
|
20725
|
+
}));
|
|
20726
|
+
});
|
|
20727
|
+
const context = validatedInput.sheetName ? ` in sheet "${validatedInput.sheetName}"` : " in spreadsheet";
|
|
20728
|
+
return formatSuccessResponse(
|
|
20729
|
+
{
|
|
20730
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
20731
|
+
sheetName: validatedInput.sheetName,
|
|
20732
|
+
tableCount: tables.length,
|
|
20733
|
+
tables
|
|
20734
|
+
},
|
|
20735
|
+
`Found ${tables.length} table(s)${context}`
|
|
20736
|
+
);
|
|
20737
|
+
} catch (error2) {
|
|
20738
|
+
return handleError(error2);
|
|
20739
|
+
}
|
|
20740
|
+
}
|
|
20741
|
+
|
|
20133
20742
|
// src/tools/check-access.ts
|
|
20134
20743
|
var CheckAccessSchema = external_exports.object({
|
|
20135
20744
|
spreadsheetId: external_exports.string().describe("The ID of the spreadsheet to check access for")
|
|
@@ -23366,6 +23975,10 @@ var toolHandlers = /* @__PURE__ */ new Map([
|
|
|
23366
23975
|
["sheets_merge_cells", mergeCellsHandler],
|
|
23367
23976
|
["sheets_unmerge_cells", unmergeCellsHandler],
|
|
23368
23977
|
["sheets_add_conditional_formatting", addConditionalFormattingHandler],
|
|
23978
|
+
["sheets_add_table", addTableHandler],
|
|
23979
|
+
["sheets_update_table", updateTableHandler],
|
|
23980
|
+
["sheets_delete_table", deleteTableHandler],
|
|
23981
|
+
["sheets_get_tables", getTablesHandler],
|
|
23369
23982
|
// Batch operations
|
|
23370
23983
|
["sheets_batch_delete_sheets", handleBatchDeleteSheets],
|
|
23371
23984
|
["sheets_batch_format_cells", handleBatchFormatCells],
|
|
@@ -23414,6 +24027,10 @@ var allTools = [
|
|
|
23414
24027
|
mergeCellsTool,
|
|
23415
24028
|
unmergeCellsTool,
|
|
23416
24029
|
addConditionalFormattingTool,
|
|
24030
|
+
addTableTool,
|
|
24031
|
+
updateTableTool,
|
|
24032
|
+
deleteTableTool,
|
|
24033
|
+
getTablesTool,
|
|
23417
24034
|
batchDeleteSheetsTool,
|
|
23418
24035
|
batchFormatCellsTool,
|
|
23419
24036
|
createChartTool,
|
|
@@ -23443,6 +24060,23 @@ async function main() {
|
|
|
23443
24060
|
console.error("Authentication Error:", error2.message);
|
|
23444
24061
|
process.exit(1);
|
|
23445
24062
|
}
|
|
24063
|
+
let toolsetConfig;
|
|
24064
|
+
try {
|
|
24065
|
+
toolsetConfig = resolveToolsets();
|
|
24066
|
+
} catch (error2) {
|
|
24067
|
+
if (error2 instanceof ToolsetConfigError) {
|
|
24068
|
+
console.error(`Configuration Error: ${error2.message}`);
|
|
24069
|
+
process.exit(1);
|
|
24070
|
+
}
|
|
24071
|
+
throw error2;
|
|
24072
|
+
}
|
|
24073
|
+
const { enabled, readOnly, allowed } = toolsetConfig;
|
|
24074
|
+
const exposedTools = allTools.filter((tool) => allowed.has(tool.name)).map((tool) => ({ ...tool, annotations: annotationsFor(tool.name) }));
|
|
24075
|
+
if (enabled.length < Object.keys(TOOLSETS).length || readOnly) {
|
|
24076
|
+
console.error(
|
|
24077
|
+
`Toolsets: ${enabled.join(", ")}${readOnly ? " (read-only)" : ""} \u2014 ${exposedTools.length}/${allTools.length} tools`
|
|
24078
|
+
);
|
|
24079
|
+
}
|
|
23446
24080
|
const server = new Server(
|
|
23447
24081
|
{
|
|
23448
24082
|
name: "spreadsheet",
|
|
@@ -23458,11 +24092,16 @@ async function main() {
|
|
|
23458
24092
|
);
|
|
23459
24093
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
23460
24094
|
return {
|
|
23461
|
-
tools:
|
|
24095
|
+
tools: exposedTools
|
|
23462
24096
|
};
|
|
23463
24097
|
});
|
|
23464
24098
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
23465
24099
|
const { name, arguments: args } = request.params;
|
|
24100
|
+
if (!allowed.has(name)) {
|
|
24101
|
+
throw new Error(
|
|
24102
|
+
toolHandlers.has(name) ? `Tool ${name} is disabled by the current GSHEETS_TOOLSETS/GSHEETS_READ_ONLY configuration` : `Unknown tool: ${name}`
|
|
24103
|
+
);
|
|
24104
|
+
}
|
|
23466
24105
|
const handler = toolHandlers.get(name);
|
|
23467
24106
|
if (!handler) {
|
|
23468
24107
|
throw new Error(`Unknown tool: ${name}`);
|