apple-numbers-mcp 1.1.9 → 1.1.11
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 +38 -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.4/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.4/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.4/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.4/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.4/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.4/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();
|
|
@@ -3770,6 +3770,7 @@ var require_fast_uri = __commonJS({
|
|
|
3770
3770
|
return uriTokens.join("");
|
|
3771
3771
|
}
|
|
3772
3772
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3773
|
+
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3773
3774
|
function getParseError(parsed, matches) {
|
|
3774
3775
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3775
3776
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3799,6 +3800,11 @@ var require_fast_uri = __commonJS({
|
|
|
3799
3800
|
uri = "//" + uri;
|
|
3800
3801
|
}
|
|
3801
3802
|
}
|
|
3803
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX);
|
|
3804
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
|
|
3805
|
+
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3806
|
+
malformedAuthorityOrPort = true;
|
|
3807
|
+
}
|
|
3802
3808
|
const matches = uri.match(URI_PARSE);
|
|
3803
3809
|
if (matches) {
|
|
3804
3810
|
parsed.scheme = matches[1];
|
|
@@ -3842,7 +3848,7 @@ var require_fast_uri = __commonJS({
|
|
|
3842
3848
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3843
3849
|
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
3844
3850
|
try {
|
|
3845
|
-
parsed.host = URL
|
|
3851
|
+
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
3846
3852
|
} catch (e) {
|
|
3847
3853
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3848
3854
|
}
|
|
@@ -21421,7 +21427,7 @@ function findSystemPython() {
|
|
|
21421
21427
|
}
|
|
21422
21428
|
}
|
|
21423
21429
|
throw new Error(
|
|
21424
|
-
"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.
|
|
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. Run the doctor tool to diagnose, and see https://github.com/sweetrb/apple-numbers-mcp#troubleshooting"
|
|
21425
21431
|
);
|
|
21426
21432
|
}
|
|
21427
21433
|
function resolvePython() {
|
|
@@ -22138,27 +22144,27 @@ function runDoctor(_manager) {
|
|
|
22138
22144
|
checks.push({
|
|
22139
22145
|
name: "numbers_app",
|
|
22140
22146
|
status: "ok",
|
|
22141
|
-
detail: `Numbers.app present \u2014
|
|
22147
|
+
detail: `Numbers.app present \u2014 formula and formatting tools available (${found})`
|
|
22142
22148
|
});
|
|
22143
22149
|
} else {
|
|
22144
22150
|
checks.push({
|
|
22145
22151
|
name: "numbers_app",
|
|
22146
22152
|
status: "warn",
|
|
22147
|
-
detail: "Numbers.app not found. Reads
|
|
22153
|
+
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."
|
|
22148
22154
|
});
|
|
22149
22155
|
}
|
|
22150
22156
|
} catch (e) {
|
|
22151
22157
|
checks.push({
|
|
22152
22158
|
name: "numbers_app",
|
|
22153
22159
|
status: "warn",
|
|
22154
|
-
detail: `could not verify Numbers.app: ${String(e)}. Reads
|
|
22160
|
+
detail: `could not verify Numbers.app: ${String(e)}. Reads, exports and value/structure writes still work; only the formula/formatting tools need Numbers.app.`
|
|
22155
22161
|
});
|
|
22156
22162
|
}
|
|
22157
22163
|
try {
|
|
22158
22164
|
checks.push({
|
|
22159
22165
|
name: "automation_permission",
|
|
22160
22166
|
status: "ok",
|
|
22161
|
-
detail: "AppleScript
|
|
22167
|
+
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."
|
|
22162
22168
|
});
|
|
22163
22169
|
} catch (e) {
|
|
22164
22170
|
checks.push({
|
|
@@ -22308,7 +22314,7 @@ var headerCellSchema = external_exports.string().max(MAX_HEADER_LEN);
|
|
|
22308
22314
|
var server = new McpServer({
|
|
22309
22315
|
name: "apple-numbers",
|
|
22310
22316
|
version: version2,
|
|
22311
|
-
description: "MCP server for reading and writing Apple Numbers (.numbers) spreadsheet files.
|
|
22317
|
+
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."
|
|
22312
22318
|
});
|
|
22313
22319
|
server.registerTool(
|
|
22314
22320
|
"health-check",
|
|
@@ -22330,7 +22336,7 @@ server.registerTool(
|
|
|
22330
22336
|
server.registerTool(
|
|
22331
22337
|
"doctor",
|
|
22332
22338
|
{
|
|
22333
|
-
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
|
|
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 (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.",
|
|
22334
22340
|
inputSchema: {},
|
|
22335
22341
|
outputSchema: {
|
|
22336
22342
|
healthy: external_exports.boolean().optional(),
|
|
@@ -22395,7 +22401,7 @@ ${summary}`,
|
|
|
22395
22401
|
server.registerTool(
|
|
22396
22402
|
"read-table",
|
|
22397
22403
|
{
|
|
22398
|
-
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,
|
|
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, 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.",
|
|
22399
22405
|
inputSchema: {
|
|
22400
22406
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22401
22407
|
sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
|
|
@@ -22470,7 +22476,7 @@ ${lines.join("\n")}`, {
|
|
|
22470
22476
|
server.registerTool(
|
|
22471
22477
|
"export-table",
|
|
22472
22478
|
{
|
|
22473
|
-
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.",
|
|
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.\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.",
|
|
22474
22480
|
inputSchema: {
|
|
22475
22481
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22476
22482
|
format: external_exports.enum(["csv", "tsv", "json"]).describe("Output format"),
|
|
@@ -22566,7 +22572,7 @@ server.registerTool(
|
|
|
22566
22572
|
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.
|
|
22567
22573
|
Returns: the written coordinates, value, and the sheet/table affected.
|
|
22568
22574
|
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).
|
|
22569
|
-
Safety: modifies the .numbers file in place and OVERWRITES any existing data in the target cell.`,
|
|
22575
|
+
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.`,
|
|
22570
22576
|
inputSchema: {
|
|
22571
22577
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22572
22578
|
row: external_exports.number().int().min(0).max(MAX_INDEX).describe("Row index (0-based)"),
|
|
@@ -22598,7 +22604,7 @@ Safety: modifies the .numbers file in place and OVERWRITES any existing data in
|
|
|
22598
22604
|
server.registerTool(
|
|
22599
22605
|
"set-cells-batch",
|
|
22600
22606
|
{
|
|
22601
|
-
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.",
|
|
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 via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES any existing data in the targeted cells.",
|
|
22602
22608
|
inputSchema: {
|
|
22603
22609
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22604
22610
|
updates: external_exports.array(
|
|
@@ -22632,7 +22638,7 @@ server.registerTool(
|
|
|
22632
22638
|
server.registerTool(
|
|
22633
22639
|
"add-rows",
|
|
22634
22640
|
{
|
|
22635
|
-
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.",
|
|
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 via the numbers-parser sidecar (does not require Numbers.app). Additive (appends rows) and does not overwrite existing data, but still mutates the file.",
|
|
22636
22642
|
inputSchema: {
|
|
22637
22643
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22638
22644
|
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(
|
|
@@ -22661,7 +22667,7 @@ server.registerTool(
|
|
|
22661
22667
|
server.registerTool(
|
|
22662
22668
|
"delete-rows",
|
|
22663
22669
|
{
|
|
22664
|
-
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.",
|
|
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 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.",
|
|
22665
22671
|
inputSchema: {
|
|
22666
22672
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22667
22673
|
startRow: external_exports.number().int().min(0).max(MAX_INDEX).describe("First row to delete (0-based, inclusive)"),
|
|
@@ -22688,7 +22694,7 @@ server.registerTool(
|
|
|
22688
22694
|
server.registerTool(
|
|
22689
22695
|
"add-sheet",
|
|
22690
22696
|
{
|
|
22691
|
-
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.",
|
|
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. 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.",
|
|
22692
22698
|
inputSchema: {
|
|
22693
22699
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22694
22700
|
sheetName: external_exports.string().max(MAX_NAME_LEN).describe("Name for the new sheet"),
|
|
@@ -22714,7 +22720,7 @@ server.registerTool(
|
|
|
22714
22720
|
server.registerTool(
|
|
22715
22721
|
"add-table",
|
|
22716
22722
|
{
|
|
22717
|
-
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.",
|
|
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. 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.",
|
|
22718
22724
|
inputSchema: {
|
|
22719
22725
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22720
22726
|
sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
|
|
@@ -22740,7 +22746,7 @@ server.registerTool(
|
|
|
22740
22746
|
server.registerTool(
|
|
22741
22747
|
"import-csv",
|
|
22742
22748
|
{
|
|
22743
|
-
description:
|
|
22749
|
+
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.',
|
|
22744
22750
|
inputSchema: {
|
|
22745
22751
|
inputPath: external_exports.string().describe("Path to the CSV/TSV/JSON input file"),
|
|
22746
22752
|
outputPath: external_exports.string().describe("Path for the output .numbers file"),
|
|
@@ -22772,7 +22778,7 @@ Sheet: "${result.sheetName}" | Table: "${result.tableName}"`,
|
|
|
22772
22778
|
server.registerTool(
|
|
22773
22779
|
"update-rows",
|
|
22774
22780
|
{
|
|
22775
|
-
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.",
|
|
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 via the numbers-parser sidecar (does not require Numbers.app) and OVERWRITES the entire contents of each targeted row.",
|
|
22776
22782
|
inputSchema: {
|
|
22777
22783
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22778
22784
|
updates: external_exports.array(
|
|
@@ -22852,7 +22858,7 @@ server.registerTool(
|
|
|
22852
22858
|
server.registerTool(
|
|
22853
22859
|
"set-formula",
|
|
22854
22860
|
{
|
|
22855
|
-
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
|
|
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 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.',
|
|
22856
22862
|
inputSchema: {
|
|
22857
22863
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22858
22864
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22883,7 +22889,7 @@ Computed value: ${result.computedValue}`,
|
|
|
22883
22889
|
server.registerTool(
|
|
22884
22890
|
"set-formulas-batch",
|
|
22885
22891
|
{
|
|
22886
|
-
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
|
|
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 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.",
|
|
22887
22893
|
inputSchema: {
|
|
22888
22894
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22889
22895
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22941,7 +22947,7 @@ var cellStyleSchema = external_exports.object({
|
|
|
22941
22947
|
server.registerTool(
|
|
22942
22948
|
"set-cell-style",
|
|
22943
22949
|
{
|
|
22944
|
-
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
|
|
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 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.",
|
|
22945
22951
|
inputSchema: {
|
|
22946
22952
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22947
22953
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22968,7 +22974,7 @@ server.registerTool(
|
|
|
22968
22974
|
server.registerTool(
|
|
22969
22975
|
"set-cells-style-batch",
|
|
22970
22976
|
{
|
|
22971
|
-
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
|
|
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 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.",
|
|
22972
22978
|
inputSchema: {
|
|
22973
22979
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
22974
22980
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -22999,7 +23005,7 @@ server.registerTool(
|
|
|
22999
23005
|
server.registerTool(
|
|
23000
23006
|
"set-column-width",
|
|
23001
23007
|
{
|
|
23002
|
-
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
|
|
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 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.",
|
|
23003
23009
|
inputSchema: {
|
|
23004
23010
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23005
23011
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23029,7 +23035,7 @@ server.registerTool(
|
|
|
23029
23035
|
server.registerTool(
|
|
23030
23036
|
"set-row-height",
|
|
23031
23037
|
{
|
|
23032
|
-
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
|
|
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 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.",
|
|
23033
23039
|
inputSchema: {
|
|
23034
23040
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23035
23041
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23059,7 +23065,7 @@ server.registerTool(
|
|
|
23059
23065
|
server.registerTool(
|
|
23060
23066
|
"merge-cells",
|
|
23061
23067
|
{
|
|
23062
|
-
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
|
|
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 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.",
|
|
23063
23069
|
inputSchema: {
|
|
23064
23070
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23065
23071
|
sheet: external_exports.string().describe("Sheet name"),
|
|
@@ -23086,7 +23092,7 @@ server.registerTool(
|
|
|
23086
23092
|
server.registerTool(
|
|
23087
23093
|
"unmerge-cells",
|
|
23088
23094
|
{
|
|
23089
|
-
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
|
|
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 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).",
|
|
23090
23096
|
inputSchema: {
|
|
23091
23097
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
23092
23098
|
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.11",
|
|
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
|
}
|