apple-numbers-mcp 1.1.10 → 1.1.12
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 +53 -12
- package/build/index.js +52 -32
- package/docs/AUTOMATION-PERMISSION.md +55 -44
- package/docs/LIMITATIONS.md +121 -40
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -77,9 +77,24 @@ numbers-parser`); see [Requirements](#requirements).
|
|
|
77
77
|
|
|
78
78
|
### Other Hosts (Hermes, Antigravity)
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Two more hosts can run the same `apple-numbers` MCP server (`npx -y apple-numbers-mcp`). All paths still need the `numbers-parser` Python sidecar available; see [Requirements](#requirements).
|
|
81
81
|
|
|
82
|
-
- **[Hermes Agent](https://hermes-agent.nousresearch.com/)** (NousResearch) — Hermes has no plugin/marketplace drop-in
|
|
82
|
+
- **[Hermes Agent](https://hermes-agent.nousresearch.com/)** (NousResearch) — Hermes has no plugin/marketplace drop-in, so there is nothing in this repo to install from. Register the server with the CLI:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
hermes mcp add apple-numbers --command npx --args -y apple-numbers-mcp
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or add it to `~/.hermes/config.yaml` by hand:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
mcp_servers:
|
|
92
|
+
apple-numbers:
|
|
93
|
+
command: npx
|
|
94
|
+
args: ["-y", "apple-numbers-mcp"]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Restart your Hermes session afterward so the tools load.
|
|
83
98
|
- **[Antigravity](https://antigravity.google/)** (Google) — add the server entry from [`.antigravity-plugin/mcp_config.json`](https://github.com/sweetrb/apple-numbers-mcp/blob/main/.antigravity-plugin/mcp_config.json) to `~/.gemini/config/mcp_config.json` (or via Antigravity's MCP settings).
|
|
84
99
|
|
|
85
100
|
### Manual Installation
|
|
@@ -138,10 +153,10 @@ The entrypoint is written as:
|
|
|
138
153
|
|
|
139
154
|
## Requirements
|
|
140
155
|
|
|
141
|
-
- **macOS** — the npm package is macOS-only (it declares `os: ["darwin"]`). Reads go through `numbers-parser` directly (no Numbers.app needed); **formatting and formula tools additionally require Numbers.app.**
|
|
156
|
+
- **macOS** — the npm package is macOS-only (it declares `os: ["darwin"]`). Reads **and value/structure writes** go through `numbers-parser` directly (no Numbers.app needed); **formatting and formula tools additionally require Numbers.app.**
|
|
142
157
|
- **Node.js 20+** — Required for the MCP server
|
|
143
158
|
- **Python 3.11+** — the `numbers-parser` library installs automatically into a project-local venv on first use (or pre-warm with `pnpm run setup`). numbers-parser requires Python ≥ 3.10; macOS ships 3.9, so install a newer Python first (e.g. `brew install python@3.12`).
|
|
144
|
-
- **Automation permission (
|
|
159
|
+
- **Automation permission (formulas & formatting only)** — Reads, exports and ordinary value/structure writes (`set-cell(s)`, `add`/`update`/`delete-rows`, `add-sheet`/`add-table`, `rename-*`, `create-spreadsheet`, `import-csv`) need no special permission and no Numbers.app. Only the eight formula/format tools — `set-formula(s)`, `set-cell(s)-style`, `set-column-width`/`set-row-height`, `merge-cells`/`unmerge-cells` — drive Numbers.app via AppleScript and require the host app to have Automation permission for Numbers, granted on first use. See the [Automation Permission guide](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/AUTOMATION-PERMISSION.md). Run the **`doctor`** tool to verify your setup.
|
|
145
160
|
|
|
146
161
|
## Features
|
|
147
162
|
|
|
@@ -218,11 +233,11 @@ Verify Python 3 and `numbers-parser` are installed and reachable from the server
|
|
|
218
233
|
|
|
219
234
|
#### `doctor`
|
|
220
235
|
|
|
221
|
-
Run a full setup diagnostic with four separate checks: `python_interpreter` (the resolved Python's path and version — warns when it's older than 3.11, the most common setup failure since stock macOS ships 3.9), `numbers_parser` (the read sidecar — required for all reads/exports), `numbers_app` (Numbers.app present — required for
|
|
236
|
+
Run a full setup diagnostic with four separate checks: `python_interpreter` (the resolved Python's path and version — warns when it's older than 3.11, the most common setup failure since stock macOS ships 3.9), `numbers_parser` (the read sidecar — required for all reads/exports), `numbers_app` (Numbers.app present — required for the formula/format tools only), and `automation_permission` (an informational reminder that those tools need Automation permission for Numbers.app). Each is reported as ok / warn / fail with actionable advice. This is the richer counterpart to `health-check`; reach for it first when a tool returns a permission or setup error.
|
|
222
237
|
|
|
223
238
|
**Parameters:** None
|
|
224
239
|
|
|
225
|
-
**Returns:** A per-check report. The `structuredContent` carries the raw `{ healthy, checks[] }`, where each check has `name`, `status` (`ok`/`warn`/`fail`), and `detail`.
|
|
240
|
+
**Returns:** A per-check report. The `structuredContent` carries the raw `{ healthy, checks[] }`, where each check has `name`, `status` (`ok`/`warn`/`fail`), and `detail`. Only the formula/format tools need Automation permission — see [docs/AUTOMATION-PERMISSION.md](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/AUTOMATION-PERMISSION.md).
|
|
226
241
|
|
|
227
242
|
---
|
|
228
243
|
|
|
@@ -251,6 +266,8 @@ Read data from a table. Returns headers and rows. Defaults to the first sheet an
|
|
|
251
266
|
| `endRow` | number | No | 0-based end row, inclusive (default: last row) |
|
|
252
267
|
| `columns` | (string \| number)[] | No | Column filter: header names or 0-based indices |
|
|
253
268
|
|
|
269
|
+
**Returns:** Sheet name, table name, the headers and rows selected, and `numRows` / `numCols` describing **the selection** — not the table. When you pass `startRow`/`endRow`/`columns`, those counts describe what came back; use [`get-file-info`](#get-file-info) for the table's real dimensions.
|
|
270
|
+
|
|
254
271
|
---
|
|
255
272
|
|
|
256
273
|
#### `get-cell`
|
|
@@ -286,6 +303,8 @@ Case-insensitive partial match across every cell in a `.numbers` file.
|
|
|
286
303
|
|
|
287
304
|
Export a table to CSV, TSV, or JSON.
|
|
288
305
|
|
|
306
|
+
**⚠️ Safety:** Writes a file to disk at `outputPath`. The path is written unconditionally — any existing file there is **overwritten** — so confirm the destination before calling.
|
|
307
|
+
|
|
289
308
|
| Parameter | Type | Required | Description |
|
|
290
309
|
|-----------|------|----------|-------------|
|
|
291
310
|
| `path` | string | Yes | Path to the `.numbers` file |
|
|
@@ -404,6 +423,8 @@ Add a new sheet, optionally with headers for the default table.
|
|
|
404
423
|
| `tableName` | string | No | Name for the default table |
|
|
405
424
|
| `headers` | string[] | No | Headers for the default table |
|
|
406
425
|
|
|
426
|
+
**Table geometry:** pass `headers` and the new table is created **1 row × `headers.length` columns**; omit them and you get a **12 × 8** grid of empty cells. There is no way to override the size through MCP — delete the extra rows with `delete-rows` if you don't want them. The response reports the dimensions that were created.
|
|
427
|
+
|
|
407
428
|
---
|
|
408
429
|
|
|
409
430
|
#### `add-table`
|
|
@@ -417,6 +438,8 @@ Add a new table to an existing sheet.
|
|
|
417
438
|
| `tableName` | string | No | Name for the new table |
|
|
418
439
|
| `headers` | string[] | No | Column headers |
|
|
419
440
|
|
|
441
|
+
**Table geometry:** same rule as [`add-sheet`](#add-sheet) — with `headers` the table is **1 × `headers.length`**, without them **12 × 8**.
|
|
442
|
+
|
|
420
443
|
---
|
|
421
444
|
|
|
422
445
|
#### `rename-sheet` / `rename-table`
|
|
@@ -468,7 +491,7 @@ Write multiple formulas in a single operation.
|
|
|
468
491
|
| `path` | string | Yes | Path to the `.numbers` file |
|
|
469
492
|
| `sheet` | string | Yes | Sheet name |
|
|
470
493
|
| `table` | string | Yes | Table name |
|
|
471
|
-
| `
|
|
494
|
+
| `formulas` | array | Yes | Array of `{row, col, formula}` objects |
|
|
472
495
|
|
|
473
496
|
---
|
|
474
497
|
|
|
@@ -560,6 +583,12 @@ Import a CSV, TSV, or JSON file into a new `.numbers` spreadsheet. Auto-detects
|
|
|
560
583
|
| `sheetName` | string | No | Default: `"Sheet 1"` |
|
|
561
584
|
| `tableName` | string | No | Default: `"Table 1"` |
|
|
562
585
|
|
|
586
|
+
**CSV/TSV fields are auto-typed.** Every field from a `csv`/`tsv` source is converted before it is written: `""` → empty cell, `true`/`false` → boolean, and anything Python's `int()`/`float()` parses → number. So a zip code `01234` becomes the number `1234`, `007` becomes `7`, and `1e5` becomes the number `100000`. This is deliberately less conservative than `set-cell`/`add-rows`, which keep leading-zero strings intact, and there is no per-column type control on import. For zero-padded identifiers, import from JSON instead (JSON values are passed through untouched) or repair the column afterwards with `set-cells-batch` using `type: "string"`.
|
|
587
|
+
|
|
588
|
+
**JSON column sets come from the first object.** For an array of objects, the columns are the keys of `data[0]`; a key that appears only in a later object is dropped and objects missing an early key get blank cells — normalize the keys first. For an array of arrays, columns are named `Column_0`, `Column_1`, … and the first row is kept as data.
|
|
589
|
+
|
|
590
|
+
**`format: "auto"` falls back to CSV.** Any extension that isn't `.csv`, `.tsv` or `.json` is parsed as comma-separated rather than erroring, so pass `format` explicitly for unusual extensions. The format actually used is echoed back in the result.
|
|
591
|
+
|
|
563
592
|
---
|
|
564
593
|
|
|
565
594
|
## Usage Patterns
|
|
@@ -584,7 +613,7 @@ AI: [calls search with query='Acme Corp']
|
|
|
584
613
|
|
|
585
614
|
```
|
|
586
615
|
User: "Set B5 to =SUM(B2:B4) and bold the header row"
|
|
587
|
-
AI: [calls set-formula] [calls set-cells-style-batch with
|
|
616
|
+
AI: [calls set-formula] [calls set-cells-style-batch with style {fontName: "Helvetica-Bold"} on row 0]
|
|
588
617
|
"Done — B5 is =SUM(B2:B4) and the header row is bold."
|
|
589
618
|
```
|
|
590
619
|
|
|
@@ -648,6 +677,15 @@ All configuration is optional — the server works out of the box.
|
|
|
648
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`). |
|
|
649
678
|
| `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. |
|
|
650
679
|
|
|
680
|
+
> **Per-call timeouts are not configurable.** Every `numbers-parser` sidecar call
|
|
681
|
+
> runs under a fixed **30-second** timeout and every AppleScript call under a fixed
|
|
682
|
+
> **60-second** one; there is no environment variable for either.
|
|
683
|
+
> `APPLE_NUMBERS_MCP_SETUP_TIMEOUT` above governs only the one-time venv bootstrap,
|
|
684
|
+
> and `APPLE_NUMBERS_MCP_MAX_BUFFER` caps stdout size, not wall-clock time. If you
|
|
685
|
+
> hit `Operation timed out after 30000ms`, narrow the request — use `read-table`'s
|
|
686
|
+
> `startRow`/`endRow` and `columns`, split large batches — rather than looking for
|
|
687
|
+
> a knob.
|
|
688
|
+
|
|
651
689
|
### Configuration file (when the host strips `env`)
|
|
652
690
|
|
|
653
691
|
Some host apps (e.g. Claude Desktop) launch the MCP server with a scrubbed
|
|
@@ -686,19 +724,22 @@ This is the same Python-sidecar pattern used by [apple-photos-mcp](https://githu
|
|
|
686
724
|
- **Local only** — All operations happen on the local machine. No data is sent to external servers.
|
|
687
725
|
- **No credential storage** — The server doesn't store any passwords or authentication tokens.
|
|
688
726
|
- **File-system access** — Tools take explicit paths; the server only reads or writes files you name.
|
|
689
|
-
- **Numbers.app automation** — Formatting and formula tools drive Numbers.app via AppleScript. macOS will prompt for automation permission on first use.
|
|
727
|
+
- **Numbers.app automation** — Formatting and formula tools drive Numbers.app via AppleScript. macOS will prompt for automation permission on first use. These tools **open the file in Numbers.app** (launching it if it isn't running), **save the whole document** — including any unsaved edits you have open in it — and **leave it open** afterwards. Everything else, including all value and structure writes, runs entirely in the Python sidecar and never touches the app.
|
|
690
728
|
|
|
691
729
|
---
|
|
692
730
|
|
|
693
731
|
## Known Limitations
|
|
694
732
|
|
|
695
|
-
For the full rundown — the
|
|
696
|
-
|
|
733
|
+
For the full rundown — the values-vs-formatting backend split, AppleScript-only
|
|
734
|
+
formulas/styles, `import-csv` auto-typing, fixed per-call timeouts, indexing,
|
|
735
|
+
dates, format lag, and concurrent edits — see **[docs/LIMITATIONS.md](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/LIMITATIONS.md)**.
|
|
697
736
|
The summary below is the quick version.
|
|
698
737
|
|
|
699
738
|
| Limitation | Reason |
|
|
700
739
|
|------------|--------|
|
|
701
|
-
| Formatting / formulas / dimensions need Numbers.app | `numbers-parser` doesn't write styles or formulas; the AppleScript layer fills that gap (macOS only) |
|
|
740
|
+
| Formatting / formulas / dimensions need Numbers.app | `numbers-parser` doesn't write styles or formulas; the AppleScript layer fills that gap (macOS only). Value and structure writes do **not** need it |
|
|
741
|
+
| `import-csv` auto-types CSV/TSV fields | Leading zeros are lost (`01234` → `1234`); JSON input is passed through untouched |
|
|
742
|
+
| Per-call timeouts are fixed | 30 s for sidecar calls, 60 s for AppleScript; neither is configurable — narrow the request instead |
|
|
702
743
|
| No conditional formatting | Not exposed by `numbers-parser` |
|
|
703
744
|
| No charts or images | Not exposed by `numbers-parser` |
|
|
704
745
|
| Sheet deletion not supported | Not exposed by `numbers-parser` |
|
package/build/index.js
CHANGED
|
@@ -3104,9 +3104,9 @@ var require_data = __commonJS({
|
|
|
3104
3104
|
}
|
|
3105
3105
|
});
|
|
3106
3106
|
|
|
3107
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3107
|
+
// node_modules/.pnpm/fast-uri@3.1.5/node_modules/fast-uri/lib/utils.js
|
|
3108
3108
|
var require_utils = __commonJS({
|
|
3109
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3109
|
+
"node_modules/.pnpm/fast-uri@3.1.5/node_modules/fast-uri/lib/utils.js"(exports, module) {
|
|
3110
3110
|
"use strict";
|
|
3111
3111
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3112
3112
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
@@ -3417,9 +3417,9 @@ var require_utils = __commonJS({
|
|
|
3417
3417
|
}
|
|
3418
3418
|
});
|
|
3419
3419
|
|
|
3420
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3420
|
+
// node_modules/.pnpm/fast-uri@3.1.5/node_modules/fast-uri/lib/schemes.js
|
|
3421
3421
|
var require_schemes = __commonJS({
|
|
3422
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3422
|
+
"node_modules/.pnpm/fast-uri@3.1.5/node_modules/fast-uri/lib/schemes.js"(exports, module) {
|
|
3423
3423
|
"use strict";
|
|
3424
3424
|
var { isUUID } = require_utils();
|
|
3425
3425
|
var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
|
|
@@ -3627,9 +3627,9 @@ var require_schemes = __commonJS({
|
|
|
3627
3627
|
}
|
|
3628
3628
|
});
|
|
3629
3629
|
|
|
3630
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3630
|
+
// node_modules/.pnpm/fast-uri@3.1.5/node_modules/fast-uri/index.js
|
|
3631
3631
|
var require_fast_uri = __commonJS({
|
|
3632
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3632
|
+
"node_modules/.pnpm/fast-uri@3.1.5/node_modules/fast-uri/index.js"(exports, module) {
|
|
3633
3633
|
"use strict";
|
|
3634
3634
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3635
3635
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
@@ -3645,7 +3645,12 @@ var require_fast_uri = __commonJS({
|
|
|
3645
3645
|
}
|
|
3646
3646
|
function resolve2(baseURI, relativeURI, options) {
|
|
3647
3647
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3648
|
-
const
|
|
3648
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3649
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3650
|
+
if (baseMalformed || relativeMalformed) {
|
|
3651
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3652
|
+
}
|
|
3653
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3649
3654
|
schemelessOptions.skipEscape = true;
|
|
3650
3655
|
return serialize(resolved, schemelessOptions);
|
|
3651
3656
|
}
|
|
@@ -3771,6 +3776,7 @@ var require_fast_uri = __commonJS({
|
|
|
3771
3776
|
}
|
|
3772
3777
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3773
3778
|
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3779
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3774
3780
|
function getParseError(parsed, matches) {
|
|
3775
3781
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3776
3782
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3805,6 +3811,20 @@ var require_fast_uri = __commonJS({
|
|
|
3805
3811
|
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3806
3812
|
malformedAuthorityOrPort = true;
|
|
3807
3813
|
}
|
|
3814
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3815
|
+
if (introducerMatch !== null) {
|
|
3816
|
+
const region = introducerMatch[1];
|
|
3817
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3818
|
+
if (normalizedRegion.length >= 2) {
|
|
3819
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3820
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3821
|
+
malformedAuthorityOrPort = true;
|
|
3822
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3823
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3824
|
+
malformedAuthorityOrPort = true;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3808
3828
|
const matches = uri.match(URI_PARSE);
|
|
3809
3829
|
if (matches) {
|
|
3810
3830
|
parsed.scheme = matches[1];
|
|
@@ -21427,7 +21447,7 @@ function findSystemPython() {
|
|
|
21427
21447
|
}
|
|
21428
21448
|
}
|
|
21429
21449
|
throw new Error(
|
|
21430
|
-
"Python 3 not found on PATH. Install Python >= 3.11 (stock macOS ships 3.9 - brew install python@3.12), or run scripts/setup.sh from a repo checkout.
|
|
21450
|
+
"Python 3 not found on PATH. Install Python >= 3.11 (stock macOS ships 3.9 - brew install python@3.12), or run scripts/setup.sh from a repo checkout. Run the doctor tool to diagnose, and see https://github.com/sweetrb/apple-numbers-mcp#troubleshooting"
|
|
21431
21451
|
);
|
|
21432
21452
|
}
|
|
21433
21453
|
function resolvePython() {
|
|
@@ -22144,27 +22164,27 @@ function runDoctor(_manager) {
|
|
|
22144
22164
|
checks.push({
|
|
22145
22165
|
name: "numbers_app",
|
|
22146
22166
|
status: "ok",
|
|
22147
|
-
detail: `Numbers.app present \u2014
|
|
22167
|
+
detail: `Numbers.app present \u2014 formula and formatting tools available (${found})`
|
|
22148
22168
|
});
|
|
22149
22169
|
} else {
|
|
22150
22170
|
checks.push({
|
|
22151
22171
|
name: "numbers_app",
|
|
22152
22172
|
status: "warn",
|
|
22153
|
-
detail: "Numbers.app not found. Reads
|
|
22173
|
+
detail: "Numbers.app not found. Reads, exports and value/structure writes via numbers-parser still work; only the formula and formatting tools (set-formula(s), set-cell(s)-style, set-column-width/set-row-height, merge-cells/unmerge-cells) need Numbers.app installed."
|
|
22154
22174
|
});
|
|
22155
22175
|
}
|
|
22156
22176
|
} catch (e) {
|
|
22157
22177
|
checks.push({
|
|
22158
22178
|
name: "numbers_app",
|
|
22159
22179
|
status: "warn",
|
|
22160
|
-
detail: `could not verify Numbers.app: ${String(e)}. Reads
|
|
22180
|
+
detail: `could not verify Numbers.app: ${String(e)}. Reads, exports and value/structure writes still work; only the formula/formatting tools need Numbers.app.`
|
|
22161
22181
|
});
|
|
22162
22182
|
}
|
|
22163
22183
|
try {
|
|
22164
22184
|
checks.push({
|
|
22165
22185
|
name: "automation_permission",
|
|
22166
22186
|
status: "ok",
|
|
22167
|
-
detail: "AppleScript
|
|
22187
|
+
detail: "The AppleScript formula/formatting tools require Automation permission for Numbers.app, granted on first use (System Settings > Privacy & Security > Automation). Reads and value/structure writes do not."
|
|
22168
22188
|
});
|
|
22169
22189
|
} catch (e) {
|
|
22170
22190
|
checks.push({
|
|
@@ -22314,7 +22334,7 @@ var headerCellSchema = external_exports.string().max(MAX_HEADER_LEN);
|
|
|
22314
22334
|
var server = new McpServer({
|
|
22315
22335
|
name: "apple-numbers",
|
|
22316
22336
|
version: version2,
|
|
22317
|
-
description: "MCP server for reading and writing Apple Numbers (.numbers) spreadsheet files.
|
|
22337
|
+
description: "MCP server for reading and writing Apple Numbers (.numbers) spreadsheet files. Inspect file structure, read/search/export table data and individual cells, create spreadsheets, write cells and add/update/delete rows, manage sheets and tables, import CSV/TSV/JSON \u2014 all offline via the numbers-parser sidecar \u2014 and, via Numbers.app, write live formulas, cell styling, column/row dimensions, and merges."
|
|
22318
22338
|
});
|
|
22319
22339
|
server.registerTool(
|
|
22320
22340
|
"health-check",
|
|
@@ -22336,7 +22356,7 @@ server.registerTool(
|
|
|
22336
22356
|
server.registerTool(
|
|
22337
22357
|
"doctor",
|
|
22338
22358
|
{
|
|
22339
|
-
description: "Use when: a tool returns a permission or setup error, or you want the full setup diagnostic before writing/formatting.\nReturns: four checks \u2014 the resolved Python interpreter (path + version), numbers-parser (read sidecar), Numbers.app (needed for
|
|
22359
|
+
description: "Use when: a tool returns a permission or setup error, or you want the full setup diagnostic before writing/formatting.\nReturns: four checks \u2014 the resolved Python interpreter (path + version), numbers-parser (the read/write sidecar), Numbers.app (needed only for the formula/formatting tools), and Automation permission \u2014 each as ok/warn/fail with actionable advice.\nDo not use when: you only need the lightweight read-sidecar version check \u2014 use health-check instead.",
|
|
22340
22360
|
inputSchema: {},
|
|
22341
22361
|
outputSchema: {
|
|
22342
22362
|
healthy: external_exports.boolean().optional(),
|
|
@@ -22401,7 +22421,7 @@ ${summary}`,
|
|
|
22401
22421
|
server.registerTool(
|
|
22402
22422
|
"read-table",
|
|
22403
22423
|
{
|
|
22404
|
-
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,
|
|
22424
|
+
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.",
|
|
22405
22425
|
inputSchema: {
|
|
22406
22426
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22407
22427
|
sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
|
|
@@ -22476,7 +22496,7 @@ ${lines.join("\n")}`, {
|
|
|
22476
22496
|
server.registerTool(
|
|
22477
22497
|
"export-table",
|
|
22478
22498
|
{
|
|
22479
|
-
description: "Use when: you want to write a table's data out to a CSV, TSV, or JSON file on disk. Defaults to the first sheet and first table if sheet/table are omitted.\nReturns: the exported row count, format, and output path.\nDo not use when: you want the data inline in the response rather than a file \u2014 use read-table; or you want to build a new .numbers file from a CSV/TSV/JSON source \u2014 use import-csv.",
|
|
22499
|
+
description: "Use when: you want to write a table's data out to a CSV, TSV, or JSON file on disk. Defaults to the first sheet and first table if sheet/table are omitted.\nReturns: the exported row count, format, and output path.\nDo not use when: you want the data inline in the response rather than a file \u2014 use read-table; or you want to build a new .numbers file from a CSV/TSV/JSON source \u2014 use import-csv.\nSafety: writes a file to disk at outputPath. The path is written unconditionally \u2014 any existing file there is OVERWRITTEN \u2014 so confirm the destination before calling.",
|
|
22480
22500
|
inputSchema: {
|
|
22481
22501
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22482
22502
|
format: external_exports.enum(["csv", "tsv", "json"]).describe("Output format"),
|
|
@@ -22572,7 +22592,7 @@ server.registerTool(
|
|
|
22572
22592
|
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.
|
|
22573
22593
|
Returns: the written coordinates, value, and the sheet/table affected.
|
|
22574
22594
|
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).
|
|
22575
|
-
Safety: modifies the .numbers file in place and OVERWRITES any existing data in the target cell.`,
|
|
22595
|
+
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.`,
|
|
22576
22596
|
inputSchema: {
|
|
22577
22597
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22578
22598
|
row: external_exports.number().int().min(0).max(MAX_INDEX).describe("Row index (0-based)"),
|
|
@@ -22604,7 +22624,7 @@ Safety: modifies the .numbers file in place and OVERWRITES any existing data in
|
|
|
22604
22624
|
server.registerTool(
|
|
22605
22625
|
"set-cells-batch",
|
|
22606
22626
|
{
|
|
22607
|
-
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 and OVERWRITES any existing data in the targeted cells.",
|
|
22627
|
+
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.",
|
|
22608
22628
|
inputSchema: {
|
|
22609
22629
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22610
22630
|
updates: external_exports.array(
|
|
@@ -22638,7 +22658,7 @@ server.registerTool(
|
|
|
22638
22658
|
server.registerTool(
|
|
22639
22659
|
"add-rows",
|
|
22640
22660
|
{
|
|
22641
|
-
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. Additive (appends rows) and does not overwrite existing data, but still mutates the file.",
|
|
22661
|
+
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.",
|
|
22642
22662
|
inputSchema: {
|
|
22643
22663
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22644
22664
|
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(
|
|
@@ -22667,7 +22687,7 @@ server.registerTool(
|
|
|
22667
22687
|
server.registerTool(
|
|
22668
22688
|
"delete-rows",
|
|
22669
22689
|
{
|
|
22670
|
-
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 and the deleted rows cannot be recovered. Double-check the 0-based inclusive range before calling.",
|
|
22690
|
+
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.",
|
|
22671
22691
|
inputSchema: {
|
|
22672
22692
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22673
22693
|
startRow: external_exports.number().int().min(0).max(MAX_INDEX).describe("First row to delete (0-based, inclusive)"),
|
|
@@ -22694,7 +22714,7 @@ server.registerTool(
|
|
|
22694
22714
|
server.registerTool(
|
|
22695
22715
|
"add-sheet",
|
|
22696
22716
|
{
|
|
22697
|
-
description: "Use when: you want to add a new sheet to an existing .numbers file, optionally naming its default table and providing its headers.\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. Additive (adds a new sheet) and does not overwrite existing data, but still mutates the file.",
|
|
22717
|
+
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.",
|
|
22698
22718
|
inputSchema: {
|
|
22699
22719
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22700
22720
|
sheetName: external_exports.string().max(MAX_NAME_LEN).describe("Name for the new sheet"),
|
|
@@ -22720,7 +22740,7 @@ server.registerTool(
|
|
|
22720
22740
|
server.registerTool(
|
|
22721
22741
|
"add-table",
|
|
22722
22742
|
{
|
|
22723
|
-
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.\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. Additive (adds a new table) and does not overwrite existing data, but still mutates the file.",
|
|
22743
|
+
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.",
|
|
22724
22744
|
inputSchema: {
|
|
22725
22745
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22726
22746
|
sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
|
|
@@ -22746,7 +22766,7 @@ server.registerTool(
|
|
|
22746
22766
|
server.registerTool(
|
|
22747
22767
|
"import-csv",
|
|
22748
22768
|
{
|
|
22749
|
-
description:
|
|
22769
|
+
description: 'Use when: you want to convert an existing CSV, TSV, or JSON file into a new .numbers spreadsheet. Format is auto-detected from the input file extension unless you specify it explicitly; any unrecognized extension falls back to CSV. CSV/TSV fields are auto-typed (numbers, booleans, empty cells), so zero-padded identifiers LOSE their leading zeros \u2014 "01234" becomes 1234. JSON values are passed through untouched, but an array of objects takes its column set from the FIRST object only, silently dropping keys that appear later.\nReturns: the imported row and column counts, detected format, and the input/output paths plus sheet/table names.\nDo not use when: building a file from headers/rows you already have in hand \u2014 use create-spreadsheet; or exporting a .numbers table out to CSV/TSV/JSON \u2014 use export-table.\nSafety: writes the output .numbers file via the numbers-parser sidecar (does not require Numbers.app). The output path is written unconditionally \u2014 if a file already exists there it is OVERWRITTEN in place; choose a fresh output path or confirm overwrite first.',
|
|
22750
22770
|
inputSchema: {
|
|
22751
22771
|
inputPath: external_exports.string().describe("Path to the CSV/TSV/JSON input file"),
|
|
22752
22772
|
outputPath: external_exports.string().describe("Path for the output .numbers file"),
|
|
@@ -22778,7 +22798,7 @@ Sheet: "${result.sheetName}" | Table: "${result.tableName}"`,
|
|
|
22778
22798
|
server.registerTool(
|
|
22779
22799
|
"update-rows",
|
|
22780
22800
|
{
|
|
22781
|
-
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 and OVERWRITES the entire contents of each targeted row.",
|
|
22801
|
+
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.",
|
|
22782
22802
|
inputSchema: {
|
|
22783
22803
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22784
22804
|
updates: external_exports.array(
|
|
@@ -22858,7 +22878,7 @@ server.registerTool(
|
|
|
22858
22878
|
server.registerTool(
|
|
22859
22879
|
"set-formula",
|
|
22860
22880
|
{
|
|
22861
|
-
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
|
|
22881
|
+
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.',
|
|
22862
22882
|
inputSchema: {
|
|
22863
22883
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22864
22884
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22889,7 +22909,7 @@ Computed value: ${result.computedValue}`,
|
|
|
22889
22909
|
server.registerTool(
|
|
22890
22910
|
"set-formulas-batch",
|
|
22891
22911
|
{
|
|
22892
|
-
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
|
|
22912
|
+
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.",
|
|
22893
22913
|
inputSchema: {
|
|
22894
22914
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22895
22915
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22947,7 +22967,7 @@ var cellStyleSchema = external_exports.object({
|
|
|
22947
22967
|
server.registerTool(
|
|
22948
22968
|
"set-cell-style",
|
|
22949
22969
|
{
|
|
22950
|
-
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
|
|
22970
|
+
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.",
|
|
22951
22971
|
inputSchema: {
|
|
22952
22972
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22953
22973
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22974,7 +22994,7 @@ server.registerTool(
|
|
|
22974
22994
|
server.registerTool(
|
|
22975
22995
|
"set-cells-style-batch",
|
|
22976
22996
|
{
|
|
22977
|
-
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
|
|
22997
|
+
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.",
|
|
22978
22998
|
inputSchema: {
|
|
22979
22999
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22980
23000
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23005,7 +23025,7 @@ server.registerTool(
|
|
|
23005
23025
|
server.registerTool(
|
|
23006
23026
|
"set-column-width",
|
|
23007
23027
|
{
|
|
23008
|
-
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
|
|
23028
|
+
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.",
|
|
23009
23029
|
inputSchema: {
|
|
23010
23030
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23011
23031
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23035,7 +23055,7 @@ server.registerTool(
|
|
|
23035
23055
|
server.registerTool(
|
|
23036
23056
|
"set-row-height",
|
|
23037
23057
|
{
|
|
23038
|
-
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
|
|
23058
|
+
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.",
|
|
23039
23059
|
inputSchema: {
|
|
23040
23060
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23041
23061
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23065,7 +23085,7 @@ server.registerTool(
|
|
|
23065
23085
|
server.registerTool(
|
|
23066
23086
|
"merge-cells",
|
|
23067
23087
|
{
|
|
23068
|
-
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
|
|
23088
|
+
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.",
|
|
23069
23089
|
inputSchema: {
|
|
23070
23090
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23071
23091
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23092,7 +23112,7 @@ server.registerTool(
|
|
|
23092
23112
|
server.registerTool(
|
|
23093
23113
|
"unmerge-cells",
|
|
23094
23114
|
{
|
|
23095
|
-
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
|
|
23115
|
+
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).",
|
|
23096
23116
|
inputSchema: {
|
|
23097
23117
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23098
23118
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
-
# Automation Permission for
|
|
1
|
+
# Automation Permission for Formula & Formatting Operations
|
|
2
2
|
|
|
3
3
|
Apple Numbers MCP has **two distinct backends**, and only one of them needs a
|
|
4
|
-
special macOS permission
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
special macOS permission. The split is **values vs. formatting**, not read vs.
|
|
5
|
+
write:
|
|
6
|
+
|
|
7
|
+
- **Reads and value/structure writes** go through
|
|
8
|
+
[numbers-parser](https://pypi.org/project/numbers-parser/) (Python). They open
|
|
9
|
+
the `.numbers` file **directly** off disk and need **no** special permission —
|
|
10
|
+
just normal file access. No Apple event is ever sent, so Numbers.app doesn't
|
|
11
|
+
even have to be installed.
|
|
12
|
+
- **Formulas and formatting** go through **AppleScript driving Numbers.app**.
|
|
10
13
|
numbers-parser can't write styles or formulas, so the server scripts Numbers.app
|
|
11
14
|
to do it. macOS gates one app controlling another behind the **Automation**
|
|
12
15
|
permission, so these tools require it.
|
|
@@ -16,51 +19,50 @@ how to grant or reset it, what failure looks like, and how to verify.
|
|
|
16
19
|
|
|
17
20
|
## Which tools need Automation permission
|
|
18
21
|
|
|
19
|
-
**Need it** (AppleScript → Numbers.app —
|
|
20
|
-
changes, CSV import, file creation):
|
|
22
|
+
**Need it** (AppleScript → Numbers.app — formulas, styles, dimensions, merges):
|
|
21
23
|
|
|
22
|
-
- `set-cell`, `set-cells-batch`
|
|
23
|
-
- `add-rows`, `update-rows`, `delete-rows`
|
|
24
|
-
- `add-sheet`, `add-table`, `rename-sheet`, `rename-table`
|
|
25
24
|
- `set-formula`, `set-formulas-batch`
|
|
26
25
|
- `set-cell-style`, `set-cells-style-batch`
|
|
27
26
|
- `set-column-width`, `set-row-height`
|
|
28
27
|
- `merge-cells`, `unmerge-cells`
|
|
29
|
-
- `create-spreadsheet`
|
|
30
|
-
- `import-csv`
|
|
31
28
|
|
|
32
|
-
**Do NOT need it** (pure numbers-parser, file
|
|
29
|
+
**Do NOT need it** (pure numbers-parser, the file opened directly off disk):
|
|
33
30
|
|
|
34
|
-
- `get-file-info`
|
|
35
|
-
- `
|
|
36
|
-
|
|
37
|
-
- `
|
|
38
|
-
- `
|
|
31
|
+
- Reads — `get-file-info`, `read-table`, `get-cell`, `search`, `export-table`
|
|
32
|
+
- Value writes — `set-cell`, `set-cells-batch`, `add-rows`, `update-rows`,
|
|
33
|
+
`delete-rows`
|
|
34
|
+
- Structure writes — `add-sheet`, `add-table`, `rename-sheet`, `rename-table`
|
|
35
|
+
- File creation — `create-spreadsheet`, `import-csv`
|
|
39
36
|
- `health-check` / `doctor` (these only probe the environment)
|
|
40
37
|
|
|
41
|
-
So you can
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
So you can inspect, search, export **and edit** a spreadsheet — cell values, whole
|
|
39
|
+
rows, sheets and tables, new files from CSV — with no Automation permission at all,
|
|
40
|
+
and with Numbers.app not installed. The permission only matters the moment you
|
|
41
|
+
write a **live formula** or change **formatting** (font, color, number format,
|
|
42
|
+
alignment, column width, row height, merges).
|
|
44
43
|
|
|
45
|
-
> **Numbers.app must be installed
|
|
46
|
-
> the app has to be present at `/Applications/Numbers.app`
|
|
47
|
-
> `/System/Applications/Numbers.app`). Reads
|
|
48
|
-
> missing, `doctor` reports `numbers_app: warn`
|
|
44
|
+
> **Numbers.app must be installed for the formula/format tools.** Only that path
|
|
45
|
+
> scripts Numbers.app, so the app has to be present at `/Applications/Numbers.app`
|
|
46
|
+
> (or `/System/Applications/Numbers.app`). Reads and the numbers-parser writes
|
|
47
|
+
> don't need it. If Numbers.app is missing, `doctor` reports `numbers_app: warn`
|
|
48
|
+
> and only the formula/format tools fail.
|
|
49
49
|
|
|
50
50
|
## How the permission prompt appears
|
|
51
51
|
|
|
52
|
-
The **first time** a
|
|
52
|
+
The **first time** a formula or formatting tool runs, macOS shows a one-time
|
|
53
|
+
dialog:
|
|
53
54
|
|
|
54
55
|
> **"<Host App>" wants access to control "Numbers". Allowing control will provide
|
|
55
56
|
> access to documents and data in "Numbers", and to perform actions within that
|
|
56
57
|
> app.**
|
|
57
58
|
|
|
58
|
-
Click **OK / Allow**. The grant is remembered, so subsequent
|
|
59
|
+
Click **OK / Allow**. The grant is remembered, so subsequent calls don't prompt
|
|
59
60
|
again. The "host app" is whatever process launched the MCP server — Claude
|
|
60
61
|
Desktop, Terminal, iTerm, or VS Code — **not** `node` and **not** Numbers.app.
|
|
61
62
|
|
|
62
|
-
If the host app runs headless or the dialog is dismissed/denied, the
|
|
63
|
-
(see below) and you must grant the permission manually.
|
|
63
|
+
If the host app runs headless or the dialog is dismissed/denied, the
|
|
64
|
+
formula/format call fails (see below) and you must grant the permission manually.
|
|
65
|
+
Value and structure writes are unaffected — they never send an Apple event.
|
|
64
66
|
|
|
65
67
|
## How to grant it manually
|
|
66
68
|
|
|
@@ -85,7 +87,7 @@ If the host app runs headless or the dialog is dismissed/denied, the write fails
|
|
|
85
87
|
## What failure looks like
|
|
86
88
|
|
|
87
89
|
When the host app lacks Automation permission for Numbers, the AppleScript layer
|
|
88
|
-
fails and the
|
|
90
|
+
fails and the formula/format tool returns an error containing:
|
|
89
91
|
|
|
90
92
|
```
|
|
91
93
|
Not authorized to send Apple events to Numbers.
|
|
@@ -95,9 +97,10 @@ Not authorized to send Apple events to Numbers.
|
|
|
95
97
|
`"Numbers got an error: ..."` style messages if Numbers.app itself can't be
|
|
96
98
|
driven. In all of these cases the underlying `.numbers` file is **not** modified.
|
|
97
99
|
|
|
98
|
-
If you instead see
|
|
99
|
-
|
|
100
|
-
Numbers.app
|
|
100
|
+
If you instead see an error saying Numbers can't be found or isn't running, the
|
|
101
|
+
problem is that the app isn't installed/openable — install Numbers.app and retry.
|
|
102
|
+
(The tools do **not** require Numbers.app to already be running: the AppleScript
|
|
103
|
+
layer launches it and opens the file itself. It does have to be *installed*.)
|
|
101
104
|
|
|
102
105
|
## Resetting the permission
|
|
103
106
|
|
|
@@ -117,8 +120,10 @@ e.g.:
|
|
|
117
120
|
tccutil reset AppleEvents com.apple.Terminal
|
|
118
121
|
```
|
|
119
122
|
|
|
120
|
-
After resetting, **fully quit and reopen the host app**, then run any
|
|
121
|
-
(e.g. `set-cell`) and click **OK** when the dialog
|
|
123
|
+
After resetting, **fully quit and reopen the host app**, then run any
|
|
124
|
+
formula/format tool (e.g. `set-cell-style`) and click **OK** when the dialog
|
|
125
|
+
appears. A value write such as `set-cell` will *not* re-trigger the prompt — it
|
|
126
|
+
never crosses the AppleScript boundary.
|
|
122
127
|
|
|
123
128
|
## Verifying
|
|
124
129
|
|
|
@@ -128,20 +133,26 @@ as `ok` / `warn` / `fail`:
|
|
|
128
133
|
- **`python_interpreter`** — the resolved Python's path and version (warns when
|
|
129
134
|
older than 3.11; stock macOS ships 3.9).
|
|
130
135
|
- **`numbers_parser`** — is the Python read sidecar installed (powers all reads).
|
|
131
|
-
- **`numbers_app`** — is Numbers.app present (required for
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
- **`numbers_app`** — is Numbers.app present (required for the formula/format
|
|
137
|
+
tools; everything else works without it).
|
|
138
|
+
- **`automation_permission`** — an informational reminder that the formula/format
|
|
139
|
+
tools need Automation permission, granted on first use.
|
|
134
140
|
|
|
135
141
|
> **Note:** Automation permission can't be probed without actually trying to
|
|
136
142
|
> control Numbers (that would itself trigger the prompt), so `doctor` reports it
|
|
137
143
|
> **informationally** rather than testing it live. The definitive test is to run a
|
|
138
|
-
>
|
|
139
|
-
>
|
|
140
|
-
> grant or reset it
|
|
144
|
+
> tool that actually crosses the AppleScript boundary — e.g. `set-cell-style` (or
|
|
145
|
+
> `set-formula`) on a scratch file. If it succeeds, the permission is in place; if
|
|
146
|
+
> it returns *"Not authorized to send Apple events to Numbers,"* grant or reset it
|
|
147
|
+
> as above.
|
|
148
|
+
>
|
|
149
|
+
> **Don't test with `set-cell`** (or any other value/structure write). Those run
|
|
150
|
+
> on the numbers-parser sidecar and succeed whether or not the grant exists, so
|
|
151
|
+
> they always "pass" and tell you nothing about the permission.
|
|
141
152
|
|
|
142
153
|
## See also
|
|
143
154
|
|
|
144
|
-
- [docs/LIMITATIONS.md](./LIMITATIONS.md) — the full
|
|
155
|
+
- [docs/LIMITATIONS.md](./LIMITATIONS.md) — the full backend split and other
|
|
145
156
|
limits.
|
|
146
157
|
- [Known Limitations](../README.md#known-limitations) and
|
|
147
158
|
[Security and Privacy](../README.md#security-and-privacy) in the README.
|
package/docs/LIMITATIONS.md
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
1
|
# Limitations
|
|
2
2
|
|
|
3
3
|
Apple Numbers MCP is a **hybrid** bridge to `.numbers` spreadsheets: it **reads**
|
|
4
|
-
them offline with
|
|
5
|
-
(Python, no app
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
them *and writes their values and structure* offline with
|
|
5
|
+
[numbers-parser](https://pypi.org/project/numbers-parser/) (Python, no app
|
|
6
|
+
required), and **formats** them — styles, live formulas, column/row dimensions,
|
|
7
|
+
merges — by scripting **Numbers.app** via AppleScript (because numbers-parser
|
|
8
|
+
can't write those). That split is the source of most of the limitations below.
|
|
9
|
+
Note it is a **values-vs-formatting** split, not a read-vs-write one.
|
|
8
10
|
|
|
9
11
|
This page documents the real limitations — what the server can't do and why — so
|
|
10
12
|
they aren't re-investigated every release. These agree with the README's
|
|
11
13
|
[Known Limitations](../README.md#known-limitations); this page adds the *why* and
|
|
12
14
|
*what to do* for each.
|
|
13
15
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
**Why:** numbers-parser can read a `.numbers` file directly off disk
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
16
|
+
## Formulas & formatting require Numbers.app + Automation permission (nothing else does)
|
|
17
|
+
|
|
18
|
+
**Why:** numbers-parser can read a `.numbers` file directly off disk *and* write
|
|
19
|
+
values, rows, sheets and tables into it — but it does **not** write styles,
|
|
20
|
+
formulas, or cell-dimension changes reliably. To do those, the server scripts
|
|
21
|
+
**Numbers.app** through AppleScript. That means the eight formula/format tools
|
|
22
|
+
need (a) **Numbers.app installed** and (b) the host app to have **Automation**
|
|
23
|
+
permission to control Numbers. Every other tool — the reads (`get-file-info`,
|
|
24
|
+
`read-table`, `get-cell`, `search`, `export-table`) *and* the value/structure
|
|
25
|
+
writes (`set-cell(s)`, `add/update/delete-rows`, `add-sheet`/`add-table`,
|
|
26
|
+
`rename-*`, `create-spreadsheet`, `import-csv`) — uses only numbers-parser and
|
|
27
|
+
needs **neither**: no Numbers.app, no Automation grant, no Apple event.
|
|
28
|
+
|
|
29
|
+
**What to do:** For inspection/search/export, and for ordinary value and structure
|
|
30
|
+
edits, nothing special is needed. Only for `set-formula(s)`, `set-*-style`,
|
|
31
|
+
`set-column-width`/`set-row-height` and `merge`/`unmerge` do you have to install
|
|
32
|
+
Numbers.app and grant Automation permission. Full steps in
|
|
33
|
+
[AUTOMATION-PERMISSION.md](./AUTOMATION-PERMISSION.md). Verify with `doctor` — and
|
|
34
|
+
test the grant with a *formula/format* tool, since a `set-cell` succeeds without
|
|
35
|
+
it and so proves nothing.
|
|
30
36
|
|
|
31
37
|
## Formulas and styling are AppleScript-only
|
|
32
38
|
|
|
@@ -37,9 +43,11 @@ numbers-parser doesn't expose reliable writes for these. They go exclusively
|
|
|
37
43
|
through Numbers.app via AppleScript, so they inherit the macOS-only + Automation
|
|
38
44
|
requirements above and can't run on Linux.
|
|
39
45
|
|
|
40
|
-
**What to do:** Run formula/format tools on macOS with Numbers.app.
|
|
41
|
-
|
|
42
|
-
formatted values (via `get-cell verbose: true`) — you just can't set them.
|
|
46
|
+
**What to do:** Run formula/format tools on macOS with Numbers.app installed.
|
|
47
|
+
Where only the Python path is available, you can still *read* formulas and
|
|
48
|
+
formatted values (via `get-cell verbose: true`) — you just can't set them. Note
|
|
49
|
+
the npm package declares `os: ["darwin"]`, so Linux isn't a supported install
|
|
50
|
+
target even for that read-only subset (see "macOS only" below).
|
|
43
51
|
|
|
44
52
|
## Values vs. formulas
|
|
45
53
|
|
|
@@ -50,6 +58,40 @@ value writes the literal string, not a live formula.
|
|
|
50
58
|
**What to do:** Use `set-formula` / `set-formulas-batch` to write actual formulas
|
|
51
59
|
(these require Numbers.app). Use the value tools for plain data.
|
|
52
60
|
|
|
61
|
+
## `import-csv` auto-types CSV/TSV fields, so leading zeros are lost
|
|
62
|
+
|
|
63
|
+
**Why:** For a `csv` or `tsv` source, every field is passed through the sidecar's
|
|
64
|
+
`_auto_convert()` before it is written: `""` becomes an empty cell, `true`/`false`
|
|
65
|
+
become booleans, and anything Python's `int()` or `float()` accepts becomes a
|
|
66
|
+
number. That is deliberately *less* conservative than the value-write tools
|
|
67
|
+
(`set-cell`, `set-cells-batch`, `add-rows`, `update-rows`), which reject
|
|
68
|
+
leading-zero integers so identifier-like strings keep their exact text. So a zip
|
|
69
|
+
code `01234` imports as the number `1234`, `007` imports as `7`, and a part number
|
|
70
|
+
`1e5` imports as the number `100000`. There is no per-column type control on import, and the
|
|
71
|
+
conversion isn't reversible once the `.numbers` file is written.
|
|
72
|
+
|
|
73
|
+
A `json` source does **not** do this — those values go straight through, so a JSON
|
|
74
|
+
string `"01234"` survives intact.
|
|
75
|
+
|
|
76
|
+
**What to do:** For CSV/TSV columns holding zero-padded identifiers, import from
|
|
77
|
+
JSON instead, or repair the affected cells after import with `set-cells-batch`
|
|
78
|
+
using `type: "string"`. Spot-check with `get-cell` after importing anything
|
|
79
|
+
identifier-like.
|
|
80
|
+
|
|
81
|
+
## `import-csv` takes its JSON columns from the first object only
|
|
82
|
+
|
|
83
|
+
**Why:** For an array-of-objects JSON source the column set is the keys of the
|
|
84
|
+
**first** object. A key that appears only in a later object is dropped silently,
|
|
85
|
+
and objects missing an early key get blank cells. An array-of-arrays source has no
|
|
86
|
+
header names at all: columns are named `Column_0`, `Column_1`, … and the first row
|
|
87
|
+
is kept as data. Separately, when `format` is `auto`, any extension that isn't
|
|
88
|
+
`.csv`, `.tsv` or `.json` falls back to **CSV** rather than erroring.
|
|
89
|
+
|
|
90
|
+
**What to do:** Normalize a heterogeneous JSON export to a common key set before
|
|
91
|
+
importing, and pass `format` explicitly for inputs whose extension isn't one of the
|
|
92
|
+
three recognized ones. The tool's response reports the format it actually used and
|
|
93
|
+
the column count it produced — check both when the input is unusual.
|
|
94
|
+
|
|
53
95
|
## No charts, images, or conditional formatting
|
|
54
96
|
|
|
55
97
|
**Why:** numbers-parser doesn't expose charts, embedded images, or
|
|
@@ -105,24 +147,41 @@ upgrade numbers-parser first. Check the installed version with `health-check` /
|
|
|
105
147
|
|
|
106
148
|
## Concurrent edits while Numbers.app has the file open
|
|
107
149
|
|
|
108
|
-
**Why:** Two backends can touch the same file. The
|
|
109
|
-
whatever is on disk
|
|
110
|
-
file **open with unsaved changes**, what
|
|
111
|
-
|
|
112
|
-
can disagree, and saves can race.
|
|
150
|
+
**Why:** Two backends can touch the same file. The numbers-parser path (reads and
|
|
151
|
+
value/structure writes) sees whatever is on disk; the formula/format path drives
|
|
152
|
+
Numbers.app. If Numbers.app has the file **open with unsaved changes**, what
|
|
153
|
+
numbers-parser reads off disk can be stale, and an AppleScript call targets the
|
|
154
|
+
app's in-memory copy — the two can disagree, and saves can race.
|
|
155
|
+
|
|
156
|
+
Three side effects of the AppleScript path matter here, because they apply to
|
|
157
|
+
**every** formula/format tool:
|
|
158
|
+
|
|
159
|
+
1. **It opens the file itself**, launching Numbers.app if it isn't running. These
|
|
160
|
+
tools don't require Numbers.app to already be running — only *installed* — so a
|
|
161
|
+
single `set-column-width` on an idle Mac can pop a window and, the first time,
|
|
162
|
+
the Automation prompt.
|
|
163
|
+
2. **It saves the whole document, not just the change.** If you have the workbook
|
|
164
|
+
open in Numbers with unsaved hand edits, one formula/format call commits *all*
|
|
165
|
+
of them to disk.
|
|
166
|
+
3. **It leaves the document open** afterwards — nothing ever closes it — so every
|
|
167
|
+
later numbers-parser read or write on that file races the app's in-memory copy,
|
|
168
|
+
which is exactly the hazard this section describes.
|
|
113
169
|
|
|
114
170
|
**What to do:** Prefer to **close the file in Numbers.app** before reading or doing
|
|
115
|
-
bulk writes through the server, so disk and app agree
|
|
171
|
+
bulk writes through the server, so disk and app agree — and close it again after a
|
|
172
|
+
run of formula/format calls, which will have reopened it. If you must keep it open,
|
|
116
173
|
read back with `read-table` / `get-cell` after writes to confirm the result landed,
|
|
117
174
|
and avoid editing the same cells by hand in Numbers.app at the same time.
|
|
118
175
|
|
|
119
176
|
## Concurrent writes to the same file are last-writer-wins
|
|
120
177
|
|
|
121
178
|
**Why:** Each write tool opens the `.numbers` file, applies its change to an
|
|
122
|
-
in-memory document, and saves the **whole document** back.
|
|
123
|
-
**atomic** — every mutating command writes to a sibling
|
|
124
|
-
it onto the target — so an interrupted or crashed
|
|
125
|
-
half-written file.
|
|
179
|
+
in-memory document, and saves the **whole document** back. On the numbers-parser
|
|
180
|
+
path the save is **atomic** — every mutating sidecar command writes to a sibling
|
|
181
|
+
temp file and `os.replace()`s it onto the target — so an interrupted or crashed
|
|
182
|
+
write can never leave a torn or half-written file. (The formula/format tools save
|
|
183
|
+
through Numbers.app instead, so that guarantee is the app's, not this server's.)
|
|
184
|
+
But atomicity protects against *corruption*, **not** against
|
|
126
185
|
*lost updates*: if two writes to the same file overlap (two agents, two server
|
|
127
186
|
instances, or a server write racing a hand edit in Numbers.app), each loaded the
|
|
128
187
|
document independently, and whichever saves **last** overwrites the other's change
|
|
@@ -135,11 +194,33 @@ related edits into a single call (`set-cells-batch`, `update-rows`,
|
|
|
135
194
|
starting the next on the same file, and after a burst of writes read back with
|
|
136
195
|
`read-table` / `get-cell` to confirm the final state is what you intended.
|
|
137
196
|
|
|
138
|
-
##
|
|
139
|
-
|
|
140
|
-
**Why:**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
197
|
+
## Per-call timeouts are fixed (30 s sidecar, 60 s AppleScript)
|
|
198
|
+
|
|
199
|
+
**Why:** Every numbers-parser sidecar call runs under a **30-second** process
|
|
200
|
+
timeout, and every AppleScript call under **60 seconds** (the generated script also
|
|
201
|
+
carries an in-app `with timeout` block set 5 s lower, so Numbers.app aborts cleanly
|
|
202
|
+
before the process is killed). Neither is configurable — there is no
|
|
203
|
+
`APPLE_NUMBERS_MCP_*` variable for either one.
|
|
204
|
+
`APPLE_NUMBERS_MCP_SETUP_TIMEOUT` governs only the one-time venv bootstrap, and
|
|
205
|
+
`APPLE_NUMBERS_MCP_MAX_BUFFER` caps stdout size, not wall-clock time. A very large
|
|
206
|
+
workbook, or a wedged Numbers.app, surfaces as `Operation timed out after
|
|
207
|
+
30000ms. File may be very large.`
|
|
208
|
+
|
|
209
|
+
**What to do:** Narrow the request rather than hunting for a knob: read in slices
|
|
210
|
+
with `read-table`'s `startRow`/`endRow` and `columns`, split large
|
|
211
|
+
`set-cells-batch` / `update-rows` calls into smaller batches, and close the file in
|
|
212
|
+
Numbers.app so the AppleScript layer isn't waiting on the app.
|
|
213
|
+
|
|
214
|
+
## macOS only (the package declares `os: ["darwin"]`)
|
|
215
|
+
|
|
216
|
+
**Why:** The published npm package declares `"os": ["darwin"]`, so `npm`/`pnpm`
|
|
217
|
+
refuse to install it on any other platform (`EBADPLATFORM`). The numbers-parser
|
|
218
|
+
path — reads plus value/structure writes — is platform-independent in principle
|
|
219
|
+
(nothing in the sidecar branches on the platform), but the formula/format tools
|
|
220
|
+
script Numbers.app, which exists only on macOS, so the package as a whole is
|
|
221
|
+
pinned to darwin deliberately.
|
|
222
|
+
|
|
223
|
+
**What to do:** Run this server on a Mac. Add Numbers.app plus Automation
|
|
224
|
+
permission only if you need formulas or formatting; reads and value/structure
|
|
225
|
+
writes need neither. See
|
|
145
226
|
[AUTOMATION-PERMISSION.md](./AUTOMATION-PERMISSION.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apple-numbers-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
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",
|
|
@@ -87,6 +87,6 @@
|
|
|
87
87
|
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
88
88
|
"test:all": "vitest run && vitest run --config vitest.integration.config.ts",
|
|
89
89
|
"test:watch": "vitest",
|
|
90
|
-
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .
|
|
90
|
+
"version": "node scripts/sync-plugin-version.mjs && git add .claude-plugin .agents/plugins codex .antigravity-plugin"
|
|
91
91
|
}
|
|
92
92
|
}
|