gogcli-mcp-sheets 2.0.12 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +81 -0
- package/dist/index.js +405 -36
- package/manifest.json +70 -2
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/tools/sheets-extra.ts +328 -7
- package/tests/tools/sheets-extra.test.ts +547 -3
package/dist/index.js
CHANGED
|
@@ -31074,7 +31074,7 @@ async function run(args, options = {}) {
|
|
|
31074
31074
|
|
|
31075
31075
|
// ../gogcli-mcp/src/tools/utils.ts
|
|
31076
31076
|
var accountParam = external_exports.string().optional().describe(
|
|
31077
|
-
"Google account email to use
|
|
31077
|
+
"Google account email to use, e.g. you@gmail.com \u2014 must be the full address, not a bare username. Overrides the GOG_ACCOUNT env var. Omit to use the single configured account."
|
|
31078
31078
|
);
|
|
31079
31079
|
var ids = {
|
|
31080
31080
|
course: external_exports.string().describe("Course ID"),
|
|
@@ -31133,26 +31133,41 @@ function toError(err) {
|
|
|
31133
31133
|
}
|
|
31134
31134
|
var AUTH_ERROR_PATTERN = /\b(401|unauthorized|token.*(expired|revoked)|invalid_grant)\b/i;
|
|
31135
31135
|
var TRANSIENT_ERROR_PATTERN = /\b429\b|\b5\d\d\b|\bquota\b|rateLimit|\bDEADLINE_EXCEEDED\b/i;
|
|
31136
|
+
var GRID_LIMIT_ERROR_PATTERN = /exceeds grid limits/i;
|
|
31136
31137
|
var AUTH_HINT = "\n\nAuthentication may have expired. Use gog_auth_add to re-authorize the account. Ask the user if they would like to re-authenticate.";
|
|
31137
31138
|
var TRANSIENT_HINT = "\n\nThis error is often transient. Retry the same call before trying a different approach (do not fall back to smaller writes or row-by-row operations).";
|
|
31139
|
+
var GRID_LIMIT_HINT = "\n\nThe target range is outside the sheet's current grid. Add the missing rows or columns first with gog_sheets_insert (dimension: rows or cols), then retry the write.";
|
|
31140
|
+
function formatAccountList(raw) {
|
|
31141
|
+
try {
|
|
31142
|
+
const parsed = JSON.parse(raw);
|
|
31143
|
+
if (Array.isArray(parsed?.accounts)) {
|
|
31144
|
+
return parsed.accounts.map((a) => a?.email).filter(Boolean).join("\n");
|
|
31145
|
+
}
|
|
31146
|
+
} catch {
|
|
31147
|
+
}
|
|
31148
|
+
return raw.trim();
|
|
31149
|
+
}
|
|
31150
|
+
async function diagnose(err) {
|
|
31151
|
+
const errText = toError(err).content[0].text;
|
|
31152
|
+
const isAuthError = AUTH_ERROR_PATTERN.test(errText);
|
|
31153
|
+
const isTransientError = !isAuthError && TRANSIENT_ERROR_PATTERN.test(errText);
|
|
31154
|
+
const isGridLimitError = GRID_LIMIT_ERROR_PATTERN.test(errText);
|
|
31155
|
+
const hint = isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : isGridLimitError ? GRID_LIMIT_HINT : "";
|
|
31156
|
+
try {
|
|
31157
|
+
const accounts = formatAccountList(await run(["auth", "list"]));
|
|
31158
|
+
return toText(`${errText}
|
|
31159
|
+
|
|
31160
|
+
Configured accounts:
|
|
31161
|
+
${accounts || "(none)"}${hint}`);
|
|
31162
|
+
} catch {
|
|
31163
|
+
return toText(`${errText}${hint}`);
|
|
31164
|
+
}
|
|
31165
|
+
}
|
|
31138
31166
|
async function runOrDiagnose(args, options) {
|
|
31139
31167
|
try {
|
|
31140
31168
|
return toText(await run(args, options));
|
|
31141
31169
|
} catch (err) {
|
|
31142
|
-
|
|
31143
|
-
const errText = base.content[0].text;
|
|
31144
|
-
const isAuthError = AUTH_ERROR_PATTERN.test(errText);
|
|
31145
|
-
const isTransientError = !isAuthError && TRANSIENT_ERROR_PATTERN.test(errText);
|
|
31146
|
-
const hint = isAuthError ? AUTH_HINT : isTransientError ? TRANSIENT_HINT : "";
|
|
31147
|
-
try {
|
|
31148
|
-
const accounts = await run(["auth", "list"]);
|
|
31149
|
-
return toText(`${errText}
|
|
31150
|
-
|
|
31151
|
-
Configured accounts:
|
|
31152
|
-
${accounts}${hint}`);
|
|
31153
|
-
} catch {
|
|
31154
|
-
return toText(`${errText}${hint}`);
|
|
31155
|
-
}
|
|
31170
|
+
return diagnose(err);
|
|
31156
31171
|
}
|
|
31157
31172
|
}
|
|
31158
31173
|
|
|
@@ -31218,8 +31233,62 @@ function registerAuthTools(server2) {
|
|
|
31218
31233
|
});
|
|
31219
31234
|
}
|
|
31220
31235
|
|
|
31236
|
+
// ../gogcli-mcp/src/tools/sheets-a1.ts
|
|
31237
|
+
function colToLetter(n) {
|
|
31238
|
+
let s = "";
|
|
31239
|
+
while (n > 0) {
|
|
31240
|
+
const rem = (n - 1) % 26;
|
|
31241
|
+
s = String.fromCharCode(65 + rem) + s;
|
|
31242
|
+
n = Math.floor((n - 1) / 26);
|
|
31243
|
+
}
|
|
31244
|
+
return s;
|
|
31245
|
+
}
|
|
31246
|
+
function letterToCol(s) {
|
|
31247
|
+
let n = 0;
|
|
31248
|
+
for (const ch of s.toUpperCase()) {
|
|
31249
|
+
n = n * 26 + (ch.charCodeAt(0) - 64);
|
|
31250
|
+
}
|
|
31251
|
+
return n;
|
|
31252
|
+
}
|
|
31253
|
+
function expandAnchorRange(range, rows, cols) {
|
|
31254
|
+
const bang = range.lastIndexOf("!");
|
|
31255
|
+
const sheet = bang >= 0 ? range.slice(0, bang + 1) : "";
|
|
31256
|
+
const cell = bang >= 0 ? range.slice(bang + 1) : range;
|
|
31257
|
+
const m = /^([A-Za-z]+)([0-9]+)$/.exec(cell);
|
|
31258
|
+
if (!m) return range;
|
|
31259
|
+
const startCol = letterToCol(m[1]);
|
|
31260
|
+
const startRow = parseInt(m[2], 10);
|
|
31261
|
+
const endCol = colToLetter(startCol + cols - 1);
|
|
31262
|
+
const endRow = startRow + rows - 1;
|
|
31263
|
+
return `${sheet}${m[1].toUpperCase()}${startRow}:${endCol}${endRow}`;
|
|
31264
|
+
}
|
|
31265
|
+
function countNonEmptyCells(getOutput) {
|
|
31266
|
+
let parsed;
|
|
31267
|
+
try {
|
|
31268
|
+
parsed = JSON.parse(getOutput);
|
|
31269
|
+
} catch {
|
|
31270
|
+
return -1;
|
|
31271
|
+
}
|
|
31272
|
+
const values = parsed?.values;
|
|
31273
|
+
if (!Array.isArray(values)) return 0;
|
|
31274
|
+
let count = 0;
|
|
31275
|
+
for (const row of values) {
|
|
31276
|
+
if (!Array.isArray(row)) continue;
|
|
31277
|
+
for (const cell of row) {
|
|
31278
|
+
if (cell !== null && String(cell).trim() !== "") count++;
|
|
31279
|
+
}
|
|
31280
|
+
}
|
|
31281
|
+
return count;
|
|
31282
|
+
}
|
|
31283
|
+
|
|
31221
31284
|
// ../gogcli-mcp/src/tools/sheets.ts
|
|
31222
31285
|
var cellValueParam = external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]);
|
|
31286
|
+
var dryRunParam = external_exports.boolean().optional().describe(
|
|
31287
|
+
"Preview the operation without modifying the sheet (gog --dry-run): reports the intended actions and exits without writing."
|
|
31288
|
+
);
|
|
31289
|
+
var failIfNotEmptyParam = external_exports.boolean().optional().describe(
|
|
31290
|
+
'Safety guard against silent overwrites: before writing, read the target range and refuse the write if any target cell already holds data. Costs one extra read. Anchor ranges (e.g. "Sheet1!A1") are expanded to the full area your values will cover; explicit and named ranges are checked as-is.'
|
|
31291
|
+
);
|
|
31223
31292
|
function registerSheetsTools(server2) {
|
|
31224
31293
|
server2.registerTool("gog_sheets_get", {
|
|
31225
31294
|
description: 'Read values from a Google Sheets range. Returns a JSON object with a "values" array of rows.',
|
|
@@ -31239,13 +31308,31 @@ function registerSheetsTools(server2) {
|
|
|
31239
31308
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID (from the URL)"),
|
|
31240
31309
|
range: external_exports.string().describe("Top-left cell or range in A1 notation, e.g. Sheet1!A1"),
|
|
31241
31310
|
values: external_exports.array(external_exports.array(cellValueParam)).describe('2D array of values (rows of columns). Cells may be string/number/boolean/null; strings starting with "=" are formulas.'),
|
|
31311
|
+
dry_run: dryRunParam,
|
|
31312
|
+
fail_if_not_empty: failIfNotEmptyParam,
|
|
31242
31313
|
account: accountParam
|
|
31243
31314
|
}
|
|
31244
|
-
}, async ({ spreadsheetId, range, values, account }) => {
|
|
31245
|
-
|
|
31246
|
-
|
|
31247
|
-
|
|
31248
|
-
|
|
31315
|
+
}, async ({ spreadsheetId, range, values, account, dry_run, fail_if_not_empty }) => {
|
|
31316
|
+
const cols = values.reduce((max, row) => Math.max(max, row.length), 0);
|
|
31317
|
+
if (fail_if_not_empty && values.length > 0 && cols > 0) {
|
|
31318
|
+
const readRange = expandAnchorRange(range, values.length, cols);
|
|
31319
|
+
let existing;
|
|
31320
|
+
try {
|
|
31321
|
+
existing = await run(["sheets", "get", spreadsheetId, readRange], { account });
|
|
31322
|
+
} catch (err) {
|
|
31323
|
+
return diagnose(err);
|
|
31324
|
+
}
|
|
31325
|
+
const occupied = countNonEmptyCells(existing);
|
|
31326
|
+
if (occupied !== 0) {
|
|
31327
|
+
const detail = occupied < 0 ? "could not be verified as empty" : `already contains data in ${occupied} cell(s)`;
|
|
31328
|
+
return toText(
|
|
31329
|
+
`Write aborted (fail_if_not_empty): target range ${readRange} ${detail}. Re-run without fail_if_not_empty to overwrite, or clear it first with gog_sheets_clear.`
|
|
31330
|
+
);
|
|
31331
|
+
}
|
|
31332
|
+
}
|
|
31333
|
+
const args = ["sheets", "update", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
|
|
31334
|
+
if (dry_run) args.push("--dry-run");
|
|
31335
|
+
return runOrDiagnose(args, { account });
|
|
31249
31336
|
});
|
|
31250
31337
|
server2.registerTool("gog_sheets_append", {
|
|
31251
31338
|
description: 'Append rows to a Google Sheet after the last row with data in the given range. Values may be strings, numbers, booleans, or null. Strings starting with "=" are interpreted as formulas.',
|
|
@@ -31254,13 +31341,13 @@ function registerSheetsTools(server2) {
|
|
|
31254
31341
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID (from the URL)"),
|
|
31255
31342
|
range: external_exports.string().describe("Range indicating which sheet/columns to append to, e.g. Sheet1!A:C"),
|
|
31256
31343
|
values: external_exports.array(external_exports.array(cellValueParam)).describe('2D array of rows to append. Cells may be string/number/boolean/null; strings starting with "=" are formulas.'),
|
|
31344
|
+
dry_run: dryRunParam,
|
|
31257
31345
|
account: accountParam
|
|
31258
31346
|
}
|
|
31259
|
-
}, async ({ spreadsheetId, range, values, account }) => {
|
|
31260
|
-
|
|
31261
|
-
|
|
31262
|
-
|
|
31263
|
-
);
|
|
31347
|
+
}, async ({ spreadsheetId, range, values, account, dry_run }) => {
|
|
31348
|
+
const args = ["sheets", "append", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`];
|
|
31349
|
+
if (dry_run) args.push("--dry-run");
|
|
31350
|
+
return runOrDiagnose(args, { account });
|
|
31264
31351
|
});
|
|
31265
31352
|
server2.registerTool("gog_sheets_clear", {
|
|
31266
31353
|
description: "Clear all values in a Google Sheets range (formatting is preserved).",
|
|
@@ -31268,13 +31355,16 @@ function registerSheetsTools(server2) {
|
|
|
31268
31355
|
inputSchema: {
|
|
31269
31356
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31270
31357
|
range: external_exports.string().describe("Range in A1 notation to clear"),
|
|
31358
|
+
dry_run: dryRunParam,
|
|
31271
31359
|
account: accountParam
|
|
31272
31360
|
}
|
|
31273
|
-
}, async ({ spreadsheetId, range, account }) => {
|
|
31274
|
-
|
|
31361
|
+
}, async ({ spreadsheetId, range, account, dry_run }) => {
|
|
31362
|
+
const args = ["sheets", "clear", spreadsheetId, range];
|
|
31363
|
+
if (dry_run) args.push("--dry-run");
|
|
31364
|
+
return runOrDiagnose(args, { account });
|
|
31275
31365
|
});
|
|
31276
31366
|
server2.registerTool("gog_sheets_metadata", {
|
|
31277
|
-
description: "Get spreadsheet metadata: title,
|
|
31367
|
+
description: "Get spreadsheet metadata: title, named ranges, and per-tab properties including grid dimensions (gridProperties.rowCount / columnCount). Use this to learn a sheet's current size before writing \u2014 a write outside the grid fails.",
|
|
31278
31368
|
annotations: { readOnlyHint: true },
|
|
31279
31369
|
inputSchema: {
|
|
31280
31370
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
@@ -31309,7 +31399,7 @@ function registerSheetsTools(server2) {
|
|
|
31309
31399
|
}
|
|
31310
31400
|
|
|
31311
31401
|
// ../gogcli-mcp/src/server.ts
|
|
31312
|
-
var VERSION = true ? "2.0
|
|
31402
|
+
var VERSION = true ? "2.3.0" : "0.0.0";
|
|
31313
31403
|
function createServer(options) {
|
|
31314
31404
|
return new McpServer({
|
|
31315
31405
|
name: options?.name ?? "gogcli",
|
|
@@ -31329,6 +31419,33 @@ function hexToRgb(hex3) {
|
|
|
31329
31419
|
blue: (n & 255) / 255
|
|
31330
31420
|
};
|
|
31331
31421
|
}
|
|
31422
|
+
async function checkDateFormatTarget(spreadsheetId, range, account) {
|
|
31423
|
+
const peek = await runOrDiagnose(
|
|
31424
|
+
["sheets", "get", spreadsheetId, range, "--render=UNFORMATTED_VALUE"],
|
|
31425
|
+
{ account }
|
|
31426
|
+
);
|
|
31427
|
+
let parsed;
|
|
31428
|
+
try {
|
|
31429
|
+
parsed = JSON.parse(peek.content[0].text);
|
|
31430
|
+
} catch {
|
|
31431
|
+
return null;
|
|
31432
|
+
}
|
|
31433
|
+
const rows = parsed.values;
|
|
31434
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
31435
|
+
let sawSmallInt = false;
|
|
31436
|
+
for (const row of rows) {
|
|
31437
|
+
for (const cell of row) {
|
|
31438
|
+
if (cell === null || cell === void 0 || cell === "") continue;
|
|
31439
|
+
if (typeof cell !== "number") return null;
|
|
31440
|
+
if (!Number.isInteger(cell)) return null;
|
|
31441
|
+
if (cell < 0) return null;
|
|
31442
|
+
if (cell >= 1e4) return null;
|
|
31443
|
+
sawSmallInt = true;
|
|
31444
|
+
}
|
|
31445
|
+
}
|
|
31446
|
+
if (!sawSmallInt) return null;
|
|
31447
|
+
return "Warning: applying DATE/DATE_TIME format to cells holding small integers (< 10000) will render them as dates near 1899-12-30 because Sheets interprets numeric values as day-serials from that epoch. If those integers are ordinals (1, 2, 3, ...) and not day offsets, this is almost certainly not what you want. Pass force:true to suppress this warning, or convert the cells to real dates / strings first.";
|
|
31448
|
+
}
|
|
31332
31449
|
function registerExtraSheetsTools(server2) {
|
|
31333
31450
|
server2.registerTool("gog_sheets_list_tabs", {
|
|
31334
31451
|
description: "List tabs (sheets) in a spreadsheet with their titles, sheetIds, and indices. A friendlier view than gog_sheets_metadata when you only need the tab list \u2014 useful for restructuring a workbook over a long agent session without losing track of names.",
|
|
@@ -31422,7 +31539,7 @@ function registerExtraSheetsTools(server2) {
|
|
|
31422
31539
|
return runOrDiagnose(args, { account });
|
|
31423
31540
|
});
|
|
31424
31541
|
server2.registerTool("gog_sheets_insert", {
|
|
31425
|
-
description: "Insert rows or columns into a sheet.",
|
|
31542
|
+
description: "Insert rows or columns into a sheet. With after:false (default), the new dimension lands at start. With after:true, the new dimension lands at start+1 (the existing dimension at start is preserved).",
|
|
31426
31543
|
annotations: { destructiveHint: true },
|
|
31427
31544
|
inputSchema: {
|
|
31428
31545
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
@@ -31430,11 +31547,12 @@ function registerExtraSheetsTools(server2) {
|
|
|
31430
31547
|
dimension: external_exports.string().describe("Dimension to insert: ROWS or COLUMNS"),
|
|
31431
31548
|
start: external_exports.number().describe("Start index (0-based)"),
|
|
31432
31549
|
count: external_exports.number().optional().describe("Number of rows/columns to insert (default: 1)"),
|
|
31433
|
-
after: external_exports.boolean().optional().describe("Insert after the start index instead of before"),
|
|
31550
|
+
after: external_exports.boolean().optional().describe("Insert after the start index instead of before. With after:true the new dimension lands at start+1, leaving the existing dimension at start untouched."),
|
|
31434
31551
|
account: accountParam
|
|
31435
31552
|
}
|
|
31436
31553
|
}, async ({ spreadsheetId, sheet, dimension, start, count, after, account }) => {
|
|
31437
|
-
const
|
|
31554
|
+
const effectiveStart = after ? start + 1 : start;
|
|
31555
|
+
const args = ["sheets", "insert", spreadsheetId, sheet, dimension, String(effectiveStart)];
|
|
31438
31556
|
if (count !== void 0) args.push(`--count=${count}`);
|
|
31439
31557
|
if (after) args.push("--after");
|
|
31440
31558
|
return runOrDiagnose(args, { account });
|
|
@@ -31553,20 +31671,29 @@ function registerExtraSheetsTools(server2) {
|
|
|
31553
31671
|
return runOrDiagnose(args, { account: a.account });
|
|
31554
31672
|
});
|
|
31555
31673
|
server2.registerTool("gog_sheets_number_format", {
|
|
31556
|
-
description: "Set number format on a range (currency, percentage, date, etc.).",
|
|
31674
|
+
description: "Set number format on a range (currency, percentage, date, etc.). When type is DATE or DATE_TIME, the target range is peeked first; if every numeric cell is a small integer (< 10000), a warning is prepended to the response because Sheets will render those as 1899/1900 day-serials. Pass force:true to skip the check.",
|
|
31557
31675
|
annotations: { destructiveHint: true },
|
|
31558
31676
|
inputSchema: {
|
|
31559
31677
|
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31560
31678
|
range: external_exports.string().describe("Range to format (e.g. Sheet1!A1:A10)"),
|
|
31561
|
-
type: external_exports.string().optional().describe("Format type: NUMBER, CURRENCY, PERCENT, DATE, TIME, SCIENTIFIC, etc."),
|
|
31679
|
+
type: external_exports.string().optional().describe("Format type: NUMBER, CURRENCY, PERCENT, DATE, DATE_TIME, TIME, SCIENTIFIC, etc."),
|
|
31562
31680
|
pattern: external_exports.string().optional().describe('Custom format pattern (e.g. "#,##0.00", "yyyy-mm-dd")'),
|
|
31681
|
+
force: external_exports.boolean().optional().describe("Skip the DATE/DATE_TIME small-integer warning check"),
|
|
31563
31682
|
account: accountParam
|
|
31564
31683
|
}
|
|
31565
|
-
}, async ({ spreadsheetId, range, type, pattern, account }) => {
|
|
31684
|
+
}, async ({ spreadsheetId, range, type, pattern, force, account }) => {
|
|
31685
|
+
const isDateType = type === "DATE" || type === "DATE_TIME";
|
|
31686
|
+
const warning = isDateType && !force ? await checkDateFormatTarget(spreadsheetId, range, account) : null;
|
|
31566
31687
|
const args = ["sheets", "number-format", spreadsheetId, range];
|
|
31567
31688
|
if (type) args.push(`--type=${type}`);
|
|
31568
31689
|
if (pattern) args.push(`--pattern=${pattern}`);
|
|
31569
|
-
|
|
31690
|
+
const result = await runOrDiagnose(args, { account });
|
|
31691
|
+
if (warning) {
|
|
31692
|
+
return { content: [{ type: "text", text: `${warning}
|
|
31693
|
+
|
|
31694
|
+
${result.content[0].text}` }] };
|
|
31695
|
+
}
|
|
31696
|
+
return result;
|
|
31570
31697
|
});
|
|
31571
31698
|
server2.registerTool("gog_sheets_read_format", {
|
|
31572
31699
|
description: "Read cell formatting for a range.",
|
|
@@ -31742,6 +31869,248 @@ function registerExtraSheetsTools(server2) {
|
|
|
31742
31869
|
{ account }
|
|
31743
31870
|
);
|
|
31744
31871
|
});
|
|
31872
|
+
server2.registerTool("gog_sheets_chart_list", {
|
|
31873
|
+
description: "List embedded charts in a spreadsheet (chartId, type, position).",
|
|
31874
|
+
annotations: { readOnlyHint: true },
|
|
31875
|
+
inputSchema: {
|
|
31876
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31877
|
+
account: accountParam
|
|
31878
|
+
}
|
|
31879
|
+
}, async ({ spreadsheetId, account }) => {
|
|
31880
|
+
return runOrDiagnose(["sheets", "chart", "list", spreadsheetId], { account });
|
|
31881
|
+
});
|
|
31882
|
+
server2.registerTool("gog_sheets_chart_get", {
|
|
31883
|
+
description: "Get the full definition (spec + position) of a single chart by its numeric chart ID.",
|
|
31884
|
+
annotations: { readOnlyHint: true },
|
|
31885
|
+
inputSchema: {
|
|
31886
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31887
|
+
chartId: external_exports.string().describe("Numeric chart ID (from gog_sheets_chart_list)"),
|
|
31888
|
+
account: accountParam
|
|
31889
|
+
}
|
|
31890
|
+
}, async ({ spreadsheetId, chartId, account }) => {
|
|
31891
|
+
return runOrDiagnose(["sheets", "chart", "get", spreadsheetId, chartId], { account });
|
|
31892
|
+
});
|
|
31893
|
+
server2.registerTool("gog_sheets_chart_create", {
|
|
31894
|
+
description: "Create an embedded chart from a JSON spec. specJson is a Sheets API ChartSpec (or full EmbeddedChart) \u2014 inline or @/path/to/file.json. Anchor the chart with sheet + anchor (A1 cell), and optionally size it with width/height pixels.",
|
|
31895
|
+
inputSchema: {
|
|
31896
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31897
|
+
specJson: external_exports.string().describe("ChartSpec or EmbeddedChart JSON (inline or @file)"),
|
|
31898
|
+
sheet: external_exports.string().optional().describe("Sheet name for the anchor (resolved to sheetId)"),
|
|
31899
|
+
anchor: external_exports.string().optional().describe("Anchor cell in A1 notation (e.g. A1, E10)"),
|
|
31900
|
+
width: external_exports.number().optional().describe("Chart width in pixels (default: 600)"),
|
|
31901
|
+
height: external_exports.number().optional().describe("Chart height in pixels (default: 371)"),
|
|
31902
|
+
account: accountParam
|
|
31903
|
+
}
|
|
31904
|
+
}, async ({ spreadsheetId, specJson, sheet, anchor, width, height, account }) => {
|
|
31905
|
+
const args = ["sheets", "chart", "create", spreadsheetId, `--spec-json=${specJson}`];
|
|
31906
|
+
if (sheet) args.push(`--sheet=${sheet}`);
|
|
31907
|
+
if (anchor) args.push(`--anchor=${anchor}`);
|
|
31908
|
+
if (width !== void 0) args.push(`--width=${width}`);
|
|
31909
|
+
if (height !== void 0) args.push(`--height=${height}`);
|
|
31910
|
+
return runOrDiagnose(args, { account });
|
|
31911
|
+
});
|
|
31912
|
+
server2.registerTool("gog_sheets_chart_update", {
|
|
31913
|
+
description: "Replace a chart spec by chart ID. specJson is a Sheets API ChartSpec (or full EmbeddedChart) \u2014 inline or @/path/to/file.json.",
|
|
31914
|
+
annotations: { destructiveHint: true },
|
|
31915
|
+
inputSchema: {
|
|
31916
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31917
|
+
chartId: external_exports.string().describe("Numeric chart ID to update"),
|
|
31918
|
+
specJson: external_exports.string().describe("ChartSpec or EmbeddedChart JSON (inline or @file)"),
|
|
31919
|
+
account: accountParam
|
|
31920
|
+
}
|
|
31921
|
+
}, async ({ spreadsheetId, chartId, specJson, account }) => {
|
|
31922
|
+
return runOrDiagnose(
|
|
31923
|
+
["sheets", "chart", "update", spreadsheetId, chartId, `--spec-json=${specJson}`],
|
|
31924
|
+
{ account }
|
|
31925
|
+
);
|
|
31926
|
+
});
|
|
31927
|
+
server2.registerTool("gog_sheets_chart_delete", {
|
|
31928
|
+
description: "Delete a chart by its numeric chart ID.",
|
|
31929
|
+
annotations: { destructiveHint: true },
|
|
31930
|
+
inputSchema: {
|
|
31931
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31932
|
+
chartId: external_exports.string().describe("Numeric chart ID to delete"),
|
|
31933
|
+
account: accountParam
|
|
31934
|
+
}
|
|
31935
|
+
}, async ({ spreadsheetId, chartId, account }) => {
|
|
31936
|
+
return runOrDiagnose(["sheets", "chart", "delete", spreadsheetId, chartId], { account });
|
|
31937
|
+
});
|
|
31938
|
+
server2.registerTool("gog_sheets_table_list", {
|
|
31939
|
+
description: "List Google Sheets tables in a spreadsheet (tableId, name, range).",
|
|
31940
|
+
annotations: { readOnlyHint: true },
|
|
31941
|
+
inputSchema: {
|
|
31942
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31943
|
+
account: accountParam
|
|
31944
|
+
}
|
|
31945
|
+
}, async ({ spreadsheetId, account }) => {
|
|
31946
|
+
return runOrDiagnose(["sheets", "table", "list", spreadsheetId], { account });
|
|
31947
|
+
});
|
|
31948
|
+
server2.registerTool("gog_sheets_table_get", {
|
|
31949
|
+
description: "Get a single Google Sheets table (definition + columns) by its table ID.",
|
|
31950
|
+
annotations: { readOnlyHint: true },
|
|
31951
|
+
inputSchema: {
|
|
31952
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31953
|
+
tableId: external_exports.string().describe("Table ID (from gog_sheets_table_list)"),
|
|
31954
|
+
account: accountParam
|
|
31955
|
+
}
|
|
31956
|
+
}, async ({ spreadsheetId, tableId, account }) => {
|
|
31957
|
+
return runOrDiagnose(["sheets", "table", "get", spreadsheetId, tableId], { account });
|
|
31958
|
+
});
|
|
31959
|
+
server2.registerTool("gog_sheets_table_create", {
|
|
31960
|
+
description: "Create a Google Sheets table over a range. columnsJson is a JSON array of column definitions (each {columnName, columnType?}); valid columnType values: TEXT, DOUBLE, BOOLEAN, DATE, DROPDOWN. Inline JSON or @/path/to/file.json.",
|
|
31961
|
+
inputSchema: {
|
|
31962
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31963
|
+
range: external_exports.string().describe("Range the table covers (e.g. Sheet1!A1:D20)"),
|
|
31964
|
+
name: external_exports.string().describe("Table name"),
|
|
31965
|
+
columnsJson: external_exports.string().describe("Column definitions as JSON array or @file (columnName + optional columnType)"),
|
|
31966
|
+
account: accountParam
|
|
31967
|
+
}
|
|
31968
|
+
}, async ({ spreadsheetId, range, name, columnsJson, account }) => {
|
|
31969
|
+
return runOrDiagnose(
|
|
31970
|
+
["sheets", "table", "create", spreadsheetId, range, `--name=${name}`, `--columns-json=${columnsJson}`],
|
|
31971
|
+
{ account }
|
|
31972
|
+
);
|
|
31973
|
+
});
|
|
31974
|
+
server2.registerTool("gog_sheets_table_append", {
|
|
31975
|
+
description: "Append data rows to a table. valuesJson is a JSON 2D array of row values.",
|
|
31976
|
+
inputSchema: {
|
|
31977
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31978
|
+
tableId: external_exports.string().describe("Table ID to append to"),
|
|
31979
|
+
valuesJson: external_exports.string().describe('Values as JSON 2D array (e.g. [["a",1],["b",2]])'),
|
|
31980
|
+
input: external_exports.enum(["RAW", "USER_ENTERED"]).optional().describe("Value input option (default: USER_ENTERED)"),
|
|
31981
|
+
account: accountParam
|
|
31982
|
+
}
|
|
31983
|
+
}, async ({ spreadsheetId, tableId, valuesJson, input, account }) => {
|
|
31984
|
+
const args = ["sheets", "table", "append", spreadsheetId, tableId, `--values-json=${valuesJson}`];
|
|
31985
|
+
if (input) args.push(`--input=${input}`);
|
|
31986
|
+
return runOrDiagnose(args, { account });
|
|
31987
|
+
});
|
|
31988
|
+
server2.registerTool("gog_sheets_table_clear", {
|
|
31989
|
+
description: "Clear all data rows from a table (keeps the table and its columns).",
|
|
31990
|
+
annotations: { destructiveHint: true },
|
|
31991
|
+
inputSchema: {
|
|
31992
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
31993
|
+
tableId: external_exports.string().describe("Table ID to clear"),
|
|
31994
|
+
account: accountParam
|
|
31995
|
+
}
|
|
31996
|
+
}, async ({ spreadsheetId, tableId, account }) => {
|
|
31997
|
+
return runOrDiagnose(["sheets", "table", "clear", spreadsheetId, tableId], { account });
|
|
31998
|
+
});
|
|
31999
|
+
server2.registerTool("gog_sheets_table_delete", {
|
|
32000
|
+
description: "Delete a table by its table ID.",
|
|
32001
|
+
annotations: { destructiveHint: true },
|
|
32002
|
+
inputSchema: {
|
|
32003
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32004
|
+
tableId: external_exports.string().describe("Table ID to delete"),
|
|
32005
|
+
account: accountParam
|
|
32006
|
+
}
|
|
32007
|
+
}, async ({ spreadsheetId, tableId, account }) => {
|
|
32008
|
+
return runOrDiagnose(["sheets", "table", "delete", spreadsheetId, tableId], { account });
|
|
32009
|
+
});
|
|
32010
|
+
server2.registerTool("gog_sheets_banding_list", {
|
|
32011
|
+
description: "List alternating-color banded ranges. Optionally scope to a single sheet.",
|
|
32012
|
+
annotations: { readOnlyHint: true },
|
|
32013
|
+
inputSchema: {
|
|
32014
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32015
|
+
sheet: external_exports.string().optional().describe("Only list banding from this sheet"),
|
|
32016
|
+
account: accountParam
|
|
32017
|
+
}
|
|
32018
|
+
}, async ({ spreadsheetId, sheet, account }) => {
|
|
32019
|
+
const args = ["sheets", "banding", "list", spreadsheetId];
|
|
32020
|
+
if (sheet) args.push(`--sheet=${sheet}`);
|
|
32021
|
+
return runOrDiagnose(args, { account });
|
|
32022
|
+
});
|
|
32023
|
+
server2.registerTool("gog_sheets_banding_set", {
|
|
32024
|
+
description: "Apply alternating colors to a range. Provide rowPropertiesJson and/or columnPropertiesJson \u2014 each a Sheets API BandingProperties JSON object ({headerColor, firstBandColor, secondBandColor, footerColor}). At least one is required.",
|
|
32025
|
+
inputSchema: {
|
|
32026
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32027
|
+
range: external_exports.string().describe("Range to band (e.g. Sheet1!A1:D20)"),
|
|
32028
|
+
rowPropertiesJson: external_exports.string().optional().describe("BandingProperties JSON for row colors"),
|
|
32029
|
+
columnPropertiesJson: external_exports.string().optional().describe("BandingProperties JSON for column colors"),
|
|
32030
|
+
account: accountParam
|
|
32031
|
+
}
|
|
32032
|
+
}, async ({ spreadsheetId, range, rowPropertiesJson, columnPropertiesJson, account }) => {
|
|
32033
|
+
const args = ["sheets", "banding", "set", spreadsheetId, range];
|
|
32034
|
+
if (rowPropertiesJson) args.push(`--row-properties-json=${rowPropertiesJson}`);
|
|
32035
|
+
if (columnPropertiesJson) args.push(`--column-properties-json=${columnPropertiesJson}`);
|
|
32036
|
+
return runOrDiagnose(args, { account });
|
|
32037
|
+
});
|
|
32038
|
+
server2.registerTool("gog_sheets_banding_clear", {
|
|
32039
|
+
description: "Remove alternating-color banding. Pass id to remove a single banded range, or all:true with sheet to remove every banding on that sheet.",
|
|
32040
|
+
annotations: { destructiveHint: true },
|
|
32041
|
+
inputSchema: {
|
|
32042
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32043
|
+
id: external_exports.number().optional().describe("Banded range ID to remove"),
|
|
32044
|
+
all: external_exports.boolean().optional().describe("Remove all banding from the sheet (requires sheet)"),
|
|
32045
|
+
sheet: external_exports.string().optional().describe("Sheet name (used with all:true)"),
|
|
32046
|
+
account: accountParam
|
|
32047
|
+
}
|
|
32048
|
+
}, async ({ spreadsheetId, id, all, sheet, account }) => {
|
|
32049
|
+
const args = ["sheets", "banding", "clear", spreadsheetId];
|
|
32050
|
+
if (id !== void 0) args.push(`--id=${id}`);
|
|
32051
|
+
if (all) args.push("--all");
|
|
32052
|
+
if (sheet) args.push(`--sheet=${sheet}`);
|
|
32053
|
+
return runOrDiagnose(args, { account });
|
|
32054
|
+
});
|
|
32055
|
+
server2.registerTool("gog_sheets_conditional_format_list", {
|
|
32056
|
+
description: "List conditional formatting rules. Optionally scope to a single sheet.",
|
|
32057
|
+
annotations: { readOnlyHint: true },
|
|
32058
|
+
inputSchema: {
|
|
32059
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32060
|
+
sheet: external_exports.string().optional().describe("Only list rules from this sheet"),
|
|
32061
|
+
account: accountParam
|
|
32062
|
+
}
|
|
32063
|
+
}, async ({ spreadsheetId, sheet, account }) => {
|
|
32064
|
+
const args = ["sheets", "conditional-format", "list", spreadsheetId];
|
|
32065
|
+
if (sheet) args.push(`--sheet=${sheet}`);
|
|
32066
|
+
return runOrDiagnose(args, { account });
|
|
32067
|
+
});
|
|
32068
|
+
server2.registerTool("gog_sheets_conditional_format_add", {
|
|
32069
|
+
description: "Add a conditional formatting rule to a range. type picks the condition; expr is its value/formula (omit for blank/not-blank). formatJson is the CellFormat to apply when the condition matches (inline or @file). Use formatFields to force-send zero/false fields (e.g. backgroundColor,textFormat.bold).",
|
|
32070
|
+
inputSchema: {
|
|
32071
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32072
|
+
range: external_exports.string().describe("Range the rule applies to (e.g. Sheet1!A1:A100)"),
|
|
32073
|
+
type: external_exports.enum([
|
|
32074
|
+
"text-eq",
|
|
32075
|
+
"text-contains",
|
|
32076
|
+
"text-starts-with",
|
|
32077
|
+
"text-ends-with",
|
|
32078
|
+
"number-eq",
|
|
32079
|
+
"number-gt",
|
|
32080
|
+
"number-gte",
|
|
32081
|
+
"number-lt",
|
|
32082
|
+
"number-lte",
|
|
32083
|
+
"blank",
|
|
32084
|
+
"not-blank",
|
|
32085
|
+
"custom-formula"
|
|
32086
|
+
]).describe("Rule type"),
|
|
32087
|
+
formatJson: external_exports.string().describe("CellFormat JSON to apply when the condition matches (inline or @file)"),
|
|
32088
|
+
expr: external_exports.string().optional().describe("Expression value or custom formula (omit for blank/not-blank)"),
|
|
32089
|
+
formatFields: external_exports.string().optional().describe("Format field mask for force-sending zero/false fields (e.g. backgroundColor,textFormat.bold)"),
|
|
32090
|
+
account: accountParam
|
|
32091
|
+
}
|
|
32092
|
+
}, async ({ spreadsheetId, range, type, formatJson, expr, formatFields, account }) => {
|
|
32093
|
+
const args = ["sheets", "conditional-format", "add", spreadsheetId, range, `--type=${type}`, `--format-json=${formatJson}`];
|
|
32094
|
+
if (expr !== void 0) args.push(`--expr=${expr}`);
|
|
32095
|
+
if (formatFields) args.push(`--format-fields=${formatFields}`);
|
|
32096
|
+
return runOrDiagnose(args, { account });
|
|
32097
|
+
});
|
|
32098
|
+
server2.registerTool("gog_sheets_conditional_format_clear", {
|
|
32099
|
+
description: "Remove conditional formatting rules from a sheet. Pass index to remove a single rule by its 0-based index, or all:true to remove every rule on the sheet.",
|
|
32100
|
+
annotations: { destructiveHint: true },
|
|
32101
|
+
inputSchema: {
|
|
32102
|
+
spreadsheetId: external_exports.string().describe("Spreadsheet ID"),
|
|
32103
|
+
sheet: external_exports.string().describe("Sheet name to clear rules from"),
|
|
32104
|
+
index: external_exports.number().optional().describe("0-based rule index to remove"),
|
|
32105
|
+
all: external_exports.boolean().optional().describe("Remove all conditional formatting rules from the sheet"),
|
|
32106
|
+
account: accountParam
|
|
32107
|
+
}
|
|
32108
|
+
}, async ({ spreadsheetId, sheet, index, all, account }) => {
|
|
32109
|
+
const args = ["sheets", "conditional-format", "clear", spreadsheetId, `--sheet=${sheet}`];
|
|
32110
|
+
if (index !== void 0) args.push(`--index=${index}`);
|
|
32111
|
+
if (all) args.push("--all");
|
|
32112
|
+
return runOrDiagnose(args, { account });
|
|
32113
|
+
});
|
|
31745
32114
|
}
|
|
31746
32115
|
|
|
31747
32116
|
// src/index.ts
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp-sheets",
|
|
5
5
|
"display_name": "gogcli (Sheets)",
|
|
6
|
-
"version": "2.0
|
|
6
|
+
"version": "2.3.0",
|
|
7
7
|
"description": "Extended Google Sheets for Claude via gogcli — auth + full Sheets support",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
"name": "gog_sheets_number_format",
|
|
153
|
-
"description": "Set number format on a range (currency, percentage, date, etc.)."
|
|
153
|
+
"description": "Set number format on a range (currency, percentage, date, etc.). When type is DATE or DATE_TIME, peeks the target range first and warns if every numeric cell is a small integer (< 10000) — Sheets would render those as 1899/1900 day-serials. Pass force:true to skip the check."
|
|
154
154
|
},
|
|
155
155
|
{
|
|
156
156
|
"name": "gog_sheets_read_format",
|
|
@@ -207,6 +207,74 @@
|
|
|
207
207
|
{
|
|
208
208
|
"name": "gog_sheets_reorder_tab",
|
|
209
209
|
"description": "Move a tab to a specific 0-based position."
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"name": "gog_sheets_chart_list",
|
|
213
|
+
"description": "List embedded charts in a spreadsheet."
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"name": "gog_sheets_chart_get",
|
|
217
|
+
"description": "Get a chart's full definition (spec + position) by chart ID."
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "gog_sheets_chart_create",
|
|
221
|
+
"description": "Create an embedded chart from a JSON ChartSpec."
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"name": "gog_sheets_chart_update",
|
|
225
|
+
"description": "Replace a chart's spec by chart ID."
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
"name": "gog_sheets_chart_delete",
|
|
229
|
+
"description": "Delete a chart by chart ID."
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"name": "gog_sheets_table_list",
|
|
233
|
+
"description": "List Google Sheets tables in a spreadsheet."
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"name": "gog_sheets_table_get",
|
|
237
|
+
"description": "Get a Google Sheets table by table ID."
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"name": "gog_sheets_table_create",
|
|
241
|
+
"description": "Create a Google Sheets table over a range with typed columns."
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
"name": "gog_sheets_table_append",
|
|
245
|
+
"description": "Append data rows to a table."
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": "gog_sheets_table_clear",
|
|
249
|
+
"description": "Clear all data rows from a table."
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
"name": "gog_sheets_table_delete",
|
|
253
|
+
"description": "Delete a table by table ID."
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
"name": "gog_sheets_banding_list",
|
|
257
|
+
"description": "List alternating-color banded ranges."
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"name": "gog_sheets_banding_set",
|
|
261
|
+
"description": "Apply alternating colors to a range."
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"name": "gog_sheets_banding_clear",
|
|
265
|
+
"description": "Remove alternating-color banding by ID or for a whole sheet."
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
"name": "gog_sheets_conditional_format_list",
|
|
269
|
+
"description": "List conditional formatting rules."
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"name": "gog_sheets_conditional_format_add",
|
|
273
|
+
"description": "Add a conditional formatting rule to a range."
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"name": "gog_sheets_conditional_format_clear",
|
|
277
|
+
"description": "Remove conditional formatting rules from a sheet."
|
|
210
278
|
}
|
|
211
279
|
],
|
|
212
280
|
"compatibility": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gogcli-mcp-sheets",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"mcpName": "io.github.chrischall/gogcli-mcp-sheets",
|
|
5
5
|
"description": "Extended Google Sheets MCP server via gogcli — all base tools plus full Sheets support",
|
|
6
6
|
"author": "Claude Code (AI) <https://www.anthropic.com/claude>",
|