apple-numbers-mcp 1.1.19 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -675,6 +675,7 @@ All configuration is optional — the server works out of the box.
675
675
  | `APPLE_NUMBERS_MCP_MAX_BUFFER` | 50 MB (Python reader) / 64 MB (AppleScript) | Max bytes captured from a subprocess's stdout, applied to both the Python reader and the AppleScript layer. Raise it if a very large spreadsheet is truncated; lower it to cap memory. |
676
676
  | `APPLE_NUMBERS_MCP_CONFIG_FILE` | `~/Library/Application Support/apple-numbers-mcp/config.json` | Path to the JSON config file (see below). |
677
677
  | `APPLE_NUMBERS_MCP_NO_AUTO_SETUP` | unset (auto-setup on) | Set to a truthy value (`1`, `true`) to disable the one-time automatic creation of the Python venv. When set, the server will not run `setup.sh` on its own — you must provide `numbers-parser` yourself (`pip3 install numbers-parser` or `pnpm run setup`). |
678
+ | `APPLE_NUMBERS_MCP_EXTRA_ROOTS` | unset | Colon-separated **absolute** directories to add to the allowed-path roots, e.g. `/Data/Finance:/srv/shared`. The built-in roots already cover your home directory, the temp dirs and `/Volumes` (where external and network mounts appear), so this is for genuinely unusual layouts rather than routine configuration. Entries are canonicalized like the built-ins, so a symlinked entry cannot smuggle in a wider parent; relative and empty entries are ignored. **Widening a security boundary — set it deliberately.** |
678
679
  | `APPLE_NUMBERS_MCP_SETUP_TIMEOUT` | `300000` (5 minutes) | Timeout in milliseconds for the automatic venv bootstrap (`setup.sh`). Raise it on a slow network where the `numbers-parser` pip install would otherwise time out. |
679
680
 
680
681
  > **Per-call timeouts are not configurable.** Every `numbers-parser` sidecar call
@@ -775,9 +776,10 @@ These are tracked for future releases. The underlying `numbers-parser` library h
775
776
  - Ensure the file extension is `.numbers`.
776
777
 
777
778
  ### "Output path … is outside the allowed roots" / "Input path … is outside the allowed roots"
778
- - `export-table`, `create-spreadsheet` and `import-csv` read and write ordinary files, so their paths are restricted to your **home directory**, `/tmp`, `/private/tmp`, and `/Volumes` — the server will not write into `/etc`, `/Library`, an app bundle, or any other system location, and will not read a source file from one.
779
+ - Every tool that touches a file on disk is bounded to your **home directory**, `/tmp`, `/private/tmp`, and `/Volumes` — the server will not write into `/etc`, `/Library`, an app bundle, or any other system location, and will not read from one.
780
+ - **As of 1.2.0 this covers the `.numbers` file itself**, not just export/import destinations. `get-file-info`, `read-table`, `search` and the in-place writes (`set-cell`, `add-rows`, `set-formula`, …) previously accepted a path anywhere on disk.
779
781
  - Symlinks are resolved **before** the check, so a link inside an allowed directory that points outside it is rejected too; the error reports the resolved path, which is usually the quickest way to see where a path really went.
