mcp-gsheets 1.7.0 → 1.7.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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +36 -132
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  ![Coverage](https://codecov.io/gh/freema/mcp-gsheets/branch/main/graph/badge.svg)
10
10
  ![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
11
11
  ![TypeScript](https://img.shields.io/badge/TypeScript-5.0%2B-007ACC?logo=typescript&logoColor=white)
12
- ![Node](https://img.shields.io/badge/Node.js-18%2B-339933?logo=node.js&logoColor=white)
12
+ ![Node](https://img.shields.io/badge/Node.js-20%2B-339933?logo=node.js&logoColor=white)
13
13
  ![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?logo=prettier&logoColor=white)
14
14
 
15
15
  A Model Context Protocol (MCP) server for Google Sheets API integration. Enables reading, writing, and managing Google Sheets documents directly from your MCP client (e.g., Claude Code, Claude Desktop, Cursor, etc.).
@@ -23,7 +23,7 @@ A Model Context Protocol (MCP) server for Google Sheets API integration. Enables
23
23
 
24
24
  ## Requirements
25
25
 
26
- - [Node.js](https://nodejs.org/) v18 or higher
26
+ - [Node.js](https://nodejs.org/) v20 or higher
27
27
  - [Google Cloud Project](https://console.cloud.google.com) with Sheets API enabled
28
28
  - Service Account with JSON key file
29
29
  - [npm](https://www.npmjs.com/)
@@ -375,6 +375,8 @@ npm run dev # Watch mode with auto-reload
375
375
  | `sheets_get_sheet_structure` | Lightweight structural metadata only — no per-cell data. Returns dimensions, frozen rows/cols, tab colour, column widths, row heights, hidden columns/rows, and all merges in A1 notation. Single fast API call | `spreadsheetId`, `sheetName` |
376
376
  | `sheets_get_formatting_compact` | Read cell formatting for a range and return it as compact A1Range→format pairs (run-length encoded). Identical adjacent cells are collapsed into rectangular ranges — reduces output by 90 %+ compared to per-cell data | `spreadsheetId`, `sheetName`, `range`, `useEffectiveFormat`, `fields` |
377
377
  | `sheets_get_full_sheet_snapshot` | Master one-shot tool — returns all structural and formatting metadata (merges, dimensions, conditional formatting, and optionally cell formatting) in a single API call. Supports `fields` filter and `compactMode` to limit response size | `spreadsheetId`, `sheetName`, `includeFormattingRange`, `fields`, `compactMode` |
378
+ | `sheets_get_basic_filter` | Read the Basic Filter (AutoFilter) configuration for a sheet, including filtered range, sort specs, and per-column filter criteria (hidden values, conditions, colour filters) | `spreadsheetId`, `sheetName` |
379
+ | `sheets_get_data_validation` | Read data validation rules (checkboxes, dropdowns, custom formulas) from a sheet or range. Returns compact run-length-encoded list of unique rules grouped by cell ranges | `spreadsheetId`, `sheetName`, `range` |
378
380
 
379
381
  ## 🔧 Code Quality
380
382
 
package/dist/index.js CHANGED
@@ -19472,6 +19472,21 @@ async function handleCopyTo(input) {
19472
19472
  }
19473
19473
 
19474
19474
  // src/utils/range-helpers.ts
19475
+ function findSheetOrThrow(sheets, sheetName) {
19476
+ const sheet = sheets.find((s) => s.properties?.title === sheetName);
19477
+ if (!sheet) {
19478
+ const available = sheets.map((s) => s.properties?.title).filter(Boolean).join(", ");
19479
+ throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
19480
+ }
19481
+ return sheet;
19482
+ }
19483
+ function gridRangeToA1(range) {
19484
+ const startCol = range.startColumnIndex ?? 0;
19485
+ const startRow = (range.startRowIndex ?? 0) + 1;
19486
+ const endCol = (range.endColumnIndex ?? startCol + 1) - 1;
19487
+ const endRow = range.endRowIndex ?? startRow;
19488
+ return `${colIndexToLetter(startCol)}${startRow}:${colIndexToLetter(endCol)}${endRow}`;
19489
+ }
19475
19490
  function columnToIndex(column) {
19476
19491
  let index = 0;
19477
19492
  for (let i = 0; i < column.length; i++) {
@@ -21402,23 +21417,6 @@ var getMergedCellsTool = {
21402
21417
  required: ["spreadsheetId", "sheetName"]
21403
21418
  }
21404
21419
  };
21405
- function colIndexToLetter2(index) {
21406
- let result = "";
21407
- let n = index + 1;
21408
- while (n > 0) {
21409
- const rem = (n - 1) % 26;
21410
- result = String.fromCharCode(65 + rem) + result;
21411
- n = Math.floor((n - 1) / 26);
21412
- }
21413
- return result;
21414
- }
21415
- function gridRangeToA1(startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {
21416
- const startCol = colIndexToLetter2(startColumnIndex);
21417
- const endCol = colIndexToLetter2(endColumnIndex - 1);
21418
- const startRow = startRowIndex + 1;
21419
- const endRow = endRowIndex;
21420
- return `${startCol}${startRow}:${endCol}${endRow}`;
21421
- }
21422
21420
  async function handleGetMergedCells(input) {
21423
21421
  try {
21424
21422
  const { spreadsheetId, sheetName } = inputSchema.parse(input);
@@ -21427,20 +21425,9 @@ async function handleGetMergedCells(input) {
21427
21425
  spreadsheetId,
21428
21426
  fields: "sheets.properties.title,sheets.properties.sheetId,sheets.merges"
21429
21427
  });
21430
- const sheetData = (response.data.sheets ?? []).find(
21431
- (s) => s.properties?.title === sheetName
21432
- );
21433
- if (!sheetData) {
21434
- const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
21435
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
21436
- }
21428
+ const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
21437
21429
  const merges = (sheetData.merges ?? []).map((m) => ({
21438
- a1Notation: gridRangeToA1(
21439
- m.startRowIndex ?? 0,
21440
- m.endRowIndex ?? 0,
21441
- m.startColumnIndex ?? 0,
21442
- m.endColumnIndex ?? 0
21443
- ),
21430
+ a1Notation: gridRangeToA1(m),
21444
21431
  startRowIndex: m.startRowIndex,
21445
21432
  endRowIndex: m.endRowIndex,
21446
21433
  startColumnIndex: m.startColumnIndex,
@@ -21491,13 +21478,7 @@ async function handleGetSheetDimensions(input) {
21491
21478
  spreadsheetId,
21492
21479
  fields: "sheets.properties,sheets.data.columnMetadata,sheets.data.rowMetadata"
21493
21480
  });
21494
- const sheetData = (response.data.sheets ?? []).find(
21495
- (s) => s.properties?.title === sheetName
21496
- );
21497
- if (!sheetData) {
21498
- const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
21499
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
21500
- }
21481
+ const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
21501
21482
  const gridProps = sheetData.properties?.gridProperties ?? {};
21502
21483
  const gridData = sheetData.data?.[0] ?? {};
21503
21484
  const columns = (gridData.columnMetadata ?? []).map(
@@ -21650,13 +21631,7 @@ async function handleGetConditionalFormattingData(input) {
21650
21631
  spreadsheetId,
21651
21632
  fields: "sheets.properties.title,sheets.properties.sheetId,sheets.conditionalFormats,sheets.bandedRanges"
21652
21633
  });
21653
- const sheetData = (response.data.sheets ?? []).find(
21654
- (s) => s.properties?.title === sheetName
21655
- );
21656
- if (!sheetData) {
21657
- const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
21658
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
21659
- }
21634
+ const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
21660
21635
  return formatSuccessResponse(
21661
21636
  {
21662
21637
  sheetName,
@@ -21849,9 +21824,6 @@ var getFullSheetSnapshotTool = {
21849
21824
  required: ["spreadsheetId", "sheetName"]
21850
21825
  }
21851
21826
  };
21852
- function gridRangeToA12(startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {
21853
- return `${colIndexToLetter(startColumnIndex)}${startRowIndex + 1}:${colIndexToLetter(endColumnIndex - 1)}${endRowIndex}`;
21854
- }
21855
21827
  async function handleGetFullSheetSnapshot(input) {
21856
21828
  try {
21857
21829
  const {
@@ -21879,23 +21851,12 @@ async function handleGetFullSheetSnapshot(input) {
21879
21851
  includeGridData: !!includeFormattingRange,
21880
21852
  fields: baseFields + cellFormattingFields
21881
21853
  });
21882
- const sheetData = (response.data.sheets ?? []).find(
21883
- (s) => s.properties?.title === sheetName
21884
- );
21885
- if (!sheetData) {
21886
- const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
21887
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
21888
- }
21854
+ const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
21889
21855
  const props = sheetData.properties;
21890
21856
  const gridProps = props.gridProperties ?? {};
21891
21857
  const gridData = sheetData.data?.[0] ?? {};
21892
21858
  const merges = (sheetData.merges ?? []).map((m) => ({
21893
- a1Notation: gridRangeToA12(
21894
- m.startRowIndex ?? 0,
21895
- m.endRowIndex ?? 0,
21896
- m.startColumnIndex ?? 0,
21897
- m.endColumnIndex ?? 0
21898
- ),
21859
+ a1Notation: gridRangeToA1(m),
21899
21860
  startRowIndex: m.startRowIndex,
21900
21861
  endRowIndex: m.endRowIndex,
21901
21862
  startColumnIndex: m.startColumnIndex,
@@ -22008,13 +21969,7 @@ async function handleGetSheetStructure(input) {
22008
21969
  fields: "sheets.properties,sheets.merges,sheets.data.columnMetadata,sheets.data.rowMetadata"
22009
21970
  });
22010
21971
  const allSheets = response.data.sheets ?? [];
22011
- const sheetData = allSheets.find(
22012
- (s) => s.properties?.title === sheetName
22013
- );
22014
- if (!sheetData) {
22015
- const available = allSheets.map((s) => s.properties?.title).filter(Boolean).join(", ");
22016
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
22017
- }
21972
+ const sheetData = findSheetOrThrow(allSheets, sheetName);
22018
21973
  const props = sheetData.properties;
22019
21974
  const gridProps = props.gridProperties ?? {};
22020
21975
  const gridData = sheetData.data?.[0] ?? {};
@@ -22029,13 +21984,9 @@ async function handleGetSheetStructure(input) {
22029
21984
  (row) => row.pixelSize ?? null
22030
21985
  );
22031
21986
  const hiddenRows = (gridData.rowMetadata ?? []).map((row, i) => row.hiddenByUser ? i : -1).filter((i) => i >= 0);
22032
- const merges = (sheetData.merges ?? []).map((m) => {
22033
- const sc = m.startColumnIndex ?? 0;
22034
- const ec = (m.endColumnIndex ?? 1) - 1;
22035
- const sr = (m.startRowIndex ?? 0) + 1;
22036
- const er = m.endRowIndex ?? 1;
22037
- return `${colIndexToLetter(sc)}${sr}:${colIndexToLetter(ec)}${er}`;
22038
- });
21987
+ const merges = (sheetData.merges ?? []).map(
21988
+ (m) => gridRangeToA1(m)
21989
+ );
22039
21990
  return formatSuccessResponse(
22040
21991
  {
22041
21992
  sheetName,
@@ -22122,13 +22073,7 @@ async function handleGetFormattingCompact(input) {
22122
22073
  fields: `sheets.properties.title,${cellDataFields},sheets.data.startRow,sheets.data.startColumn`
22123
22074
  });
22124
22075
  const allSheets = response.data.sheets ?? [];
22125
- const sheetData = allSheets.find(
22126
- (s) => s.properties?.title === sheetName
22127
- );
22128
- if (!sheetData) {
22129
- const available = allSheets.map((s) => s.properties?.title).filter(Boolean).join(", ");
22130
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
22131
- }
22076
+ const sheetData = findSheetOrThrow(allSheets, sheetName);
22132
22077
  const gridData = sheetData.data?.[0];
22133
22078
  if (!gridData?.rowData) {
22134
22079
  return formatSuccessResponse(
@@ -22188,15 +22133,6 @@ var getDataValidationTool = {
22188
22133
  required: ["spreadsheetId", "sheetName"]
22189
22134
  }
22190
22135
  };
22191
- function colToLetter(col) {
22192
- let letter = "";
22193
- let c = col;
22194
- while (c >= 0) {
22195
- letter = String.fromCharCode(c % 26 + 65) + letter;
22196
- c = Math.floor(c / 26) - 1;
22197
- }
22198
- return letter;
22199
- }
22200
22136
  function validationKey(dv) {
22201
22137
  return JSON.stringify({
22202
22138
  type: dv.condition?.type ?? null,
@@ -22272,8 +22208,8 @@ function compactifyValidation(rowData, startRow, startCol) {
22272
22208
  }
22273
22209
  }
22274
22210
  }
22275
- const topLeft = `${colToLetter(c + minCol)}${r + minRow + 1}`;
22276
- const bottomRight = `${colToLetter(endC + minCol)}${endR + minRow + 1}`;
22211
+ const topLeft = `${colIndexToLetter(c + minCol)}${r + minRow + 1}`;
22212
+ const bottomRight = `${colIndexToLetter(endC + minCol)}${endR + minRow + 1}`;
22277
22213
  ranges.push(topLeft === bottomRight ? topLeft : `${topLeft}:${bottomRight}`);
22278
22214
  }
22279
22215
  }
@@ -22298,13 +22234,7 @@ async function handleGetDataValidation(input) {
22298
22234
  includeGridData: true,
22299
22235
  fields: "sheets.data.rowData.values.dataValidation,sheets.data.startRow,sheets.data.startColumn,sheets.properties.title,sheets.properties.sheetId"
22300
22236
  });
22301
- const sheetData = (response.data.sheets ?? []).find(
22302
- (s) => s.properties?.title === sheetName
22303
- );
22304
- if (!sheetData) {
22305
- const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
22306
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
22307
- }
22237
+ const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
22308
22238
  const gridData = sheetData.data?.[0];
22309
22239
  if (!gridData?.rowData) {
22310
22240
  return formatSuccessResponse(
@@ -22314,11 +22244,7 @@ async function handleGetDataValidation(input) {
22314
22244
  }
22315
22245
  const startRow = gridData.startRow ?? 0;
22316
22246
  const startCol = gridData.startColumn ?? 0;
22317
- const validationRules = compactifyValidation(
22318
- gridData.rowData,
22319
- startRow,
22320
- startCol
22321
- );
22247
+ const validationRules = compactifyValidation(gridData.rowData, startRow, startCol);
22322
22248
  return formatSuccessResponse(
22323
22249
  { sheetName, validationRules },
22324
22250
  `Data validation for sheet "${sheetName}" \u2014 ${validationRules.length} unique rule(s)`
@@ -22351,22 +22277,6 @@ var getBasicFilterTool = {
22351
22277
  required: ["spreadsheetId", "sheetName"]
22352
22278
  }
22353
22279
  };
22354
- function colToLetter2(col) {
22355
- let letter = "";
22356
- let c = col;
22357
- while (c >= 0) {
22358
- letter = String.fromCharCode(c % 26 + 65) + letter;
22359
- c = Math.floor(c / 26) - 1;
22360
- }
22361
- return letter;
22362
- }
22363
- function gridRangeToA13(range) {
22364
- const startCol = range.startColumnIndex ?? 0;
22365
- const startRow = (range.startRowIndex ?? 0) + 1;
22366
- const endCol = (range.endColumnIndex ?? startCol + 1) - 1;
22367
- const endRow = range.endRowIndex ?? startRow;
22368
- return `${colToLetter2(startCol)}${startRow}:${colToLetter2(endCol)}${endRow}`;
22369
- }
22370
22280
  async function handleGetBasicFilter(input) {
22371
22281
  try {
22372
22282
  const { spreadsheetId, sheetName } = inputSchema9.parse(input);
@@ -22375,13 +22285,7 @@ async function handleGetBasicFilter(input) {
22375
22285
  spreadsheetId,
22376
22286
  fields: "sheets.properties.title,sheets.properties.sheetId,sheets.basicFilter"
22377
22287
  });
22378
- const sheetData = (response.data.sheets ?? []).find(
22379
- (s) => s.properties?.title === sheetName
22380
- );
22381
- if (!sheetData) {
22382
- const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
22383
- throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
22384
- }
22288
+ const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
22385
22289
  const bf = sheetData.basicFilter;
22386
22290
  if (!bf) {
22387
22291
  return formatSuccessResponse(
@@ -22396,7 +22300,7 @@ async function handleGetBasicFilter(input) {
22396
22300
  const fc = spec.filterCriteria;
22397
22301
  filterCriteria.push({
22398
22302
  columnIndex: colIdx,
22399
- columnLetter: colToLetter2(colIdx),
22303
+ columnLetter: colIndexToLetter(colIdx),
22400
22304
  hiddenValues: fc?.hiddenValues ?? [],
22401
22305
  condition: fc?.condition ?? null,
22402
22306
  visibleBackgroundColor: fc?.visibleBackgroundColor ?? null,
@@ -22409,7 +22313,7 @@ async function handleGetBasicFilter(input) {
22409
22313
  const criteria = fc;
22410
22314
  filterCriteria.push({
22411
22315
  columnIndex: idx,
22412
- columnLetter: colToLetter2(idx),
22316
+ columnLetter: colIndexToLetter(idx),
22413
22317
  hiddenValues: criteria.hiddenValues ?? [],
22414
22318
  condition: criteria.condition ?? null,
22415
22319
  visibleBackgroundColor: criteria.visibleBackgroundColor ?? null,
@@ -22417,7 +22321,7 @@ async function handleGetBasicFilter(input) {
22417
22321
  });
22418
22322
  }
22419
22323
  }
22420
- const rangeA1 = bf.range ? gridRangeToA13(bf.range) : null;
22324
+ const rangeA1 = bf.range ? gridRangeToA1(bf.range) : null;
22421
22325
  return formatSuccessResponse(
22422
22326
  {
22423
22327
  sheetName,
@@ -22426,7 +22330,7 @@ async function handleGetBasicFilter(input) {
22426
22330
  range: rangeA1,
22427
22331
  sortSpecs: (bf.sortSpecs ?? []).map((s) => ({
22428
22332
  columnIndex: s.dimensionIndex,
22429
- columnLetter: s.dimensionIndex !== null && s.dimensionIndex !== void 0 ? colToLetter2(s.dimensionIndex) : null,
22333
+ columnLetter: s.dimensionIndex !== null && s.dimensionIndex !== void 0 ? colIndexToLetter(s.dimensionIndex) : null,
22430
22334
  sortOrder: s.sortOrder ?? null
22431
22335
  })),
22432
22336
  filterCriteria
@@ -22935,7 +22839,7 @@ async function main() {
22935
22839
  const server = new Server(
22936
22840
  {
22937
22841
  name: "spreadsheet",
22938
- version: "1.6.0"
22842
+ version: "1.7.1"
22939
22843
  },
22940
22844
  {
22941
22845
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-gsheets",
3
- "version": "1.7.0",
3
+ "version": "1.7.1",
4
4
  "description": "Model Context Protocol (MCP) server for Google Sheets API integration",
5
5
  "author": "freema",
6
6
  "license": "MIT",
@@ -43,7 +43,7 @@
43
43
  "ai"
44
44
  ],
45
45
  "engines": {
46
- "node": ">=18.0.0"
46
+ "node": ">=20.0.0"
47
47
  },
48
48
  "dependencies": {
49
49
  "@modelcontextprotocol/sdk": "^1.29.0",