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/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 (overrides GOG_ACCOUNT env var)"
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
- const base = toError(err);
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
- return runOrDiagnose(
31246
- ["sheets", "update", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`],
31247
- { account }
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
- return runOrDiagnose(
31261
- ["sheets", "append", spreadsheetId, range, `--values-json=${JSON.stringify(values)}`],
31262
- { account }
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
- return runOrDiagnose(["sheets", "clear", spreadsheetId, range], { account });
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, sheet tabs, named ranges, and other properties.",
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.12" : "0.0.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 args = ["sheets", "insert", spreadsheetId, sheet, dimension, String(start)];
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
- return runOrDiagnose(args, { account });
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.12",
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.12",
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>",