780
- - Fix: choose a path under one of those roots (`~/Documents/report.csv`, `/tmp/report.csv`). There is no override setting.
782
+ - Fix: choose a path under one of those roots (`~/Documents/report.numbers`, `/tmp/report.csv`). If your spreadsheets genuinely live somewhere else, add that directory to `APPLE_NUMBERS_MCP_EXTRA_ROOTS` — see [Environment variables](#environment-variables).
781
783
 
782
784
  ### Formatting / formula tools fail with "Numbers.app not running" or "Not authorized to send Apple events to Numbers"
783
785
  - Open Numbers.app at least once. macOS will prompt for automation permission — accept it.
package/build/index.js CHANGED
@@ -21813,8 +21813,7 @@ function unmergeCells(filePath, sheet, table, startRow, startCol, endRow, endCol
21813
21813
 
21814
21814
  // src/services/numbersManager.ts
21815
21815
  import { existsSync as existsSync3 } from "node:fs";
21816
- import { resolve as resolve2, extname } from "node:path";
21817
- import { homedir as homedir2 } from "node:os";
21816
+ import { extname } from "node:path";
21818
21817
 
21819
21818
  // src/utils/exportPath.ts
21820
21819
  import { lstatSync, readlinkSync, realpathSync } from "node:fs";
@@ -21833,12 +21832,18 @@ var ALLOWED_EXPORT_ROOTS = [
21833
21832
  "/Volumes"
21834
21833
  ];
21835
21834
  var ALLOWED_EXPORT_ROOTS_TEXT = "your home directory, /tmp, /private/tmp, or /Volumes";
21835
+ var EXTRA_ROOTS_ENV = "APPLE_NUMBERS_MCP_EXTRA_ROOTS";
21836
+ function extraRoots() {
21837
+ const raw = process.env[EXTRA_ROOTS_ENV];
21838
+ if (!raw) return [];
21839
+ return raw.split(":").map((s) => s.trim()).filter((s) => s.length > 0 && isAbsolute(s));
21840
+ }
21836
21841
  function canonicalize(path) {
21837
21842
  return realpathSync.native(path);
21838
21843
  }
21839
21844
  function allowedRoots() {
21840
21845
  const roots = [];
21841
- for (const root of ALLOWED_EXPORT_ROOTS) {
21846
+ for (const root of [...ALLOWED_EXPORT_ROOTS, ...extraRoots()]) {
21842
21847
  if (!roots.includes(root)) roots.push(root);
21843
21848
  try {
21844
21849
  const canonical = canonicalize(root);
@@ -21912,9 +21917,32 @@ function resolveWithinAllowedRoots(path, label) {
21912
21917
 
21913
21918
  // src/services/numbersManager.ts
21914
21919
  var NumbersManager = class {
21920
+ /**
21921
+ * Resolve and validate the path of an EXISTING `.numbers` file.
21922
+ *
21923
+ * This is the resolver behind `get-file-info`, `read-table`, `search` and
21924
+ * every in-place write (`set-cell`, `add-rows`, `set-formula`, …). It used to
21925
+ * expand `~`, `resolve()` and check the extension — and nothing else — so
21926
+ * those tools could read and modify a `.numbers` file **anywhere on disk**,
21927
+ * while its sibling `validateOutputPath` had enforced the allowed-roots
21928
+ * boundary since 1.1.19. That asymmetry was the largest remaining gap here.
21929
+ *
21930
+ * It now goes through the same `resolveWithinAllowedRoots` as the write side,
21931
+ * which also brings the hardening that came with it: canonicalization via
21932
+ * `realpathSync.native` (so a case-respelled segment cannot defeat the check
21933
+ * on case-insensitive APFS) and symlink resolution BEFORE the comparison (so
21934
+ * a link inside an allowed directory cannot point out of it).
21935
+ *
21936
+ * The extension is checked on the CANONICAL path for the same reason it is on
21937
+ * the output side: a symlink must not be able to present a `.numbers` name
21938
+ * for a target that is something else entirely.
21939
+ *
21940
+ * The built-in roots cover home, the temp dirs and `/Volumes`, so a file the
21941
+ * user could plausibly want to open is almost always already inside them.
21942
+ * `APPLE_NUMBERS_MCP_EXTRA_ROOTS` exists for the layouts they are not.
21943
+ */
21915
21944
  validatePath(filePath) {
21916
- const expanded = filePath.startsWith("~") ? filePath.replace(/^~/, homedir2()) : filePath;
21917
- const resolved = resolve2(expanded);
21945
+ const resolved = resolveWithinAllowedRoots(filePath, "Input path");
21918
21946
  if (!existsSync3(resolved)) {
21919
21947
  throw new Error(`File not found: ${resolved}`);
21920
21948
  }
@@ -22444,11 +22472,11 @@ First use get-file-info and read-table to read the relevant table(s) so you know
22444
22472
  // src/services/fileConfig.ts
22445
22473
  import { existsSync as existsSync5, readFileSync as readFileSync2 } from "node:fs";
22446
22474
  import { join as join3 } from "node:path";
22447
- import { homedir as homedir3 } from "node:os";
22475
+ import { homedir as homedir2 } from "node:os";
22448
22476
  function fileConfigPath(env = process.env) {
22449
22477
  const override = env.APPLE_NUMBERS_MCP_CONFIG_FILE;
22450
22478
  if (override && override.trim()) return override.trim();
22451
- return join3(homedir3(), "Library", "Application Support", "apple-numbers-mcp", "config.json");
22479
+ return join3(homedir2(), "Library", "Application Support", "apple-numbers-mcp", "config.json");
22452
22480
  }
22453
22481
  function loadFileConfig(env = process.env, path = fileConfigPath(env)) {
22454
22482
  const applied = [];
@@ -22595,6 +22623,8 @@ var MAX_NAME_LEN = 1024;
22595
22623
  var MAX_QUERY_LEN = 1e4;
22596
22624
  var MAX_HEADER_LEN = 1024;
22597
22625
  var headerCellSchema = external_exports.string().max(MAX_HEADER_LEN);
22626
+ var PATH_BOUNDARY_NOTE = `
22627
+ Paths: the file must resolve \u2014 after ~ expansion and symlink resolution \u2014 to a location under ${ALLOWED_EXPORT_ROOTS_TEXT}; anything else is rejected with an error naming those roots. Add directories with the ${EXTRA_ROOTS_ENV} environment variable.`;
22598
22628
  var server = new McpServer({
22599
22629
  name: "apple-numbers",
22600
22630
  version: version2,
@@ -22649,7 +22679,7 @@ registerTool(
22649
22679
  registerTool(
22650
22680
  "get-file-info",
22651
22681
  {
22652
- description: "Use when: you need to discover the structure of a .numbers file before reading or writing \u2014 the exact sheet and table names, their dimensions, and header rows. Call this first when sheet/table names are unknown.\nReturns: each sheet with its tables, per-table row\xD7col counts, and the header row (row 0) cells.\nDo not use when: you want the actual cell data \u2014 use read-table; or a single cell \u2014 use get-cell.",
22682
+ description: "Use when: you need to discover the structure of a .numbers file before reading or writing \u2014 the exact sheet and table names, their dimensions, and header rows. Call this first when sheet/table names are unknown.\nReturns: each sheet with its tables, per-table row\xD7col counts, and the header row (row 0) cells.\nDo not use when: you want the actual cell data \u2014 use read-table; or a single cell \u2014 use get-cell." + PATH_BOUNDARY_NOTE,
22653
22683
  inputSchema: {
22654
22684
  path: external_exports.string().describe("Absolute or ~-relative path to the .numbers file")
22655
22685
  },
@@ -22693,7 +22723,7 @@ ${summary}`,
22693
22723
  registerTool(
22694
22724
  "read-table",
22695
22725
  {
22696
- description: "Use when: you want the data from a table \u2014 headers plus rows, optionally limited to a row range (0-based inclusive indices; header is row 0, so data starts at row 1) and/or a subset of columns. Defaults to the first sheet and first table if sheet/table are omitted.\nReturns: sheet name, table name, the headers and rows selected, and numRows/numCols describing the SELECTION \u2014 not the table. Use get-file-info for the table's full dimensions.\nDo not use when: you only need one cell \u2014 use get-cell; you need the file's structure/names \u2014 use get-file-info; or you want to find a value's location \u2014 use search.",
22726
+ description: "Use when: you want the data from a table \u2014 headers plus rows, optionally limited to a row range (0-based inclusive indices; header is row 0, so data starts at row 1) and/or a subset of columns. Defaults to the first sheet and first table if sheet/table are omitted.\nReturns: sheet name, table name, the headers and rows selected, and numRows/numCols describing the SELECTION \u2014 not the table. Use get-file-info for the table's full dimensions.\nDo not use when: you only need one cell \u2014 use get-cell; you need the file's structure/names \u2014 use get-file-info; or you want to find a value's location \u2014 use search." + PATH_BOUNDARY_NOTE,
22697
22727
  inputSchema: {
22698
22728
  path: external_exports.string().describe("Path to the .numbers file"),
22699
22729
  sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
@@ -22729,7 +22759,7 @@ ${dataRows}`,
22729
22759
  registerTool(
22730
22760
  "search",
22731
22761
  {
22732
- description: "Use when: you need to locate where a text value appears across every cell in a .numbers file (case-insensitive partial match), optionally limited to one sheet.\nReturns: a match count and each match's sheet, table, row, column header, and value.\nDo not use when: you already know the cell's coordinates \u2014 use get-cell; or you want a whole table's contents \u2014 use read-table.",
22762
+ description: "Use when: you need to locate where a text value appears across every cell in a .numbers file (case-insensitive partial match), optionally limited to one sheet.\nReturns: a match count and each match's sheet, table, row, column header, and value.\nDo not use when: you already know the cell's coordinates \u2014 use get-cell; or you want a whole table's contents \u2014 use read-table." + PATH_BOUNDARY_NOTE,
22733
22763
  inputSchema: {
22734
22764
  path: external_exports.string().describe("Path to the .numbers file"),
22735
22765
  query: external_exports.string().max(MAX_QUERY_LEN).describe("Text to search for (case-insensitive)"),
@@ -22795,7 +22825,7 @@ registerTool(
22795
22825
  registerTool(
22796
22826
  "get-cell",
22797
22827
  {
22798
- description: "Use when: you need a single cell's value by 0-based row and column index (header is row 0). Set verbose=true to also get the formula, formatted value, and merge info.\nReturns: the cell's value and type, plus formula/formatted-value/merge details when verbose.\nDo not use when: you want many cells or whole rows \u2014 use read-table; or you don't know the coordinates \u2014 use search.",
22828
+ description: "Use when: you need a single cell's value by 0-based row and column index (header is row 0). Set verbose=true to also get the formula, formatted value, and merge info.\nReturns: the cell's value and type, plus formula/formatted-value/merge details when verbose.\nDo not use when: you want many cells or whole rows \u2014 use read-table; or you don't know the coordinates \u2014 use search." + PATH_BOUNDARY_NOTE,
22799
22829
  inputSchema: {
22800
22830
  path: external_exports.string().describe("Path to the .numbers file"),
22801
22831
  sheet: external_exports.string().describe("Sheet name"),
@@ -22864,7 +22894,7 @@ registerTool(
22864
22894
  description: `Use when: you need to write a computed value to one cell in an existing .numbers file, addressed by 0-based row and column (header is row 0). Defaults to the first sheet/table if omitted.
22865
22895
  Returns: the written coordinates, value, and the sheet/table affected.
22866
22896
  Do not use when: you're writing many cells \u2014 use set-cells-batch; replacing whole rows \u2014 use update-rows; or writing a live formula \u2014 use set-formula (passing "=SUM(...)" here writes literal text, not a formula).
22867
- Safety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES any existing data in the target cell.`,
22897
+ Safety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES any existing data in the target cell.` + PATH_BOUNDARY_NOTE,
22868
22898
  inputSchema: {
22869
22899
  path: external_exports.string().describe("Path to the .numbers file"),
22870
22900
  row: external_exports.number().int().min(0).max(MAX_INDEX).describe("Row index (0-based)"),
@@ -22896,7 +22926,7 @@ Safety: modifies the .numbers file in place via the numbers-parser sidecar (does
22896
22926
  registerTool(
22897
22927
  "set-cells-batch",
22898
22928
  {
22899
- description: "Use when: you need to write computed values to multiple cells at once (each with 0-based row/col); more efficient than many set-cell calls. Defaults to the first sheet/table if omitted.\nReturns: the number of cells written and the sheet/table affected.\nDo not use when: replacing whole rows by index \u2014 use update-rows; appending new rows \u2014 use add-rows; or writing live formulas \u2014 use set-formulas-batch.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES any existing data in the targeted cells.",
22929
+ description: "Use when: you need to write computed values to multiple cells at once (each with 0-based row/col); more efficient than many set-cell calls. Defaults to the first sheet/table if omitted.\nReturns: the number of cells written and the sheet/table affected.\nDo not use when: replacing whole rows by index \u2014 use update-rows; appending new rows \u2014 use add-rows; or writing live formulas \u2014 use set-formulas-batch.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES any existing data in the targeted cells." + PATH_BOUNDARY_NOTE,
22900
22930
  inputSchema: {
22901
22931
  path: external_exports.string().describe("Path to the .numbers file"),
22902
22932
  updates: external_exports.array(
@@ -22930,7 +22960,7 @@ registerTool(
22930
22960
  registerTool(
22931
22961
  "add-rows",
22932
22962
  {
22933
- description: "Use when: you want to append new rows of data to the end of an existing table; rows are added after the last existing row. Defaults to the first sheet/table if omitted.\nReturns: the number of rows added, the starting row index, and the new total row count.\nDo not use when: overwriting existing rows by index \u2014 use update-rows; setting individual cells \u2014 use set-cell / set-cells-batch.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app). Additive (appends rows) and does not overwrite existing data, but still mutates the file.",
22963
+ description: "Use when: you want to append new rows of data to the end of an existing table; rows are added after the last existing row. Defaults to the first sheet/table if omitted.\nReturns: the number of rows added, the starting row index, and the new total row count.\nDo not use when: overwriting existing rows by index \u2014 use update-rows; setting individual cells \u2014 use set-cell / set-cells-batch.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app). Additive (appends rows) and does not overwrite existing data, but still mutates the file." + PATH_BOUNDARY_NOTE,
22934
22964
  inputSchema: {
22935
22965
  path: external_exports.string().describe("Path to the .numbers file"),
22936
22966
  rows: external_exports.array(external_exports.array(external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]))).min(1).max(MAX_BATCH).describe(
@@ -22959,7 +22989,7 @@ registerTool(
22959
22989
  registerTool(
22960
22990
  "delete-rows",
22961
22991
  {
22962
- description: "Use when: you need to permanently remove a contiguous range of rows from a table; both startRow and endRow are 0-based inclusive indices (header is row 0). Defaults to the first sheet/table if omitted.\nReturns: the number of rows deleted and the new total row count.\nDo not use when: you only want to clear values while keeping the rows \u2014 use set-cells-batch with empty values; or overwrite rows in place \u2014 use update-rows.\nSafety: DESTRUCTIVE \u2014 requires explicit user confirmation; not undoable; the .numbers file is modified in place via the numbers-parser sidecar (does not require Numbers.app) and the deleted rows cannot be recovered. Double-check the 0-based inclusive range before calling.",
22992
+ description: "Use when: you need to permanently remove a contiguous range of rows from a table; both startRow and endRow are 0-based inclusive indices (header is row 0). Defaults to the first sheet/table if omitted.\nReturns: the number of rows deleted and the new total row count.\nDo not use when: you only want to clear values while keeping the rows \u2014 use set-cells-batch with empty values; or overwrite rows in place \u2014 use update-rows.\nSafety: DESTRUCTIVE \u2014 requires explicit user confirmation; not undoable; the .numbers file is modified in place via the numbers-parser sidecar (does not require Numbers.app) and the deleted rows cannot be recovered. Double-check the 0-based inclusive range before calling." + PATH_BOUNDARY_NOTE,
22963
22993
  inputSchema: {
22964
22994
  path: external_exports.string().describe("Path to the .numbers file"),
22965
22995
  startRow: external_exports.number().int().min(0).max(MAX_INDEX).describe("First row to delete (0-based, inclusive)"),
@@ -22986,7 +23016,7 @@ registerTool(
22986
23016
  registerTool(
22987
23017
  "add-sheet",
22988
23018
  {
22989
- description: "Use when: you want to add a new sheet to an existing .numbers file, optionally naming its default table and providing its headers. Geometry depends on headers: with headers the table is created 1 row x headers.length columns; without them it is a 12 x 8 grid of empty cells (not overridable through MCP).\nReturns: the new sheet name, its default table name, and the table's dimensions.\nDo not use when: adding a table to an existing sheet \u2014 use add-table; or creating a whole new file \u2014 use create-spreadsheet.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app). Additive (adds a new sheet) and does not overwrite existing data, but still mutates the file.",
23019
+ description: "Use when: you want to add a new sheet to an existing .numbers file, optionally naming its default table and providing its headers. Geometry depends on headers: with headers the table is created 1 row x headers.length columns; without them it is a 12 x 8 grid of empty cells (not overridable through MCP).\nReturns: the new sheet name, its default table name, and the table's dimensions.\nDo not use when: adding a table to an existing sheet \u2014 use add-table; or creating a whole new file \u2014 use create-spreadsheet.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app). Additive (adds a new sheet) and does not overwrite existing data, but still mutates the file." + PATH_BOUNDARY_NOTE,
22990
23020
  inputSchema: {
22991
23021
  path: external_exports.string().describe("Path to the .numbers file"),
22992
23022
  sheetName: external_exports.string().max(MAX_NAME_LEN).describe("Name for the new sheet"),
@@ -23012,7 +23042,7 @@ registerTool(
23012
23042
  registerTool(
23013
23043
  "add-table",
23014
23044
  {
23015
- description: "Use when: you want to add a new table to an existing sheet, optionally naming it and providing its headers. Defaults to the first sheet if omitted. Geometry depends on headers: with headers the table is created 1 row x headers.length columns; without them it is a 12 x 8 grid of empty cells (not overridable through MCP).\nReturns: the new table name, the sheet it was added to, and the table's dimensions.\nDo not use when: adding a whole new sheet \u2014 use add-sheet; or creating a new file \u2014 use create-spreadsheet.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app). Additive (adds a new table) and does not overwrite existing data, but still mutates the file.",
23045
+ description: "Use when: you want to add a new table to an existing sheet, optionally naming it and providing its headers. Defaults to the first sheet if omitted. Geometry depends on headers: with headers the table is created 1 row x headers.length columns; without them it is a 12 x 8 grid of empty cells (not overridable through MCP).\nReturns: the new table name, the sheet it was added to, and the table's dimensions.\nDo not use when: adding a whole new sheet \u2014 use add-sheet; or creating a new file \u2014 use create-spreadsheet.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app). Additive (adds a new table) and does not overwrite existing data, but still mutates the file." + PATH_BOUNDARY_NOTE,
23016
23046
  inputSchema: {
23017
23047
  path: external_exports.string().describe("Path to the .numbers file"),
23018
23048
  sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
@@ -23070,7 +23100,7 @@ Sheet: "${result.sheetName}" | Table: "${result.tableName}"`,
23070
23100
  registerTool(
23071
23101
  "update-rows",
23072
23102
  {
23073
- description: "Use when: you need to replace whole rows by 0-based index in an existing file; each update carries a row index and a complete array of column values. Carrying multiple {row, values} entries is more efficient than per-row writes. Defaults to the first sheet/table if omitted.\nReturns: the number of rows updated and the sheet/table affected.\nDo not use when: appending new rows \u2014 use add-rows; writing individual cells \u2014 use set-cell / set-cells-batch; or writing live formulas \u2014 use set-formulas-batch.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES the entire contents of each targeted row.",
23103
+ description: "Use when: you need to replace whole rows by 0-based index in an existing file; each update carries a row index and a complete array of column values. Carrying multiple {row, values} entries is more efficient than per-row writes. Defaults to the first sheet/table if omitted.\nReturns: the number of rows updated and the sheet/table affected.\nDo not use when: appending new rows \u2014 use add-rows; writing individual cells \u2014 use set-cell / set-cells-batch; or writing live formulas \u2014 use set-formulas-batch.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES the entire contents of each targeted row." + PATH_BOUNDARY_NOTE,
23074
23104
  inputSchema: {
23075
23105
  path: external_exports.string().describe("Path to the .numbers file"),
23076
23106
  updates: external_exports.array(
@@ -23102,7 +23132,7 @@ registerTool(
23102
23132
  registerTool(
23103
23133
  "rename-sheet",
23104
23134
  {
23105
- description: "Use when: you want to change a sheet's name in a .numbers file. Defaults to the first sheet if the current name is omitted.\nReturns: the old and new sheet names.\nDo not use when: renaming a table \u2014 use rename-table.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app).",
23135
+ description: "Use when: you want to change a sheet's name in a .numbers file. Defaults to the first sheet if the current name is omitted.\nReturns: the old and new sheet names.\nDo not use when: renaming a table \u2014 use rename-table.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app)." + PATH_BOUNDARY_NOTE,
23106
23136
  inputSchema: {
23107
23137
  path: external_exports.string().describe("Path to the .numbers file"),
23108
23138
  newName: external_exports.string().max(MAX_NAME_LEN).describe("New name for the sheet"),
@@ -23125,7 +23155,7 @@ registerTool(
23125
23155
  registerTool(
23126
23156
  "rename-table",
23127
23157
  {
23128
- description: "Use when: you want to change a table's name in a .numbers file. Defaults to the first sheet/table if omitted.\nReturns: the old and new table names and the sheet it belongs to.\nDo not use when: renaming a sheet \u2014 use rename-sheet.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app).",
23158
+ description: "Use when: you want to change a table's name in a .numbers file. Defaults to the first sheet/table if omitted.\nReturns: the old and new table names and the sheet it belongs to.\nDo not use when: renaming a sheet \u2014 use rename-sheet.\nSafety: modifies the .numbers file in place via the numbers-parser sidecar (does not require Numbers.app)." + PATH_BOUNDARY_NOTE,
23129
23159
  inputSchema: {
23130
23160
  path: external_exports.string().describe("Path to the .numbers file"),
23131
23161
  newName: external_exports.string().max(MAX_NAME_LEN).describe("New name for the table"),
@@ -23150,7 +23180,7 @@ registerTool(
23150
23180
  registerTool(
23151
23181
  "set-formula",
23152
23182
  {
23153
- description: 'Use when: you need to write a live formula (e.g. "=SUM(A2:A10)") into a cell so it computes in Numbers, addressed by 0-based row/col. Requires explicit sheet and table.\nReturns: the cell, the formula set, and its computed value.\nDo not use when: writing a plain computed value \u2014 use set-cell (this tool is for live formulas); or setting many formulas \u2014 use set-formulas-batch.\nSafety: drives Numbers.app via AppleScript and requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. The file is opened in Numbers.app if not already open, launching the app; the formula OVERWRITES any existing content in the target cell; the WHOLE document is saved, which commits any unsaved edits the user has open in it; and the document is left open afterwards.',
23183
+ description: 'Use when: you need to write a live formula (e.g. "=SUM(A2:A10)") into a cell so it computes in Numbers, addressed by 0-based row/col. Requires explicit sheet and table.\nReturns: the cell, the formula set, and its computed value.\nDo not use when: writing a plain computed value \u2014 use set-cell (this tool is for live formulas); or setting many formulas \u2014 use set-formulas-batch.\nSafety: drives Numbers.app via AppleScript and requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. The file is opened in Numbers.app if not already open, launching the app; the formula OVERWRITES any existing content in the target cell; the WHOLE document is saved, which commits any unsaved edits the user has open in it; and the document is left open afterwards.' + PATH_BOUNDARY_NOTE,
23154
23184
  inputSchema: {
23155
23185
  path: external_exports.string().describe("Path to the .numbers file"),
23156
23186
  sheet: external_exports.string().describe("Sheet name"),
@@ -23181,7 +23211,7 @@ Computed value: ${result.computedValue}`,
23181
23211
  registerTool(
23182
23212
  "set-formulas-batch",
23183
23213
  {
23184
- description: "Use when: you need to write live formulas to multiple cells at once (each with 0-based row/col); more efficient than many set-formula calls. Requires explicit sheet and table.\nReturns: the number of formulas set and the sheet/table affected.\nDo not use when: writing plain computed values \u2014 use set-cells-batch; or setting a single formula \u2014 use set-formula.\nSafety: drives Numbers.app via AppleScript and requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; OVERWRITES any existing content in the targeted cells; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards.",
23214
+ description: "Use when: you need to write live formulas to multiple cells at once (each with 0-based row/col); more efficient than many set-formula calls. Requires explicit sheet and table.\nReturns: the number of formulas set and the sheet/table affected.\nDo not use when: writing plain computed values \u2014 use set-cells-batch; or setting a single formula \u2014 use set-formula.\nSafety: drives Numbers.app via AppleScript and requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; OVERWRITES any existing content in the targeted cells; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards." + PATH_BOUNDARY_NOTE,
23185
23215
  inputSchema: {
23186
23216
  path: external_exports.string().describe("Path to the .numbers file"),
23187
23217
  sheet: external_exports.string().describe("Sheet name"),
@@ -23239,7 +23269,7 @@ var cellStyleSchema = external_exports.object({
23239
23269
  registerTool(
23240
23270
  "set-cell-style",
23241
23271
  {
23242
- description: "Use when: you need to format one cell \u2014 font, text/background color, number format, alignment, text wrap \u2014 addressed by 0-based row/col. Requires explicit sheet and table.\nReturns: the styled cell and the sheet/table affected.\nDo not use when: styling many cells \u2014 use set-cells-style-batch; or writing a value/formula rather than formatting \u2014 use set-cell / set-formula.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards.",
23272
+ description: "Use when: you need to format one cell \u2014 font, text/background color, number format, alignment, text wrap \u2014 addressed by 0-based row/col. Requires explicit sheet and table.\nReturns: the styled cell and the sheet/table affected.\nDo not use when: styling many cells \u2014 use set-cells-style-batch; or writing a value/formula rather than formatting \u2014 use set-cell / set-formula.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards." + PATH_BOUNDARY_NOTE,
23243
23273
  inputSchema: {
23244
23274
  path: external_exports.string().describe("Path to the .numbers file"),
23245
23275
  sheet: external_exports.string().describe("Sheet name"),
@@ -23266,7 +23296,7 @@ registerTool(
23266
23296
  registerTool(
23267
23297
  "set-cells-style-batch",
23268
23298
  {
23269
- description: "Use when: you need to format multiple cells at once (each with 0-based row/col); more efficient than many set-cell-style calls. Requires explicit sheet and table.\nReturns: the number of cells styled and the sheet/table affected.\nDo not use when: styling a single cell \u2014 use set-cell-style; or writing values/formulas rather than formatting \u2014 use set-cells-batch / set-formulas-batch.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards.",
23299
+ description: "Use when: you need to format multiple cells at once (each with 0-based row/col); more efficient than many set-cell-style calls. Requires explicit sheet and table.\nReturns: the number of cells styled and the sheet/table affected.\nDo not use when: styling a single cell \u2014 use set-cell-style; or writing values/formulas rather than formatting \u2014 use set-cells-batch / set-formulas-batch.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards." + PATH_BOUNDARY_NOTE,
23270
23300
  inputSchema: {
23271
23301
  path: external_exports.string().describe("Path to the .numbers file"),
23272
23302
  sheet: external_exports.string().describe("Sheet name"),
@@ -23297,7 +23327,7 @@ registerTool(
23297
23327
  registerTool(
23298
23328
  "set-column-width",
23299
23329
  {
23300
- description: "Use when: you need to set a column's width in pixels, addressed by 0-based column index. Requires explicit sheet and table.\nReturns: the column index and the width applied.\nDo not use when: setting a row's height \u2014 use set-row-height.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards.",
23330
+ description: "Use when: you need to set a column's width in pixels, addressed by 0-based column index. Requires explicit sheet and table.\nReturns: the column index and the width applied.\nDo not use when: setting a row's height \u2014 use set-row-height.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards." + PATH_BOUNDARY_NOTE,
23301
23331
  inputSchema: {
23302
23332
  path: external_exports.string().describe("Path to the .numbers file"),
23303
23333
  sheet: external_exports.string().describe("Sheet name"),
@@ -23327,7 +23357,7 @@ registerTool(
23327
23357
  registerTool(
23328
23358
  "set-row-height",
23329
23359
  {
23330
- description: "Use when: you need to set a row's height in pixels, addressed by 0-based row index. Requires explicit sheet and table.\nReturns: the row index and the height applied.\nDo not use when: setting a column's width \u2014 use set-column-width.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards.",
23360
+ description: "Use when: you need to set a row's height in pixels, addressed by 0-based row index. Requires explicit sheet and table.\nReturns: the row index and the height applied.\nDo not use when: setting a column's width \u2014 use set-column-width.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards." + PATH_BOUNDARY_NOTE,
23331
23361
  inputSchema: {
23332
23362
  path: external_exports.string().describe("Path to the .numbers file"),
23333
23363
  sheet: external_exports.string().describe("Sheet name"),
@@ -23357,7 +23387,7 @@ registerTool(
23357
23387
  registerTool(
23358
23388
  "merge-cells",
23359
23389
  {
23360
- description: "Use when: you need to merge a rectangular range of cells into one, given 0-based inclusive top-left and bottom-right coordinates. Requires explicit sheet and table.\nReturns: the merged range and the sheet/table affected.\nDo not use when: undoing a merge \u2014 use unmerge-cells.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards. Merging keeps only the top-left cell's content and can DROP the values in the other cells of the range.",
23390
+ description: "Use when: you need to merge a rectangular range of cells into one, given 0-based inclusive top-left and bottom-right coordinates. Requires explicit sheet and table.\nReturns: the merged range and the sheet/table affected.\nDo not use when: undoing a merge \u2014 use unmerge-cells.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards. Merging keeps only the top-left cell's content and can DROP the values in the other cells of the range." + PATH_BOUNDARY_NOTE,
23361
23391
  inputSchema: {
23362
23392
  path: external_exports.string().describe("Path to the .numbers file"),
23363
23393
  sheet: external_exports.string().describe("Sheet name"),
@@ -23384,7 +23414,7 @@ registerTool(
23384
23414
  registerTool(
23385
23415
  "unmerge-cells",
23386
23416
  {
23387
- description: "Use when: you need to split a previously merged range back into individual cells, given 0-based inclusive top-left and bottom-right coordinates. Requires explicit sheet and table.\nReturns: the unmerged range and the sheet/table affected.\nDo not use when: creating a merge \u2014 use merge-cells.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards. Previously merged cells stay empty (the original non-top-left values are not restored).",
23417
+ description: "Use when: you need to split a previously merged range back into individual cells, given 0-based inclusive top-left and bottom-right coordinates. Requires explicit sheet and table.\nReturns: the unmerged range and the sheet/table affected.\nDo not use when: creating a merge \u2014 use merge-cells.\nSafety: drives Numbers.app via AppleScript \u2014 requires Numbers.app INSTALLED (it need not already be running) plus Automation permission. Opens the file in Numbers.app if it isn't open already, launching the app; modifies it in place; saves the WHOLE document, which commits any unsaved edits the user has open in it; and leaves the document open afterwards. Previously merged cells stay empty (the original non-top-left values are not restored)." + PATH_BOUNDARY_NOTE,
23388
23418
  inputSchema: {
23389
23419
  path: external_exports.string().describe("Path to the .numbers file"),
23390
23420
  sheet: external_exports.string().describe("Sheet name"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-numbers-mcp",
3
- "version": "1.1.19",
3
+ "version": "1.2.1",
4
4
  "description": "MCP server for Apple Numbers - read, write, search, and format .numbers spreadsheets via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",