mcp-gsheets 1.7.0 → 1.8.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 +55 -3
- package/dist/index.js +780 -269
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -18085,6 +18085,8 @@ var ERROR_MESSAGES = {
|
|
|
18085
18085
|
REQUIRED_NON_NEGATIVE: (field) => `${field} must be a non-negative number`,
|
|
18086
18086
|
// Range errors
|
|
18087
18087
|
INVALID_RANGE: 'Invalid range format. Use A1 notation (e.g., "Sheet1!A1:B10")',
|
|
18088
|
+
INVALID_COLUMN_RANGE: 'Invalid column range format. Use a full-column A1 range (e.g., "Sheet1!B:D" or "Sheet1!C:C")',
|
|
18089
|
+
INVALID_ROW_RANGE: 'Invalid row range format. Use a full-row A1 range (e.g., "Sheet1!2:4" or "Sheet1!3:3")',
|
|
18088
18090
|
RANGE_REQUIRED: "range is required and must be a string",
|
|
18089
18091
|
// Spreadsheet errors
|
|
18090
18092
|
SPREADSHEET_ID_REQUIRED: "spreadsheetId is required and must be a string",
|
|
@@ -18257,6 +18259,105 @@ function createSheetValidator(additionalValidation, defaults) {
|
|
|
18257
18259
|
);
|
|
18258
18260
|
}
|
|
18259
18261
|
|
|
18262
|
+
// src/utils/range-helpers.ts
|
|
18263
|
+
function findSheetOrThrow(sheets, sheetName) {
|
|
18264
|
+
const sheet = sheets.find((s) => s.properties?.title === sheetName);
|
|
18265
|
+
if (!sheet) {
|
|
18266
|
+
const available = sheets.map((s) => s.properties?.title).filter(Boolean).join(", ");
|
|
18267
|
+
throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
|
|
18268
|
+
}
|
|
18269
|
+
return sheet;
|
|
18270
|
+
}
|
|
18271
|
+
function gridRangeToA1(range) {
|
|
18272
|
+
const startCol = range.startColumnIndex ?? 0;
|
|
18273
|
+
const startRow = (range.startRowIndex ?? 0) + 1;
|
|
18274
|
+
const endCol = (range.endColumnIndex ?? startCol + 1) - 1;
|
|
18275
|
+
const endRow = range.endRowIndex ?? startRow;
|
|
18276
|
+
return `${colIndexToLetter(startCol)}${startRow}:${colIndexToLetter(endCol)}${endRow}`;
|
|
18277
|
+
}
|
|
18278
|
+
function columnToIndex(column) {
|
|
18279
|
+
let index = 0;
|
|
18280
|
+
for (let i = 0; i < column.length; i++) {
|
|
18281
|
+
index = index * 26 + (column.charCodeAt(i) - "A".charCodeAt(0) + 1);
|
|
18282
|
+
}
|
|
18283
|
+
return index - 1;
|
|
18284
|
+
}
|
|
18285
|
+
function parseRange(range, sheetId) {
|
|
18286
|
+
const rangePart = range.includes("!") ? range.split("!")[1] : range;
|
|
18287
|
+
if (!rangePart) {
|
|
18288
|
+
throw new Error(`Invalid range format: ${range}`);
|
|
18289
|
+
}
|
|
18290
|
+
const singleCellMatch = rangePart.match(/^([A-Z]+)(\d+)$/);
|
|
18291
|
+
if (singleCellMatch?.[1] && singleCellMatch[2]) {
|
|
18292
|
+
const col = columnToIndex(singleCellMatch[1]);
|
|
18293
|
+
const row = parseInt(singleCellMatch[2]) - 1;
|
|
18294
|
+
return {
|
|
18295
|
+
sheetId: sheetId ?? null,
|
|
18296
|
+
startRowIndex: row,
|
|
18297
|
+
endRowIndex: row + 1,
|
|
18298
|
+
startColumnIndex: col,
|
|
18299
|
+
endColumnIndex: col + 1
|
|
18300
|
+
};
|
|
18301
|
+
}
|
|
18302
|
+
const rangeMatch = rangePart.match(/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/);
|
|
18303
|
+
if (!rangeMatch?.[1] || !rangeMatch[2] || !rangeMatch[3] || !rangeMatch[4]) {
|
|
18304
|
+
throw new Error(`Invalid range format: ${range}`);
|
|
18305
|
+
}
|
|
18306
|
+
return {
|
|
18307
|
+
sheetId: sheetId ?? null,
|
|
18308
|
+
startRowIndex: parseInt(rangeMatch[2]) - 1,
|
|
18309
|
+
endRowIndex: parseInt(rangeMatch[4]),
|
|
18310
|
+
startColumnIndex: columnToIndex(rangeMatch[1]),
|
|
18311
|
+
endColumnIndex: columnToIndex(rangeMatch[3]) + 1
|
|
18312
|
+
};
|
|
18313
|
+
}
|
|
18314
|
+
async function getSheetId(sheets, spreadsheetId, sheetName) {
|
|
18315
|
+
const response = await sheets.spreadsheets.get({
|
|
18316
|
+
spreadsheetId,
|
|
18317
|
+
fields: "sheets.properties"
|
|
18318
|
+
});
|
|
18319
|
+
const sheetsData = response.data.sheets || [];
|
|
18320
|
+
if (sheetName) {
|
|
18321
|
+
const sheet = sheetsData.find((s) => s.properties?.title === sheetName);
|
|
18322
|
+
if (!sheet?.properties?.sheetId) {
|
|
18323
|
+
const availableSheets = sheetsData.map((s) => s.properties?.title).filter((title) => title).join(", ");
|
|
18324
|
+
throw new Error(`Sheet "${sheetName}" not found. Available sheets: ${availableSheets}`);
|
|
18325
|
+
}
|
|
18326
|
+
return sheet.properties.sheetId;
|
|
18327
|
+
}
|
|
18328
|
+
if (sheetsData.length > 0) {
|
|
18329
|
+
const firstSheet = sheetsData[0];
|
|
18330
|
+
if (firstSheet?.properties?.sheetId !== void 0 && firstSheet.properties.sheetId !== null) {
|
|
18331
|
+
return firstSheet.properties.sheetId;
|
|
18332
|
+
}
|
|
18333
|
+
}
|
|
18334
|
+
throw new Error("No sheets found in spreadsheet");
|
|
18335
|
+
}
|
|
18336
|
+
function extractSheetName(range) {
|
|
18337
|
+
if (range.includes("!")) {
|
|
18338
|
+
const parts = range.split("!");
|
|
18339
|
+
let sheetName = parts[0];
|
|
18340
|
+
const rangePart = parts[1] || "";
|
|
18341
|
+
if (sheetName) {
|
|
18342
|
+
if (sheetName.startsWith('"') && sheetName.endsWith('"') || sheetName.startsWith("'") && sheetName.endsWith("'")) {
|
|
18343
|
+
sheetName = sheetName.slice(1, -1);
|
|
18344
|
+
}
|
|
18345
|
+
return { sheetName, range: rangePart };
|
|
18346
|
+
}
|
|
18347
|
+
}
|
|
18348
|
+
return { range };
|
|
18349
|
+
}
|
|
18350
|
+
function colIndexToLetter(index) {
|
|
18351
|
+
let result = "";
|
|
18352
|
+
let n = index + 1;
|
|
18353
|
+
while (n > 0) {
|
|
18354
|
+
const rem = (n - 1) % 26;
|
|
18355
|
+
result = String.fromCharCode(65 + rem) + result;
|
|
18356
|
+
n = Math.floor((n - 1) / 26);
|
|
18357
|
+
}
|
|
18358
|
+
return result;
|
|
18359
|
+
}
|
|
18360
|
+
|
|
18260
18361
|
// src/utils/validators.ts
|
|
18261
18362
|
function validateRequiredString(value, fieldName) {
|
|
18262
18363
|
if (!value || typeof value !== "string") {
|
|
@@ -18623,6 +18724,30 @@ function validateInsertRowsInput(input) {
|
|
|
18623
18724
|
valueInputOption
|
|
18624
18725
|
};
|
|
18625
18726
|
}
|
|
18727
|
+
function validateDeleteColumnsInput(input) {
|
|
18728
|
+
validateSpreadsheetIdField(input.spreadsheetId);
|
|
18729
|
+
validateRangeField(input.range);
|
|
18730
|
+
const { range } = extractSheetName(input.range);
|
|
18731
|
+
if (!/^[A-Z]+:[A-Z]+$/i.test(range)) {
|
|
18732
|
+
throw new Error(ERROR_MESSAGES.INVALID_COLUMN_RANGE);
|
|
18733
|
+
}
|
|
18734
|
+
return {
|
|
18735
|
+
spreadsheetId: input.spreadsheetId,
|
|
18736
|
+
range: input.range
|
|
18737
|
+
};
|
|
18738
|
+
}
|
|
18739
|
+
function validateDeleteRowsInput(input) {
|
|
18740
|
+
validateSpreadsheetIdField(input.spreadsheetId);
|
|
18741
|
+
validateRangeField(input.range);
|
|
18742
|
+
const { range } = extractSheetName(input.range);
|
|
18743
|
+
if (!/^\d+:\d+$/.test(range)) {
|
|
18744
|
+
throw new Error(ERROR_MESSAGES.INVALID_ROW_RANGE);
|
|
18745
|
+
}
|
|
18746
|
+
return {
|
|
18747
|
+
spreadsheetId: input.spreadsheetId,
|
|
18748
|
+
range: input.range
|
|
18749
|
+
};
|
|
18750
|
+
}
|
|
18626
18751
|
|
|
18627
18752
|
// src/utils/response-helpers.ts
|
|
18628
18753
|
function createTextResponse(text) {
|
|
@@ -19471,90 +19596,6 @@ async function handleCopyTo(input) {
|
|
|
19471
19596
|
}
|
|
19472
19597
|
}
|
|
19473
19598
|
|
|
19474
|
-
// src/utils/range-helpers.ts
|
|
19475
|
-
function columnToIndex(column) {
|
|
19476
|
-
let index = 0;
|
|
19477
|
-
for (let i = 0; i < column.length; i++) {
|
|
19478
|
-
index = index * 26 + (column.charCodeAt(i) - "A".charCodeAt(0) + 1);
|
|
19479
|
-
}
|
|
19480
|
-
return index - 1;
|
|
19481
|
-
}
|
|
19482
|
-
function parseRange(range, sheetId) {
|
|
19483
|
-
const rangePart = range.includes("!") ? range.split("!")[1] : range;
|
|
19484
|
-
if (!rangePart) {
|
|
19485
|
-
throw new Error(`Invalid range format: ${range}`);
|
|
19486
|
-
}
|
|
19487
|
-
const singleCellMatch = rangePart.match(/^([A-Z]+)(\d+)$/);
|
|
19488
|
-
if (singleCellMatch?.[1] && singleCellMatch[2]) {
|
|
19489
|
-
const col = columnToIndex(singleCellMatch[1]);
|
|
19490
|
-
const row = parseInt(singleCellMatch[2]) - 1;
|
|
19491
|
-
return {
|
|
19492
|
-
sheetId: sheetId ?? null,
|
|
19493
|
-
startRowIndex: row,
|
|
19494
|
-
endRowIndex: row + 1,
|
|
19495
|
-
startColumnIndex: col,
|
|
19496
|
-
endColumnIndex: col + 1
|
|
19497
|
-
};
|
|
19498
|
-
}
|
|
19499
|
-
const rangeMatch = rangePart.match(/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/);
|
|
19500
|
-
if (!rangeMatch?.[1] || !rangeMatch[2] || !rangeMatch[3] || !rangeMatch[4]) {
|
|
19501
|
-
throw new Error(`Invalid range format: ${range}`);
|
|
19502
|
-
}
|
|
19503
|
-
return {
|
|
19504
|
-
sheetId: sheetId ?? null,
|
|
19505
|
-
startRowIndex: parseInt(rangeMatch[2]) - 1,
|
|
19506
|
-
endRowIndex: parseInt(rangeMatch[4]),
|
|
19507
|
-
startColumnIndex: columnToIndex(rangeMatch[1]),
|
|
19508
|
-
endColumnIndex: columnToIndex(rangeMatch[3]) + 1
|
|
19509
|
-
};
|
|
19510
|
-
}
|
|
19511
|
-
async function getSheetId(sheets, spreadsheetId, sheetName) {
|
|
19512
|
-
const response = await sheets.spreadsheets.get({
|
|
19513
|
-
spreadsheetId,
|
|
19514
|
-
fields: "sheets.properties"
|
|
19515
|
-
});
|
|
19516
|
-
const sheetsData = response.data.sheets || [];
|
|
19517
|
-
if (sheetName) {
|
|
19518
|
-
const sheet = sheetsData.find((s) => s.properties?.title === sheetName);
|
|
19519
|
-
if (!sheet?.properties?.sheetId) {
|
|
19520
|
-
const availableSheets = sheetsData.map((s) => s.properties?.title).filter((title) => title).join(", ");
|
|
19521
|
-
throw new Error(`Sheet "${sheetName}" not found. Available sheets: ${availableSheets}`);
|
|
19522
|
-
}
|
|
19523
|
-
return sheet.properties.sheetId;
|
|
19524
|
-
}
|
|
19525
|
-
if (sheetsData.length > 0) {
|
|
19526
|
-
const firstSheet = sheetsData[0];
|
|
19527
|
-
if (firstSheet?.properties?.sheetId !== void 0 && firstSheet.properties.sheetId !== null) {
|
|
19528
|
-
return firstSheet.properties.sheetId;
|
|
19529
|
-
}
|
|
19530
|
-
}
|
|
19531
|
-
throw new Error("No sheets found in spreadsheet");
|
|
19532
|
-
}
|
|
19533
|
-
function extractSheetName(range) {
|
|
19534
|
-
if (range.includes("!")) {
|
|
19535
|
-
const parts = range.split("!");
|
|
19536
|
-
let sheetName = parts[0];
|
|
19537
|
-
const rangePart = parts[1] || "";
|
|
19538
|
-
if (sheetName) {
|
|
19539
|
-
if (sheetName.startsWith('"') && sheetName.endsWith('"') || sheetName.startsWith("'") && sheetName.endsWith("'")) {
|
|
19540
|
-
sheetName = sheetName.slice(1, -1);
|
|
19541
|
-
}
|
|
19542
|
-
return { sheetName, range: rangePart };
|
|
19543
|
-
}
|
|
19544
|
-
}
|
|
19545
|
-
return { range };
|
|
19546
|
-
}
|
|
19547
|
-
function colIndexToLetter(index) {
|
|
19548
|
-
let result = "";
|
|
19549
|
-
let n = index + 1;
|
|
19550
|
-
while (n > 0) {
|
|
19551
|
-
const rem = (n - 1) % 26;
|
|
19552
|
-
result = String.fromCharCode(65 + rem) + result;
|
|
19553
|
-
n = Math.floor((n - 1) / 26);
|
|
19554
|
-
}
|
|
19555
|
-
return result;
|
|
19556
|
-
}
|
|
19557
|
-
|
|
19558
19599
|
// src/utils/json-parser.ts
|
|
19559
19600
|
function parseJsonInput(input, propertyName) {
|
|
19560
19601
|
if (input && typeof input === "string") {
|
|
@@ -20599,29 +20640,23 @@ async function handleCreateChart(input) {
|
|
|
20599
20640
|
const axes = [];
|
|
20600
20641
|
if (validatedInput.domainAxis?.title) {
|
|
20601
20642
|
const axis = {
|
|
20602
|
-
position: "BOTTOM_AXIS"
|
|
20643
|
+
position: "BOTTOM_AXIS",
|
|
20644
|
+
title: validatedInput.domainAxis.title
|
|
20603
20645
|
};
|
|
20604
|
-
if (validatedInput.domainAxis.title !== void 0) {
|
|
20605
|
-
axis.title = validatedInput.domainAxis.title;
|
|
20606
|
-
}
|
|
20607
20646
|
axes.push(axis);
|
|
20608
20647
|
}
|
|
20609
20648
|
if (validatedInput.leftAxis?.title) {
|
|
20610
20649
|
const axis = {
|
|
20611
|
-
position: "LEFT_AXIS"
|
|
20650
|
+
position: "LEFT_AXIS",
|
|
20651
|
+
title: validatedInput.leftAxis.title
|
|
20612
20652
|
};
|
|
20613
|
-
if (validatedInput.leftAxis.title !== void 0) {
|
|
20614
|
-
axis.title = validatedInput.leftAxis.title;
|
|
20615
|
-
}
|
|
20616
20653
|
axes.push(axis);
|
|
20617
20654
|
}
|
|
20618
20655
|
if (validatedInput.rightAxis?.title) {
|
|
20619
20656
|
const axis = {
|
|
20620
|
-
position: "RIGHT_AXIS"
|
|
20657
|
+
position: "RIGHT_AXIS",
|
|
20658
|
+
title: validatedInput.rightAxis.title
|
|
20621
20659
|
};
|
|
20622
|
-
if (validatedInput.rightAxis.title !== void 0) {
|
|
20623
|
-
axis.title = validatedInput.rightAxis.title;
|
|
20624
|
-
}
|
|
20625
20660
|
axes.push(axis);
|
|
20626
20661
|
}
|
|
20627
20662
|
if (axes.length > 0) {
|
|
@@ -21379,14 +21414,10 @@ async function handleInsertRows(input) {
|
|
|
21379
21414
|
}
|
|
21380
21415
|
}
|
|
21381
21416
|
|
|
21382
|
-
// src/tools/
|
|
21383
|
-
var
|
|
21384
|
-
|
|
21385
|
-
|
|
21386
|
-
});
|
|
21387
|
-
var getMergedCellsTool = {
|
|
21388
|
-
name: "sheets_get_merged_cells",
|
|
21389
|
-
description: "Get all merged cell ranges for a specific sheet. Returns each merge as A1 notation and GridRange coordinates.",
|
|
21417
|
+
// src/tools/delete-columns.ts
|
|
21418
|
+
var deleteColumnsTool = {
|
|
21419
|
+
name: "sheets_delete_columns",
|
|
21420
|
+
description: "Delete one or more columns from a Google Sheet using a full-column A1 range",
|
|
21390
21421
|
inputSchema: {
|
|
21391
21422
|
type: "object",
|
|
21392
21423
|
properties: {
|
|
@@ -21394,66 +21425,186 @@ var getMergedCellsTool = {
|
|
|
21394
21425
|
type: "string",
|
|
21395
21426
|
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
21396
21427
|
},
|
|
21397
|
-
|
|
21428
|
+
range: {
|
|
21398
21429
|
type: "string",
|
|
21399
|
-
description: "
|
|
21430
|
+
description: 'Full-column A1 range to delete (e.g., "Sheet1!B:D" or "Sheet1!C:C"). If sheet name is omitted, the first sheet is used'
|
|
21400
21431
|
}
|
|
21401
21432
|
},
|
|
21402
|
-
required: ["spreadsheetId", "
|
|
21433
|
+
required: ["spreadsheetId", "range"]
|
|
21403
21434
|
}
|
|
21404
21435
|
};
|
|
21405
|
-
function
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
|
|
21409
|
-
const rem = (n - 1) % 26;
|
|
21410
|
-
result = String.fromCharCode(65 + rem) + result;
|
|
21411
|
-
n = Math.floor((n - 1) / 26);
|
|
21436
|
+
function parseColumnRange(range) {
|
|
21437
|
+
const match = range.match(/^([A-Z]+):([A-Z]+)$/i);
|
|
21438
|
+
if (!match?.[1] || !match[2]) {
|
|
21439
|
+
throw new Error('Column range must use full-column A1 notation, e.g. "B:D" or "C:C"');
|
|
21412
21440
|
}
|
|
21413
|
-
|
|
21414
|
-
|
|
21415
|
-
|
|
21416
|
-
|
|
21417
|
-
|
|
21418
|
-
|
|
21419
|
-
const endRow = endRowIndex;
|
|
21420
|
-
return `${startCol}${startRow}:${endCol}${endRow}`;
|
|
21441
|
+
const startIndex = columnToIndex(match[1].toUpperCase());
|
|
21442
|
+
const endIndex = columnToIndex(match[2].toUpperCase()) + 1;
|
|
21443
|
+
if (endIndex <= startIndex) {
|
|
21444
|
+
throw new Error("Column range end must be greater than or equal to the start column");
|
|
21445
|
+
}
|
|
21446
|
+
return { startIndex, endIndex };
|
|
21421
21447
|
}
|
|
21422
|
-
async function
|
|
21448
|
+
async function handleDeleteColumns(input) {
|
|
21423
21449
|
try {
|
|
21424
|
-
const
|
|
21450
|
+
const validatedInput = validateDeleteColumnsInput(input);
|
|
21425
21451
|
const sheets = await getAuthenticatedClient();
|
|
21426
|
-
const
|
|
21427
|
-
|
|
21428
|
-
|
|
21452
|
+
const { sheetName, range } = extractSheetName(validatedInput.range);
|
|
21453
|
+
const sheetId = await getSheetId(sheets, validatedInput.spreadsheetId, sheetName);
|
|
21454
|
+
const { startIndex, endIndex } = parseColumnRange(range);
|
|
21455
|
+
await sheets.spreadsheets.batchUpdate({
|
|
21456
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
21457
|
+
requestBody: {
|
|
21458
|
+
requests: [
|
|
21459
|
+
{
|
|
21460
|
+
deleteDimension: {
|
|
21461
|
+
range: {
|
|
21462
|
+
sheetId,
|
|
21463
|
+
dimension: "COLUMNS",
|
|
21464
|
+
startIndex,
|
|
21465
|
+
endIndex
|
|
21466
|
+
}
|
|
21467
|
+
}
|
|
21468
|
+
}
|
|
21469
|
+
]
|
|
21470
|
+
}
|
|
21429
21471
|
});
|
|
21430
|
-
|
|
21431
|
-
|
|
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
|
-
}
|
|
21437
|
-
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
|
-
),
|
|
21444
|
-
startRowIndex: m.startRowIndex,
|
|
21445
|
-
endRowIndex: m.endRowIndex,
|
|
21446
|
-
startColumnIndex: m.startColumnIndex,
|
|
21447
|
-
endColumnIndex: m.endColumnIndex
|
|
21448
|
-
}));
|
|
21449
|
-
return formatSuccessResponse(
|
|
21472
|
+
return formatToolResponse(
|
|
21473
|
+
`Successfully deleted ${endIndex - startIndex} columns in range ${validatedInput.range}`,
|
|
21450
21474
|
{
|
|
21451
|
-
|
|
21452
|
-
sheetId
|
|
21453
|
-
|
|
21454
|
-
|
|
21455
|
-
}
|
|
21456
|
-
|
|
21475
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
21476
|
+
sheetId,
|
|
21477
|
+
deletedColumns: endIndex - startIndex,
|
|
21478
|
+
range: validatedInput.range
|
|
21479
|
+
}
|
|
21480
|
+
);
|
|
21481
|
+
} catch (error2) {
|
|
21482
|
+
return handleError(error2);
|
|
21483
|
+
}
|
|
21484
|
+
}
|
|
21485
|
+
|
|
21486
|
+
// src/tools/delete-rows.ts
|
|
21487
|
+
var deleteRowsTool = {
|
|
21488
|
+
name: "sheets_delete_rows",
|
|
21489
|
+
description: "Delete one or more rows from a Google Sheet using a full-row A1 range",
|
|
21490
|
+
inputSchema: {
|
|
21491
|
+
type: "object",
|
|
21492
|
+
properties: {
|
|
21493
|
+
spreadsheetId: {
|
|
21494
|
+
type: "string",
|
|
21495
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
21496
|
+
},
|
|
21497
|
+
range: {
|
|
21498
|
+
type: "string",
|
|
21499
|
+
description: 'Full-row A1 range to delete (e.g., "Sheet1!2:4" or "Sheet1!3:3"). If sheet name is omitted, the first sheet is used'
|
|
21500
|
+
}
|
|
21501
|
+
},
|
|
21502
|
+
required: ["spreadsheetId", "range"]
|
|
21503
|
+
}
|
|
21504
|
+
};
|
|
21505
|
+
function parseRowRange(range) {
|
|
21506
|
+
const match = range.match(/^(\d+):(\d+)$/);
|
|
21507
|
+
if (!match?.[1] || !match[2]) {
|
|
21508
|
+
throw new Error('Row range must use full-row A1 notation, e.g. "2:4" or "3:3"');
|
|
21509
|
+
}
|
|
21510
|
+
const startRow = parseInt(match[1], 10);
|
|
21511
|
+
const endRow = parseInt(match[2], 10);
|
|
21512
|
+
if (Number.isNaN(startRow) || Number.isNaN(endRow) || startRow <= 0 || endRow <= 0) {
|
|
21513
|
+
throw new Error("Row numbers must be positive integers");
|
|
21514
|
+
}
|
|
21515
|
+
if (endRow < startRow) {
|
|
21516
|
+
throw new Error("Row range end must be greater than or equal to the start row");
|
|
21517
|
+
}
|
|
21518
|
+
return {
|
|
21519
|
+
startIndex: startRow - 1,
|
|
21520
|
+
endIndex: endRow
|
|
21521
|
+
};
|
|
21522
|
+
}
|
|
21523
|
+
async function handleDeleteRows(input) {
|
|
21524
|
+
try {
|
|
21525
|
+
const validatedInput = validateDeleteRowsInput(input);
|
|
21526
|
+
const sheets = await getAuthenticatedClient();
|
|
21527
|
+
const { sheetName, range } = extractSheetName(validatedInput.range);
|
|
21528
|
+
const sheetId = await getSheetId(sheets, validatedInput.spreadsheetId, sheetName);
|
|
21529
|
+
const { startIndex, endIndex } = parseRowRange(range);
|
|
21530
|
+
await sheets.spreadsheets.batchUpdate({
|
|
21531
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
21532
|
+
requestBody: {
|
|
21533
|
+
requests: [
|
|
21534
|
+
{
|
|
21535
|
+
deleteDimension: {
|
|
21536
|
+
range: {
|
|
21537
|
+
sheetId,
|
|
21538
|
+
dimension: "ROWS",
|
|
21539
|
+
startIndex,
|
|
21540
|
+
endIndex
|
|
21541
|
+
}
|
|
21542
|
+
}
|
|
21543
|
+
}
|
|
21544
|
+
]
|
|
21545
|
+
}
|
|
21546
|
+
});
|
|
21547
|
+
return formatToolResponse(
|
|
21548
|
+
`Successfully deleted ${endIndex - startIndex} rows in range ${validatedInput.range}`,
|
|
21549
|
+
{
|
|
21550
|
+
spreadsheetId: validatedInput.spreadsheetId,
|
|
21551
|
+
sheetId,
|
|
21552
|
+
deletedRows: endIndex - startIndex,
|
|
21553
|
+
range: validatedInput.range
|
|
21554
|
+
}
|
|
21555
|
+
);
|
|
21556
|
+
} catch (error2) {
|
|
21557
|
+
return handleError(error2);
|
|
21558
|
+
}
|
|
21559
|
+
}
|
|
21560
|
+
|
|
21561
|
+
// src/tools/get-merged-cells.ts
|
|
21562
|
+
var inputSchema = external_exports.object({
|
|
21563
|
+
spreadsheetId: external_exports.string(),
|
|
21564
|
+
sheetName: external_exports.string()
|
|
21565
|
+
});
|
|
21566
|
+
var getMergedCellsTool = {
|
|
21567
|
+
name: "sheets_get_merged_cells",
|
|
21568
|
+
description: "Get all merged cell ranges for a specific sheet. Returns each merge as A1 notation and GridRange coordinates.",
|
|
21569
|
+
inputSchema: {
|
|
21570
|
+
type: "object",
|
|
21571
|
+
properties: {
|
|
21572
|
+
spreadsheetId: {
|
|
21573
|
+
type: "string",
|
|
21574
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
21575
|
+
},
|
|
21576
|
+
sheetName: {
|
|
21577
|
+
type: "string",
|
|
21578
|
+
description: "Name of the sheet (tab) to inspect"
|
|
21579
|
+
}
|
|
21580
|
+
},
|
|
21581
|
+
required: ["spreadsheetId", "sheetName"]
|
|
21582
|
+
}
|
|
21583
|
+
};
|
|
21584
|
+
async function handleGetMergedCells(input) {
|
|
21585
|
+
try {
|
|
21586
|
+
const { spreadsheetId, sheetName } = inputSchema.parse(input);
|
|
21587
|
+
const sheets = await getAuthenticatedClient();
|
|
21588
|
+
const response = await sheets.spreadsheets.get({
|
|
21589
|
+
spreadsheetId,
|
|
21590
|
+
fields: "sheets.properties.title,sheets.properties.sheetId,sheets.merges"
|
|
21591
|
+
});
|
|
21592
|
+
const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
|
|
21593
|
+
const merges = (sheetData.merges ?? []).map((m) => ({
|
|
21594
|
+
a1Notation: gridRangeToA1(m),
|
|
21595
|
+
startRowIndex: m.startRowIndex,
|
|
21596
|
+
endRowIndex: m.endRowIndex,
|
|
21597
|
+
startColumnIndex: m.startColumnIndex,
|
|
21598
|
+
endColumnIndex: m.endColumnIndex
|
|
21599
|
+
}));
|
|
21600
|
+
return formatSuccessResponse(
|
|
21601
|
+
{
|
|
21602
|
+
sheetName,
|
|
21603
|
+
sheetId: sheetData.properties?.sheetId,
|
|
21604
|
+
mergeCount: merges.length,
|
|
21605
|
+
merges
|
|
21606
|
+
},
|
|
21607
|
+
`Found ${merges.length} merged range(s) in sheet "${sheetName}"`
|
|
21457
21608
|
);
|
|
21458
21609
|
} catch (error2) {
|
|
21459
21610
|
return handleError(error2);
|
|
@@ -21491,13 +21642,7 @@ async function handleGetSheetDimensions(input) {
|
|
|
21491
21642
|
spreadsheetId,
|
|
21492
21643
|
fields: "sheets.properties,sheets.data.columnMetadata,sheets.data.rowMetadata"
|
|
21493
21644
|
});
|
|
21494
|
-
const sheetData = (response.data.sheets ?? [])
|
|
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
|
-
}
|
|
21645
|
+
const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
|
|
21501
21646
|
const gridProps = sheetData.properties?.gridProperties ?? {};
|
|
21502
21647
|
const gridData = sheetData.data?.[0] ?? {};
|
|
21503
21648
|
const columns = (gridData.columnMetadata ?? []).map(
|
|
@@ -21619,14 +21764,91 @@ async function handleGetSheetFormatting(input) {
|
|
|
21619
21764
|
}
|
|
21620
21765
|
}
|
|
21621
21766
|
|
|
21767
|
+
// src/utils/formula-locale.ts
|
|
21768
|
+
function normalizeFormulaLocale(formula) {
|
|
21769
|
+
if (!formula?.startsWith("=")) {
|
|
21770
|
+
return { normalized: formula, raw: formula, localeDetected: "unknown" };
|
|
21771
|
+
}
|
|
21772
|
+
let result = "";
|
|
21773
|
+
let inString = false;
|
|
21774
|
+
let hasSemicolonSeparator = false;
|
|
21775
|
+
for (let i = 0; i < formula.length; i++) {
|
|
21776
|
+
const char = formula[i];
|
|
21777
|
+
if (char === '"') {
|
|
21778
|
+
if (inString && formula[i + 1] === '"') {
|
|
21779
|
+
result += '""';
|
|
21780
|
+
i++;
|
|
21781
|
+
continue;
|
|
21782
|
+
}
|
|
21783
|
+
inString = !inString;
|
|
21784
|
+
result += char;
|
|
21785
|
+
} else if (char === ";" && !inString) {
|
|
21786
|
+
hasSemicolonSeparator = true;
|
|
21787
|
+
result += ",";
|
|
21788
|
+
} else {
|
|
21789
|
+
result += char;
|
|
21790
|
+
}
|
|
21791
|
+
}
|
|
21792
|
+
const localeDetected = hasSemicolonSeparator ? "semicolon" : "comma";
|
|
21793
|
+
return { normalized: result, raw: formula, localeDetected };
|
|
21794
|
+
}
|
|
21795
|
+
function normalizeConditionalFormatFormulas(rule) {
|
|
21796
|
+
const result = { ...rule };
|
|
21797
|
+
if (result.booleanRule?.condition?.values) {
|
|
21798
|
+
const rawFormulas = [];
|
|
21799
|
+
result.booleanRule = {
|
|
21800
|
+
...result.booleanRule,
|
|
21801
|
+
condition: {
|
|
21802
|
+
...result.booleanRule.condition,
|
|
21803
|
+
values: result.booleanRule.condition.values.map((v) => {
|
|
21804
|
+
if (v.userEnteredValue?.startsWith?.("=")) {
|
|
21805
|
+
const { normalized, raw } = normalizeFormulaLocale(v.userEnteredValue);
|
|
21806
|
+
rawFormulas.push(raw);
|
|
21807
|
+
return { ...v, userEnteredValue: normalized };
|
|
21808
|
+
}
|
|
21809
|
+
return v;
|
|
21810
|
+
})
|
|
21811
|
+
}
|
|
21812
|
+
};
|
|
21813
|
+
if (rawFormulas.length > 0) {
|
|
21814
|
+
result._formulaLocaleRaw = rawFormulas;
|
|
21815
|
+
}
|
|
21816
|
+
}
|
|
21817
|
+
if (result.gradientRule) {
|
|
21818
|
+
const rawFormulas = [];
|
|
21819
|
+
const normalizeThreshold = (threshold) => {
|
|
21820
|
+
if (!threshold) {
|
|
21821
|
+
return threshold;
|
|
21822
|
+
}
|
|
21823
|
+
if (threshold.value?.startsWith?.("=")) {
|
|
21824
|
+
const { normalized, raw } = normalizeFormulaLocale(threshold.value);
|
|
21825
|
+
rawFormulas.push(raw);
|
|
21826
|
+
return { ...threshold, value: normalized };
|
|
21827
|
+
}
|
|
21828
|
+
return threshold;
|
|
21829
|
+
};
|
|
21830
|
+
result.gradientRule = {
|
|
21831
|
+
...result.gradientRule,
|
|
21832
|
+
minpoint: normalizeThreshold(result.gradientRule.minpoint),
|
|
21833
|
+
midpoint: normalizeThreshold(result.gradientRule.midpoint),
|
|
21834
|
+
maxpoint: normalizeThreshold(result.gradientRule.maxpoint)
|
|
21835
|
+
};
|
|
21836
|
+
if (rawFormulas.length > 0) {
|
|
21837
|
+
result._formulaLocaleRaw = rawFormulas;
|
|
21838
|
+
}
|
|
21839
|
+
}
|
|
21840
|
+
return result;
|
|
21841
|
+
}
|
|
21842
|
+
|
|
21622
21843
|
// src/tools/get-conditional-formatting-data.ts
|
|
21623
21844
|
var inputSchema4 = external_exports.object({
|
|
21624
21845
|
spreadsheetId: external_exports.string(),
|
|
21625
|
-
sheetName: external_exports.string()
|
|
21846
|
+
sheetName: external_exports.string(),
|
|
21847
|
+
normalizeFormulas: external_exports.boolean().optional().default(true)
|
|
21626
21848
|
});
|
|
21627
21849
|
var getConditionalFormattingDataTool = {
|
|
21628
21850
|
name: "sheets_get_conditional_formatting",
|
|
21629
|
-
description:
|
|
21851
|
+
description: 'Read conditional formatting rules and banded ranges (alternating row/column colors) for a sheet. CF formulas are normalized to English locale (semicolons \u2192 commas) by default. Each rule with a formula includes a "_formulaLocaleRaw" field with the original unmodified formula. Set normalizeFormulas:false to get raw formulas as returned by the API.',
|
|
21630
21852
|
inputSchema: {
|
|
21631
21853
|
type: "object",
|
|
21632
21854
|
properties: {
|
|
@@ -21637,6 +21859,10 @@ var getConditionalFormattingDataTool = {
|
|
|
21637
21859
|
sheetName: {
|
|
21638
21860
|
type: "string",
|
|
21639
21861
|
description: "Name of the sheet (tab) to inspect"
|
|
21862
|
+
},
|
|
21863
|
+
normalizeFormulas: {
|
|
21864
|
+
type: "boolean",
|
|
21865
|
+
description: 'Default: true. Normalize formula separators to English locale (semicolons \u2192 commas). Each normalized rule includes "_formulaLocaleRaw" with the original formula. Set to false to get formulas exactly as returned by the Google Sheets API.'
|
|
21640
21866
|
}
|
|
21641
21867
|
},
|
|
21642
21868
|
required: ["spreadsheetId", "sheetName"]
|
|
@@ -21644,24 +21870,21 @@ var getConditionalFormattingDataTool = {
|
|
|
21644
21870
|
};
|
|
21645
21871
|
async function handleGetConditionalFormattingData(input) {
|
|
21646
21872
|
try {
|
|
21647
|
-
const { spreadsheetId, sheetName } = inputSchema4.parse(input);
|
|
21873
|
+
const { spreadsheetId, sheetName, normalizeFormulas } = inputSchema4.parse(input);
|
|
21648
21874
|
const sheets = await getAuthenticatedClient();
|
|
21649
21875
|
const response = await sheets.spreadsheets.get({
|
|
21650
21876
|
spreadsheetId,
|
|
21651
21877
|
fields: "sheets.properties.title,sheets.properties.sheetId,sheets.conditionalFormats,sheets.bandedRanges"
|
|
21652
21878
|
});
|
|
21653
|
-
const sheetData = (response.data.sheets ?? [])
|
|
21654
|
-
|
|
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
|
-
}
|
|
21879
|
+
const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
|
|
21880
|
+
const rawFormats = sheetData.conditionalFormats ?? [];
|
|
21881
|
+
const conditionalFormats = normalizeFormulas ? rawFormats.map(normalizeConditionalFormatFormulas) : rawFormats;
|
|
21660
21882
|
return formatSuccessResponse(
|
|
21661
21883
|
{
|
|
21662
21884
|
sheetName,
|
|
21663
21885
|
sheetId: sheetData.properties?.sheetId,
|
|
21664
|
-
|
|
21886
|
+
formulasNormalized: normalizeFormulas,
|
|
21887
|
+
conditionalFormats,
|
|
21665
21888
|
bandedRanges: sheetData.bandedRanges ?? []
|
|
21666
21889
|
},
|
|
21667
21890
|
`Conditional formatting data for sheet "${sheetName}"`
|
|
@@ -21812,11 +22035,12 @@ var inputSchema5 = external_exports.object({
|
|
|
21812
22035
|
includeFormattingRange: external_exports.string().optional(),
|
|
21813
22036
|
useEffectiveFormat: external_exports.boolean().optional().default(false),
|
|
21814
22037
|
fields: external_exports.array(external_exports.string()).optional(),
|
|
21815
|
-
compactMode: external_exports.boolean().optional().default(false)
|
|
22038
|
+
compactMode: external_exports.boolean().optional().default(false),
|
|
22039
|
+
includeConditionalFormatting: external_exports.boolean().optional().default(true)
|
|
21816
22040
|
});
|
|
21817
22041
|
var getFullSheetSnapshotTool = {
|
|
21818
22042
|
name: "sheets_get_full_sheet_snapshot",
|
|
21819
|
-
description: "One-shot tool: reads all structural and formatting metadata for a sheet in a single API call. Returns: sheet properties (frozen rows/cols, dimensions, tab color), merged cells, column widths, row heights,
|
|
22043
|
+
description: "One-shot tool: reads all structural and formatting metadata for a sheet in a single API call. Returns: sheet properties (frozen rows/cols, dimensions, tab color), merged cells, column widths, row heights, banded ranges, and optionally cell-level formatting for a specified range (includeFormattingRange). compactMode is OFF by default \u2014 full per-cell detail is returned unless compactMode:true is provided. When compactMode is ON, adjacent cells with identical formatting are collapsed into range descriptors (90%+ smaller output). Conditional formatting rules are included by default (includeConditionalFormatting:true); set to false to exclude them. CF formulas are normalized to English locale (commas). Use fields to limit which format properties are returned. Use this before programmatically recreating a sheet.",
|
|
21820
22044
|
inputSchema: {
|
|
21821
22045
|
type: "object",
|
|
21822
22046
|
properties: {
|
|
@@ -21843,15 +22067,16 @@ var getFullSheetSnapshotTool = {
|
|
|
21843
22067
|
},
|
|
21844
22068
|
compactMode: {
|
|
21845
22069
|
type: "boolean",
|
|
21846
|
-
description: "When true, adjacent cells with identical formatting are collapsed into range descriptors (run-length encoded)
|
|
22070
|
+
description: "Default: false. Full per-cell formatting is returned unless compactMode:true is set. When true, adjacent cells with identical formatting are collapsed into range descriptors (run-length encoded), reducing output by 90%+ for typical formatted sheets. Only applies when includeFormattingRange is set."
|
|
22071
|
+
},
|
|
22072
|
+
includeConditionalFormatting: {
|
|
22073
|
+
type: "boolean",
|
|
22074
|
+
description: "Default: true. Include conditional formatting rules and banded ranges in the snapshot. CF formulas are normalized to English locale (semicolons \u2192 commas). Set to false to reduce output size when CF rules are not needed."
|
|
21847
22075
|
}
|
|
21848
22076
|
},
|
|
21849
22077
|
required: ["spreadsheetId", "sheetName"]
|
|
21850
22078
|
}
|
|
21851
22079
|
};
|
|
21852
|
-
function gridRangeToA12(startRowIndex, endRowIndex, startColumnIndex, endColumnIndex) {
|
|
21853
|
-
return `${colIndexToLetter(startColumnIndex)}${startRowIndex + 1}:${colIndexToLetter(endColumnIndex - 1)}${endRowIndex}`;
|
|
21854
|
-
}
|
|
21855
22080
|
async function handleGetFullSheetSnapshot(input) {
|
|
21856
22081
|
try {
|
|
21857
22082
|
const {
|
|
@@ -21860,11 +22085,13 @@ async function handleGetFullSheetSnapshot(input) {
|
|
|
21860
22085
|
includeFormattingRange,
|
|
21861
22086
|
useEffectiveFormat,
|
|
21862
22087
|
fields: formatFields,
|
|
21863
|
-
compactMode
|
|
22088
|
+
compactMode,
|
|
22089
|
+
includeConditionalFormatting
|
|
21864
22090
|
} = inputSchema5.parse(input);
|
|
21865
22091
|
const sheets = await getAuthenticatedClient();
|
|
21866
22092
|
const baseFields = "sheets.properties,sheets.merges,sheets.data.columnMetadata,sheets.data.rowMetadata,sheets.conditionalFormats,sheets.bandedRanges";
|
|
21867
22093
|
const formatField = useEffectiveFormat ? "effectiveFormat" : "userEnteredFormat";
|
|
22094
|
+
const compact = compactMode === true;
|
|
21868
22095
|
let cellDataFields;
|
|
21869
22096
|
if (formatFields && formatFields.length > 0) {
|
|
21870
22097
|
cellDataFields = formatFields.map((f) => `sheets.data.rowData.values.${formatField}.${f}`).join(",");
|
|
@@ -21879,23 +22106,12 @@ async function handleGetFullSheetSnapshot(input) {
|
|
|
21879
22106
|
includeGridData: !!includeFormattingRange,
|
|
21880
22107
|
fields: baseFields + cellFormattingFields
|
|
21881
22108
|
});
|
|
21882
|
-
const sheetData = (response.data.sheets ?? [])
|
|
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
|
-
}
|
|
22109
|
+
const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
|
|
21889
22110
|
const props = sheetData.properties;
|
|
21890
22111
|
const gridProps = props.gridProperties ?? {};
|
|
21891
22112
|
const gridData = sheetData.data?.[0] ?? {};
|
|
21892
22113
|
const merges = (sheetData.merges ?? []).map((m) => ({
|
|
21893
|
-
a1Notation:
|
|
21894
|
-
m.startRowIndex ?? 0,
|
|
21895
|
-
m.endRowIndex ?? 0,
|
|
21896
|
-
m.startColumnIndex ?? 0,
|
|
21897
|
-
m.endColumnIndex ?? 0
|
|
21898
|
-
),
|
|
22114
|
+
a1Notation: gridRangeToA1(m),
|
|
21899
22115
|
startRowIndex: m.startRowIndex,
|
|
21900
22116
|
endRowIndex: m.endRowIndex,
|
|
21901
22117
|
startColumnIndex: m.startColumnIndex,
|
|
@@ -21919,7 +22135,7 @@ async function handleGetFullSheetSnapshot(input) {
|
|
|
21919
22135
|
if (includeFormattingRange && gridData.rowData) {
|
|
21920
22136
|
const startRow = gridData.startRow ?? 0;
|
|
21921
22137
|
const startColumn = gridData.startColumn ?? 0;
|
|
21922
|
-
if (
|
|
22138
|
+
if (compact) {
|
|
21923
22139
|
cellFormatting = compactifyCellFormatting(
|
|
21924
22140
|
gridData.rowData,
|
|
21925
22141
|
startRow,
|
|
@@ -21943,6 +22159,11 @@ async function handleGetFullSheetSnapshot(input) {
|
|
|
21943
22159
|
);
|
|
21944
22160
|
}
|
|
21945
22161
|
}
|
|
22162
|
+
const rawConditionalFormats = sheetData.conditionalFormats ?? [];
|
|
22163
|
+
const cfSection = includeConditionalFormatting ? {
|
|
22164
|
+
conditionalFormats: rawConditionalFormats.map(normalizeConditionalFormatFormulas),
|
|
22165
|
+
bandedRanges: sheetData.bandedRanges ?? []
|
|
22166
|
+
} : {};
|
|
21946
22167
|
const snapshot = {
|
|
21947
22168
|
sheetName,
|
|
21948
22169
|
sheetId: props.sheetId,
|
|
@@ -21956,13 +22177,12 @@ async function handleGetFullSheetSnapshot(input) {
|
|
|
21956
22177
|
merges,
|
|
21957
22178
|
columns,
|
|
21958
22179
|
rows,
|
|
21959
|
-
|
|
21960
|
-
bandedRanges: sheetData.bandedRanges ?? [],
|
|
22180
|
+
...cfSection,
|
|
21961
22181
|
...cellFormatting !== null ? {
|
|
21962
22182
|
cellFormatting: {
|
|
21963
22183
|
range: `${sheetName}!${includeFormattingRange}`,
|
|
21964
22184
|
formatType: formatField,
|
|
21965
|
-
compact
|
|
22185
|
+
compact,
|
|
21966
22186
|
data: cellFormatting
|
|
21967
22187
|
}
|
|
21968
22188
|
} : {}
|
|
@@ -22008,13 +22228,7 @@ async function handleGetSheetStructure(input) {
|
|
|
22008
22228
|
fields: "sheets.properties,sheets.merges,sheets.data.columnMetadata,sheets.data.rowMetadata"
|
|
22009
22229
|
});
|
|
22010
22230
|
const allSheets = response.data.sheets ?? [];
|
|
22011
|
-
const sheetData = allSheets
|
|
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
|
-
}
|
|
22231
|
+
const sheetData = findSheetOrThrow(allSheets, sheetName);
|
|
22018
22232
|
const props = sheetData.properties;
|
|
22019
22233
|
const gridProps = props.gridProperties ?? {};
|
|
22020
22234
|
const gridData = sheetData.data?.[0] ?? {};
|
|
@@ -22029,13 +22243,9 @@ async function handleGetSheetStructure(input) {
|
|
|
22029
22243
|
(row) => row.pixelSize ?? null
|
|
22030
22244
|
);
|
|
22031
22245
|
const hiddenRows = (gridData.rowMetadata ?? []).map((row, i) => row.hiddenByUser ? i : -1).filter((i) => i >= 0);
|
|
22032
|
-
const merges = (sheetData.merges ?? []).map(
|
|
22033
|
-
|
|
22034
|
-
|
|
22035
|
-
const sr = (m.startRowIndex ?? 0) + 1;
|
|
22036
|
-
const er = m.endRowIndex ?? 1;
|
|
22037
|
-
return `${colIndexToLetter(sc)}${sr}:${colIndexToLetter(ec)}${er}`;
|
|
22038
|
-
});
|
|
22246
|
+
const merges = (sheetData.merges ?? []).map(
|
|
22247
|
+
(m) => gridRangeToA1(m)
|
|
22248
|
+
);
|
|
22039
22249
|
return formatSuccessResponse(
|
|
22040
22250
|
{
|
|
22041
22251
|
sheetName,
|
|
@@ -22122,13 +22332,7 @@ async function handleGetFormattingCompact(input) {
|
|
|
22122
22332
|
fields: `sheets.properties.title,${cellDataFields},sheets.data.startRow,sheets.data.startColumn`
|
|
22123
22333
|
});
|
|
22124
22334
|
const allSheets = response.data.sheets ?? [];
|
|
22125
|
-
const sheetData = allSheets
|
|
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
|
-
}
|
|
22335
|
+
const sheetData = findSheetOrThrow(allSheets, sheetName);
|
|
22132
22336
|
const gridData = sheetData.data?.[0];
|
|
22133
22337
|
if (!gridData?.rowData) {
|
|
22134
22338
|
return formatSuccessResponse(
|
|
@@ -22188,15 +22392,6 @@ var getDataValidationTool = {
|
|
|
22188
22392
|
required: ["spreadsheetId", "sheetName"]
|
|
22189
22393
|
}
|
|
22190
22394
|
};
|
|
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
22395
|
function validationKey(dv) {
|
|
22201
22396
|
return JSON.stringify({
|
|
22202
22397
|
type: dv.condition?.type ?? null,
|
|
@@ -22272,8 +22467,8 @@ function compactifyValidation(rowData, startRow, startCol) {
|
|
|
22272
22467
|
}
|
|
22273
22468
|
}
|
|
22274
22469
|
}
|
|
22275
|
-
const topLeft = `${
|
|
22276
|
-
const bottomRight = `${
|
|
22470
|
+
const topLeft = `${colIndexToLetter(c + minCol)}${r + minRow + 1}`;
|
|
22471
|
+
const bottomRight = `${colIndexToLetter(endC + minCol)}${endR + minRow + 1}`;
|
|
22277
22472
|
ranges.push(topLeft === bottomRight ? topLeft : `${topLeft}:${bottomRight}`);
|
|
22278
22473
|
}
|
|
22279
22474
|
}
|
|
@@ -22298,13 +22493,7 @@ async function handleGetDataValidation(input) {
|
|
|
22298
22493
|
includeGridData: true,
|
|
22299
22494
|
fields: "sheets.data.rowData.values.dataValidation,sheets.data.startRow,sheets.data.startColumn,sheets.properties.title,sheets.properties.sheetId"
|
|
22300
22495
|
});
|
|
22301
|
-
const sheetData = (response.data.sheets ?? [])
|
|
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
|
-
}
|
|
22496
|
+
const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
|
|
22308
22497
|
const gridData = sheetData.data?.[0];
|
|
22309
22498
|
if (!gridData?.rowData) {
|
|
22310
22499
|
return formatSuccessResponse(
|
|
@@ -22314,11 +22503,7 @@ async function handleGetDataValidation(input) {
|
|
|
22314
22503
|
}
|
|
22315
22504
|
const startRow = gridData.startRow ?? 0;
|
|
22316
22505
|
const startCol = gridData.startColumn ?? 0;
|
|
22317
|
-
const validationRules = compactifyValidation(
|
|
22318
|
-
gridData.rowData,
|
|
22319
|
-
startRow,
|
|
22320
|
-
startCol
|
|
22321
|
-
);
|
|
22506
|
+
const validationRules = compactifyValidation(gridData.rowData, startRow, startCol);
|
|
22322
22507
|
return formatSuccessResponse(
|
|
22323
22508
|
{ sheetName, validationRules },
|
|
22324
22509
|
`Data validation for sheet "${sheetName}" \u2014 ${validationRules.length} unique rule(s)`
|
|
@@ -22351,22 +22536,6 @@ var getBasicFilterTool = {
|
|
|
22351
22536
|
required: ["spreadsheetId", "sheetName"]
|
|
22352
22537
|
}
|
|
22353
22538
|
};
|
|
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
22539
|
async function handleGetBasicFilter(input) {
|
|
22371
22540
|
try {
|
|
22372
22541
|
const { spreadsheetId, sheetName } = inputSchema9.parse(input);
|
|
@@ -22375,13 +22544,7 @@ async function handleGetBasicFilter(input) {
|
|
|
22375
22544
|
spreadsheetId,
|
|
22376
22545
|
fields: "sheets.properties.title,sheets.properties.sheetId,sheets.basicFilter"
|
|
22377
22546
|
});
|
|
22378
|
-
const sheetData = (response.data.sheets ?? [])
|
|
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
|
-
}
|
|
22547
|
+
const sheetData = findSheetOrThrow(response.data.sheets ?? [], sheetName);
|
|
22385
22548
|
const bf = sheetData.basicFilter;
|
|
22386
22549
|
if (!bf) {
|
|
22387
22550
|
return formatSuccessResponse(
|
|
@@ -22396,7 +22559,7 @@ async function handleGetBasicFilter(input) {
|
|
|
22396
22559
|
const fc = spec.filterCriteria;
|
|
22397
22560
|
filterCriteria.push({
|
|
22398
22561
|
columnIndex: colIdx,
|
|
22399
|
-
columnLetter:
|
|
22562
|
+
columnLetter: colIndexToLetter(colIdx),
|
|
22400
22563
|
hiddenValues: fc?.hiddenValues ?? [],
|
|
22401
22564
|
condition: fc?.condition ?? null,
|
|
22402
22565
|
visibleBackgroundColor: fc?.visibleBackgroundColor ?? null,
|
|
@@ -22409,7 +22572,7 @@ async function handleGetBasicFilter(input) {
|
|
|
22409
22572
|
const criteria = fc;
|
|
22410
22573
|
filterCriteria.push({
|
|
22411
22574
|
columnIndex: idx,
|
|
22412
|
-
columnLetter:
|
|
22575
|
+
columnLetter: colIndexToLetter(idx),
|
|
22413
22576
|
hiddenValues: criteria.hiddenValues ?? [],
|
|
22414
22577
|
condition: criteria.condition ?? null,
|
|
22415
22578
|
visibleBackgroundColor: criteria.visibleBackgroundColor ?? null,
|
|
@@ -22417,7 +22580,7 @@ async function handleGetBasicFilter(input) {
|
|
|
22417
22580
|
});
|
|
22418
22581
|
}
|
|
22419
22582
|
}
|
|
22420
|
-
const rangeA1 = bf.range ?
|
|
22583
|
+
const rangeA1 = bf.range ? gridRangeToA1(bf.range) : null;
|
|
22421
22584
|
return formatSuccessResponse(
|
|
22422
22585
|
{
|
|
22423
22586
|
sheetName,
|
|
@@ -22426,7 +22589,7 @@ async function handleGetBasicFilter(input) {
|
|
|
22426
22589
|
range: rangeA1,
|
|
22427
22590
|
sortSpecs: (bf.sortSpecs ?? []).map((s) => ({
|
|
22428
22591
|
columnIndex: s.dimensionIndex,
|
|
22429
|
-
columnLetter: s.dimensionIndex !== null && s.dimensionIndex !== void 0 ?
|
|
22592
|
+
columnLetter: s.dimensionIndex !== null && s.dimensionIndex !== void 0 ? colIndexToLetter(s.dimensionIndex) : null,
|
|
22430
22593
|
sortOrder: s.sortOrder ?? null
|
|
22431
22594
|
})),
|
|
22432
22595
|
filterCriteria
|
|
@@ -22439,6 +22602,345 @@ async function handleGetBasicFilter(input) {
|
|
|
22439
22602
|
}
|
|
22440
22603
|
}
|
|
22441
22604
|
|
|
22605
|
+
// src/tools/get-border-map.ts
|
|
22606
|
+
var inputSchema10 = external_exports.object({
|
|
22607
|
+
spreadsheetId: external_exports.string(),
|
|
22608
|
+
range: external_exports.string().describe('Range with sheet prefix, e.g. "Sheet1!A1:F10"'),
|
|
22609
|
+
includeStyle: external_exports.boolean().optional().default(false)
|
|
22610
|
+
});
|
|
22611
|
+
var getBorderMapTool = {
|
|
22612
|
+
name: "sheets_get_border_map",
|
|
22613
|
+
description: 'Returns a visual tabular map of borders for a range. Instead of per-cell JSON with 4 separate border objects, returns compact grids showing which cells have top/bottom/left/right borders and their styles. Solves the ambiguity between "right border of cell N" vs "left border of cell N+1". Output: a horizontal-lines grid and a vertical-lines grid, each as a 2D array of line styles. Set includeStyle:true to include color and width details (larger output).',
|
|
22614
|
+
inputSchema: {
|
|
22615
|
+
type: "object",
|
|
22616
|
+
properties: {
|
|
22617
|
+
spreadsheetId: {
|
|
22618
|
+
type: "string",
|
|
22619
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
22620
|
+
},
|
|
22621
|
+
range: {
|
|
22622
|
+
type: "string",
|
|
22623
|
+
description: 'Range with sheet prefix, e.g. "Sheet1!A1:F10". Sheet name is required to resolve the range correctly.'
|
|
22624
|
+
},
|
|
22625
|
+
includeStyle: {
|
|
22626
|
+
type: "boolean",
|
|
22627
|
+
description: "Default: false. When true, each border line includes color and width details. When false, only the style name (SOLID, DASHED, etc.) is shown \u2014 more compact."
|
|
22628
|
+
}
|
|
22629
|
+
},
|
|
22630
|
+
required: ["spreadsheetId", "range"]
|
|
22631
|
+
}
|
|
22632
|
+
};
|
|
22633
|
+
function encodeBorder(border, includeStyle) {
|
|
22634
|
+
if (!border || border.style === "NONE" || !border.style) {
|
|
22635
|
+
return null;
|
|
22636
|
+
}
|
|
22637
|
+
if (!includeStyle) {
|
|
22638
|
+
return border.style;
|
|
22639
|
+
}
|
|
22640
|
+
return {
|
|
22641
|
+
style: border.style,
|
|
22642
|
+
...border.colorStyle ? { colorStyle: border.colorStyle } : {},
|
|
22643
|
+
...border.color ? { color: border.color } : {},
|
|
22644
|
+
...border.width !== void 0 ? { width: border.width } : {}
|
|
22645
|
+
};
|
|
22646
|
+
}
|
|
22647
|
+
async function handleGetBorderMap(input) {
|
|
22648
|
+
try {
|
|
22649
|
+
const { spreadsheetId, range, includeStyle } = inputSchema10.parse(input);
|
|
22650
|
+
if (!range.includes("!")) {
|
|
22651
|
+
throw new Error('Range must include sheet name prefix, e.g. "Sheet1!A1:F10"');
|
|
22652
|
+
}
|
|
22653
|
+
const { sheetName, range: rangeOnly } = extractSheetName(range);
|
|
22654
|
+
if (!sheetName) {
|
|
22655
|
+
throw new Error('Range must include sheet name prefix, e.g. "Sheet1!A1:F10"');
|
|
22656
|
+
}
|
|
22657
|
+
const rangeMatch = rangeOnly.match(/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/i);
|
|
22658
|
+
if (!rangeMatch) {
|
|
22659
|
+
throw new Error(`Invalid range format: "${rangeOnly}". Expected e.g. "A1:F10".`);
|
|
22660
|
+
}
|
|
22661
|
+
const startCol = columnToIndex(rangeMatch[1].toUpperCase());
|
|
22662
|
+
const startRow = parseInt(rangeMatch[2]) - 1;
|
|
22663
|
+
const endCol = columnToIndex(rangeMatch[3].toUpperCase()) + 1;
|
|
22664
|
+
const endRow = parseInt(rangeMatch[4]);
|
|
22665
|
+
const numRows = endRow - startRow;
|
|
22666
|
+
const numCols = endCol - startCol;
|
|
22667
|
+
const sheets = await getAuthenticatedClient();
|
|
22668
|
+
const response = await sheets.spreadsheets.get({
|
|
22669
|
+
spreadsheetId,
|
|
22670
|
+
ranges: [range],
|
|
22671
|
+
includeGridData: true,
|
|
22672
|
+
fields: "sheets.properties.title,sheets.data.startRow,sheets.data.startColumn,sheets.data.rowData.values.userEnteredFormat.borders"
|
|
22673
|
+
});
|
|
22674
|
+
const sheetData = (response.data.sheets ?? []).find(
|
|
22675
|
+
(s) => s.properties?.title === sheetName
|
|
22676
|
+
);
|
|
22677
|
+
if (!sheetData) {
|
|
22678
|
+
const available = (response.data.sheets ?? []).map((s) => s.properties?.title).filter(Boolean).join(", ");
|
|
22679
|
+
throw new Error(`Sheet "${sheetName}" not found. Available: ${available}`);
|
|
22680
|
+
}
|
|
22681
|
+
const gridData = sheetData.data?.[0] ?? {};
|
|
22682
|
+
const rowData = gridData.rowData ?? [];
|
|
22683
|
+
const getBorder = (rowOffset, colOffset) => {
|
|
22684
|
+
const row = rowData[rowOffset];
|
|
22685
|
+
const cell = row?.values?.[colOffset];
|
|
22686
|
+
const borders = cell?.userEnteredFormat?.borders;
|
|
22687
|
+
return [
|
|
22688
|
+
borders?.top ?? null,
|
|
22689
|
+
borders?.bottom ?? null,
|
|
22690
|
+
borders?.left ?? null,
|
|
22691
|
+
borders?.right ?? null
|
|
22692
|
+
];
|
|
22693
|
+
};
|
|
22694
|
+
const horizontalLines = [];
|
|
22695
|
+
for (let r = 0; r <= numRows; r++) {
|
|
22696
|
+
const row = [];
|
|
22697
|
+
for (let c = 0; c < numCols; c++) {
|
|
22698
|
+
let line = null;
|
|
22699
|
+
if (r > 0) {
|
|
22700
|
+
const [, bottom] = getBorder(r - 1, c);
|
|
22701
|
+
if (bottom?.style && bottom.style !== "NONE") {
|
|
22702
|
+
line = encodeBorder(bottom, includeStyle);
|
|
22703
|
+
}
|
|
22704
|
+
}
|
|
22705
|
+
if (r < numRows && line === null) {
|
|
22706
|
+
const [top] = getBorder(r, c);
|
|
22707
|
+
if (top?.style && top.style !== "NONE") {
|
|
22708
|
+
line = encodeBorder(top, includeStyle);
|
|
22709
|
+
}
|
|
22710
|
+
}
|
|
22711
|
+
row.push(line);
|
|
22712
|
+
}
|
|
22713
|
+
horizontalLines.push(row);
|
|
22714
|
+
}
|
|
22715
|
+
const verticalLines = [];
|
|
22716
|
+
for (let r = 0; r < numRows; r++) {
|
|
22717
|
+
const row = [];
|
|
22718
|
+
for (let c = 0; c <= numCols; c++) {
|
|
22719
|
+
let line = null;
|
|
22720
|
+
if (c < numCols) {
|
|
22721
|
+
const [, , left] = getBorder(r, c);
|
|
22722
|
+
if (left?.style && left.style !== "NONE") {
|
|
22723
|
+
line = encodeBorder(left, includeStyle);
|
|
22724
|
+
}
|
|
22725
|
+
}
|
|
22726
|
+
if (c > 0 && line === null) {
|
|
22727
|
+
const [, , , right] = getBorder(r, c - 1);
|
|
22728
|
+
if (right?.style && right.style !== "NONE") {
|
|
22729
|
+
line = encodeBorder(right, includeStyle);
|
|
22730
|
+
}
|
|
22731
|
+
}
|
|
22732
|
+
row.push(line);
|
|
22733
|
+
}
|
|
22734
|
+
verticalLines.push(row);
|
|
22735
|
+
}
|
|
22736
|
+
const colHeaders = Array.from({ length: numCols }, (_, i) => colIndexToLetter(startCol + i));
|
|
22737
|
+
const rowHeaders = Array.from({ length: numRows }, (_, i) => String(startRow + i + 1));
|
|
22738
|
+
return formatSuccessResponse(
|
|
22739
|
+
{
|
|
22740
|
+
range,
|
|
22741
|
+
sheetName,
|
|
22742
|
+
dimensions: { rows: numRows, cols: numCols },
|
|
22743
|
+
colHeaders,
|
|
22744
|
+
rowHeaders,
|
|
22745
|
+
// horizontalLines: (numRows+1) × numCols — line above each cell row
|
|
22746
|
+
horizontalLines: {
|
|
22747
|
+
description: "horizontalLines[r][c] = style of horizontal border line above row r at column c. r=0 \u2192 top edge, r=numRows \u2192 bottom edge.",
|
|
22748
|
+
rowCount: numRows + 1,
|
|
22749
|
+
colCount: numCols,
|
|
22750
|
+
data: horizontalLines
|
|
22751
|
+
},
|
|
22752
|
+
// verticalLines: numRows × (numCols+1) — line left of each cell column
|
|
22753
|
+
verticalLines: {
|
|
22754
|
+
description: "verticalLines[r][c] = style of vertical border line left of column c at row r. c=0 \u2192 left edge, c=numCols \u2192 right edge.",
|
|
22755
|
+
rowCount: numRows,
|
|
22756
|
+
colCount: numCols + 1,
|
|
22757
|
+
data: verticalLines
|
|
22758
|
+
}
|
|
22759
|
+
},
|
|
22760
|
+
`Border map for ${range} (${numRows}\xD7${numCols} cells)`
|
|
22761
|
+
);
|
|
22762
|
+
} catch (error2) {
|
|
22763
|
+
return handleError(error2);
|
|
22764
|
+
}
|
|
22765
|
+
}
|
|
22766
|
+
|
|
22767
|
+
// src/tools/compare-ranges.ts
|
|
22768
|
+
var inputSchema11 = external_exports.object({
|
|
22769
|
+
spreadsheetId: external_exports.string(),
|
|
22770
|
+
rangeA: external_exports.string().describe('First range with sheet prefix, e.g. "Sheet1!A6:Z6"'),
|
|
22771
|
+
rangeB: external_exports.string().describe('Second range with sheet prefix, e.g. "Sheet1!A7:Z7"'),
|
|
22772
|
+
fields: external_exports.array(external_exports.string()).optional(),
|
|
22773
|
+
useEffectiveFormat: external_exports.boolean().optional().default(false)
|
|
22774
|
+
});
|
|
22775
|
+
var compareRangesTool = {
|
|
22776
|
+
name: "sheets_compare_ranges",
|
|
22777
|
+
description: 'Compare cell formatting between two ranges of identical dimensions. Useful for verifying repeated patterns, e.g. "do all data rows 6\u201385 have identical formatting?" or "is row 10 formatted identically to the template row 5?". Returns a diff listing only the cells and properties that differ between the two ranges. Cells are compared position-by-position; rangeA and rangeB must have the same number of rows and columns. Use fields to restrict comparison to specific format properties.',
|
|
22778
|
+
inputSchema: {
|
|
22779
|
+
type: "object",
|
|
22780
|
+
properties: {
|
|
22781
|
+
spreadsheetId: {
|
|
22782
|
+
type: "string",
|
|
22783
|
+
description: "The ID of the spreadsheet (found in the URL after /d/)"
|
|
22784
|
+
},
|
|
22785
|
+
rangeA: {
|
|
22786
|
+
type: "string",
|
|
22787
|
+
description: 'First range with sheet prefix, e.g. "Sheet1!A6:Z6"'
|
|
22788
|
+
},
|
|
22789
|
+
rangeB: {
|
|
22790
|
+
type: "string",
|
|
22791
|
+
description: 'Second range with sheet prefix, e.g. "Sheet1!A7:Z7"'
|
|
22792
|
+
},
|
|
22793
|
+
fields: {
|
|
22794
|
+
type: "array",
|
|
22795
|
+
items: { type: "string" },
|
|
22796
|
+
description: 'Optional list of format property names to compare, e.g. ["backgroundColor", "textFormat"]. All format properties compared if omitted.'
|
|
22797
|
+
},
|
|
22798
|
+
useEffectiveFormat: {
|
|
22799
|
+
type: "boolean",
|
|
22800
|
+
description: "Default: false. Compare effectiveFormat (true) or userEnteredFormat (false). effectiveFormat includes conditional formatting overlays."
|
|
22801
|
+
}
|
|
22802
|
+
},
|
|
22803
|
+
required: ["spreadsheetId", "rangeA", "rangeB"]
|
|
22804
|
+
}
|
|
22805
|
+
};
|
|
22806
|
+
function parseRangeParts(range) {
|
|
22807
|
+
if (!range.includes("!")) {
|
|
22808
|
+
throw new Error(`Range must include sheet name prefix, e.g. "Sheet1!A1:F10". Got: "${range}"`);
|
|
22809
|
+
}
|
|
22810
|
+
const { sheetName, range: rangeOnly } = extractSheetName(range);
|
|
22811
|
+
if (!sheetName) {
|
|
22812
|
+
throw new Error(`Range must include sheet name prefix, e.g. "Sheet1!A1:F10". Got: "${range}"`);
|
|
22813
|
+
}
|
|
22814
|
+
const match = rangeOnly.match(/^([A-Z]+)(\d+):([A-Z]+)(\d+)$/i);
|
|
22815
|
+
if (!match) {
|
|
22816
|
+
throw new Error(`Invalid range format: "${rangeOnly}". Expected e.g. "A1:F10".`);
|
|
22817
|
+
}
|
|
22818
|
+
return {
|
|
22819
|
+
sheetName,
|
|
22820
|
+
startCol: columnToIndex(match[1].toUpperCase()),
|
|
22821
|
+
startRow: parseInt(match[2]) - 1,
|
|
22822
|
+
endCol: columnToIndex(match[3].toUpperCase()) + 1,
|
|
22823
|
+
// exclusive
|
|
22824
|
+
endRow: parseInt(match[4])
|
|
22825
|
+
// exclusive
|
|
22826
|
+
};
|
|
22827
|
+
}
|
|
22828
|
+
function getFmtKey(rowData, rowOffset, colOffset, useEffectiveFormat, fields) {
|
|
22829
|
+
const row = rowData[rowOffset];
|
|
22830
|
+
const cell = row?.values?.[colOffset];
|
|
22831
|
+
const rawFmt = useEffectiveFormat ? cell?.effectiveFormat : cell?.userEnteredFormat;
|
|
22832
|
+
if (!rawFmt) {
|
|
22833
|
+
return { key: "{}", fmt: {} };
|
|
22834
|
+
}
|
|
22835
|
+
const fmt = extractFormatFields(rawFmt, fields);
|
|
22836
|
+
return { key: JSON.stringify(fmt), fmt };
|
|
22837
|
+
}
|
|
22838
|
+
function deepDiffProperties(fmtA, fmtB) {
|
|
22839
|
+
const allKeys = /* @__PURE__ */ new Set([...Object.keys(fmtA), ...Object.keys(fmtB)]);
|
|
22840
|
+
const diffs = {};
|
|
22841
|
+
for (const key of allKeys) {
|
|
22842
|
+
const aVal = fmtA[key];
|
|
22843
|
+
const bVal = fmtB[key];
|
|
22844
|
+
if (JSON.stringify(aVal) !== JSON.stringify(bVal)) {
|
|
22845
|
+
diffs[key] = { a: aVal ?? null, b: bVal ?? null };
|
|
22846
|
+
}
|
|
22847
|
+
}
|
|
22848
|
+
return diffs;
|
|
22849
|
+
}
|
|
22850
|
+
async function handleCompareRanges(input) {
|
|
22851
|
+
try {
|
|
22852
|
+
let findGridData2 = function(parsed, rangeStr) {
|
|
22853
|
+
for (const s of allSheets) {
|
|
22854
|
+
if (s.properties?.title !== parsed.sheetName) {
|
|
22855
|
+
continue;
|
|
22856
|
+
}
|
|
22857
|
+
for (const gd of s.data ?? []) {
|
|
22858
|
+
const gdStartRow = gd.startRow ?? 0;
|
|
22859
|
+
const gdStartCol = gd.startColumn ?? 0;
|
|
22860
|
+
if (gdStartRow === parsed.startRow && gdStartCol === parsed.startCol) {
|
|
22861
|
+
return gd;
|
|
22862
|
+
}
|
|
22863
|
+
}
|
|
22864
|
+
}
|
|
22865
|
+
throw new Error(`Could not locate grid data for range "${rangeStr}". Check the sheet name.`);
|
|
22866
|
+
};
|
|
22867
|
+
var findGridData = findGridData2;
|
|
22868
|
+
const {
|
|
22869
|
+
spreadsheetId,
|
|
22870
|
+
rangeA,
|
|
22871
|
+
rangeB,
|
|
22872
|
+
fields: formatFields,
|
|
22873
|
+
useEffectiveFormat
|
|
22874
|
+
} = inputSchema11.parse(input);
|
|
22875
|
+
const parsedA = parseRangeParts(rangeA);
|
|
22876
|
+
const parsedB = parseRangeParts(rangeB);
|
|
22877
|
+
const numRowsA = parsedA.endRow - parsedA.startRow;
|
|
22878
|
+
const numColsA = parsedA.endCol - parsedA.startCol;
|
|
22879
|
+
const numRowsB = parsedB.endRow - parsedB.startRow;
|
|
22880
|
+
const numColsB = parsedB.endCol - parsedB.startCol;
|
|
22881
|
+
if (numColsA !== numColsB) {
|
|
22882
|
+
throw new Error(
|
|
22883
|
+
`Ranges must have the same number of columns. rangeA has ${numColsA} columns, rangeB has ${numColsB}.`
|
|
22884
|
+
);
|
|
22885
|
+
}
|
|
22886
|
+
if (numRowsA !== numRowsB) {
|
|
22887
|
+
throw new Error(
|
|
22888
|
+
`Ranges must have the same number of rows. rangeA has ${numRowsA} rows, rangeB has ${numRowsB}.`
|
|
22889
|
+
);
|
|
22890
|
+
}
|
|
22891
|
+
const formatField = useEffectiveFormat ? "effectiveFormat" : "userEnteredFormat";
|
|
22892
|
+
const sheets = await getAuthenticatedClient();
|
|
22893
|
+
const response = await sheets.spreadsheets.get({
|
|
22894
|
+
spreadsheetId,
|
|
22895
|
+
ranges: [rangeA, rangeB],
|
|
22896
|
+
includeGridData: true,
|
|
22897
|
+
fields: `sheets.properties.title,sheets.data.startRow,sheets.data.startColumn,sheets.data.rowData.values.${formatField}`
|
|
22898
|
+
});
|
|
22899
|
+
const allSheets = response.data.sheets ?? [];
|
|
22900
|
+
const gdA = findGridData2(parsedA, rangeA);
|
|
22901
|
+
const gdB = findGridData2(parsedB, rangeB);
|
|
22902
|
+
const rowDataA = gdA.rowData ?? [];
|
|
22903
|
+
const rowDataB = gdB.rowData ?? [];
|
|
22904
|
+
const diffs = [];
|
|
22905
|
+
let equalCells = 0;
|
|
22906
|
+
for (let r = 0; r < numRowsA; r++) {
|
|
22907
|
+
for (let c = 0; c < numColsA; c++) {
|
|
22908
|
+
const { fmt: fmtA } = getFmtKey(rowDataA, r, c, useEffectiveFormat, formatFields);
|
|
22909
|
+
const { fmt: fmtB } = getFmtKey(rowDataB, r, c, useEffectiveFormat, formatFields);
|
|
22910
|
+
const propDiffs = deepDiffProperties(fmtA, fmtB);
|
|
22911
|
+
if (Object.keys(propDiffs).length === 0) {
|
|
22912
|
+
equalCells++;
|
|
22913
|
+
continue;
|
|
22914
|
+
}
|
|
22915
|
+
diffs.push({
|
|
22916
|
+
cellA: `${colIndexToLetter(parsedA.startCol + c)}${parsedA.startRow + r + 1}`,
|
|
22917
|
+
cellB: `${colIndexToLetter(parsedB.startCol + c)}${parsedB.startRow + r + 1}`,
|
|
22918
|
+
diffs: propDiffs
|
|
22919
|
+
});
|
|
22920
|
+
}
|
|
22921
|
+
}
|
|
22922
|
+
const totalCells = numRowsA * numColsA;
|
|
22923
|
+
const identical = diffs.length === 0;
|
|
22924
|
+
return formatSuccessResponse(
|
|
22925
|
+
{
|
|
22926
|
+
rangeA,
|
|
22927
|
+
rangeB,
|
|
22928
|
+
dimensions: { rows: numRowsA, cols: numColsA, totalCells },
|
|
22929
|
+
formatType: formatField,
|
|
22930
|
+
fieldsCompared: formatFields ?? "all",
|
|
22931
|
+
identical,
|
|
22932
|
+
summary: identical ? `All ${totalCells} cells have identical formatting.` : `${diffs.length} of ${totalCells} cells differ. ${equalCells} cells are identical.`,
|
|
22933
|
+
equalCells,
|
|
22934
|
+
diffCount: diffs.length,
|
|
22935
|
+
diffs
|
|
22936
|
+
},
|
|
22937
|
+
identical ? `Ranges ${rangeA} and ${rangeB} have identical formatting` : `Found ${diffs.length} formatting difference(s) between ${rangeA} and ${rangeB}`
|
|
22938
|
+
);
|
|
22939
|
+
} catch (error2) {
|
|
22940
|
+
return handleError(error2);
|
|
22941
|
+
}
|
|
22942
|
+
}
|
|
22943
|
+
|
|
22442
22944
|
// src/resources/index.ts
|
|
22443
22945
|
function parseResourceUri(uri) {
|
|
22444
22946
|
let path;
|
|
@@ -22876,6 +23378,8 @@ var toolHandlers = /* @__PURE__ */ new Map([
|
|
|
22876
23378
|
["sheets_insert_date", handleInsertDate],
|
|
22877
23379
|
// Row operations
|
|
22878
23380
|
["sheets_insert_rows", handleInsertRows],
|
|
23381
|
+
["sheets_delete_columns", handleDeleteColumns],
|
|
23382
|
+
["sheets_delete_rows", handleDeleteRows],
|
|
22879
23383
|
// READ / Snapshot tools
|
|
22880
23384
|
["sheets_get_merged_cells", handleGetMergedCells],
|
|
22881
23385
|
["sheets_get_sheet_dimensions", handleGetSheetDimensions],
|
|
@@ -22885,7 +23389,10 @@ var toolHandlers = /* @__PURE__ */ new Map([
|
|
|
22885
23389
|
["sheets_get_sheet_structure", handleGetSheetStructure],
|
|
22886
23390
|
["sheets_get_formatting_compact", handleGetFormattingCompact],
|
|
22887
23391
|
["sheets_get_data_validation", handleGetDataValidation],
|
|
22888
|
-
["sheets_get_basic_filter", handleGetBasicFilter]
|
|
23392
|
+
["sheets_get_basic_filter", handleGetBasicFilter],
|
|
23393
|
+
// Border and comparison tools
|
|
23394
|
+
["sheets_get_border_map", handleGetBorderMap],
|
|
23395
|
+
["sheets_compare_ranges", handleCompareRanges]
|
|
22889
23396
|
]);
|
|
22890
23397
|
var allTools = [
|
|
22891
23398
|
checkAccessTool,
|
|
@@ -22915,6 +23422,8 @@ var allTools = [
|
|
|
22915
23422
|
insertLinkTool,
|
|
22916
23423
|
insertDateTool,
|
|
22917
23424
|
insertRowsTool,
|
|
23425
|
+
deleteColumnsTool,
|
|
23426
|
+
deleteRowsTool,
|
|
22918
23427
|
getMergedCellsTool,
|
|
22919
23428
|
getSheetDimensionsTool,
|
|
22920
23429
|
getSheetFormattingTool,
|
|
@@ -22923,7 +23432,9 @@ var allTools = [
|
|
|
22923
23432
|
getSheetStructureTool,
|
|
22924
23433
|
getFormattingCompactTool,
|
|
22925
23434
|
getDataValidationTool,
|
|
22926
|
-
getBasicFilterTool
|
|
23435
|
+
getBasicFilterTool,
|
|
23436
|
+
getBorderMapTool,
|
|
23437
|
+
compareRangesTool
|
|
22927
23438
|
];
|
|
22928
23439
|
async function main() {
|
|
22929
23440
|
try {
|
|
@@ -22935,7 +23446,7 @@ async function main() {
|
|
|
22935
23446
|
const server = new Server(
|
|
22936
23447
|
{
|
|
22937
23448
|
name: "spreadsheet",
|
|
22938
|
-
version: "1.
|
|
23449
|
+
version: "1.8.0"
|
|
22939
23450
|
},
|
|
22940
23451
|
{
|
|
22941
23452
|
capabilities: {
|