apple-numbers-mcp 1.1.5 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -12,7 +12,7 @@ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that e
12
12
  [![MCP](https://img.shields.io/badge/MCP-server-blue)](https://modelcontextprotocol.io)
13
13
 
14
14
  <p align="center">
15
- <img src="codex/assets/screenshot.png" alt="Apple Numbers MCP — read, write, search, and format spreadsheets from Codex, Claude, and other AI assistants" width="680">
15
+ <img src="https://raw.githubusercontent.com/sweetrb/apple-numbers-mcp/main/codex/assets/screenshot.png" alt="Apple Numbers MCP — read, write, search, and format spreadsheets from Codex, Claude, and other AI assistants" width="680">
16
16
  </p>
17
17
 
18
18
  ## What is This?
@@ -41,6 +41,12 @@ Install the sweetrb/apple-numbers-mcp MCP server so you can help me work with my
41
41
 
42
42
  Claude will handle the installation and configuration automatically. After install, you'll need to install `numbers-parser` (Python) — see [Requirements](#requirements) below.
43
43
 
44
+ Or register it yourself in one deterministic line (no clone needed):
45
+
46
+ ```bash
47
+ claude mcp add apple-numbers -s user -- npx -y apple-numbers-mcp
48
+ ```
49
+
44
50
  ### Using the Plugin Marketplace
45
51
 
46
52
  Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
@@ -52,6 +58,8 @@ Install as a Claude Code plugin for automatic configuration and enhanced AI beha
52
58
 
53
59
  This method also installs a **skill** that teaches Claude when and how to use Apple Numbers effectively.
54
60
 
61
+ The plugin runs straight from its marketplace clone under `~/.claude/plugins/` (the clone lives at `~/.claude/plugins/marketplaces/apple-numbers-mcp/`). On the **first tool call** the server auto-bootstraps a project-local Python venv inside that directory with `numbers-parser` — a one-time setup that takes about a minute and **requires Python >= 3.11 on PATH** (stock macOS ships 3.9 — `brew install python@3.12` first). To pre-warm it instead of waiting on the first call, run `scripts/setup.sh` inside that install directory.
62
+
55
63
  ### Using the Codex Marketplace
56
64
 
57
65
  Install the same public marketplace in Codex:
@@ -71,14 +79,14 @@ numbers-parser`); see [Requirements](#requirements).
71
79
 
72
80
  Configuration for two more hosts is included — each registers the same `apple-numbers` MCP server (`npx -y apple-numbers-mcp`). All paths still need the `numbers-parser` Python sidecar available; see [Requirements](#requirements).
73
81
 
74
- - **[Hermes Agent](https://hermes-agent.nousresearch.com/)** (NousResearch) — Hermes has no plugin/marketplace drop-in. Add the server with `hermes mcp add apple-numbers --command npx --args -y apple-numbers-mcp`, or merge [`.hermes-plugin/config.yaml`](.hermes-plugin/config.yaml) into `~/.hermes/config.yaml`. Details: [`.hermes-plugin/README.md`](.hermes-plugin/README.md).
75
- - **[Antigravity](https://antigravity.google/)** (Google) — add the server entry from [`.antigravity-plugin/mcp_config.json`](.antigravity-plugin/mcp_config.json) to `~/.gemini/config/mcp_config.json` (or via Antigravity's MCP settings).
82
+ - **[Hermes Agent](https://hermes-agent.nousresearch.com/)** (NousResearch) — Hermes has no plugin/marketplace drop-in. Add the server with `hermes mcp add apple-numbers --command npx --args -y apple-numbers-mcp`, or merge [`.hermes-plugin/config.yaml`](https://github.com/sweetrb/apple-numbers-mcp/blob/main/.hermes-plugin/config.yaml) into `~/.hermes/config.yaml`. Details: [`.hermes-plugin/README.md`](https://github.com/sweetrb/apple-numbers-mcp/blob/main/.hermes-plugin/README.md).
83
+ - **[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).
76
84
 
77
85
  ### Manual Installation
78
86
 
79
- **1. Install the server:**
87
+ **1. Install the server** (from the npm registry):
80
88
  ```bash
81
- npm install -g github:sweetrb/apple-numbers-mcp
89
+ npm install -g apple-numbers-mcp
82
90
  ```
83
91
 
84
92
  **2. Install numbers-parser** (the Python library this server depends on):
@@ -130,10 +138,10 @@ The entrypoint is written as:
130
138
 
131
139
  ## Requirements
132
140
 
133
- - **macOS or Linux** — `numbers-parser` reads the file format directly, so most tools work anywhere. **Formatting and formula tools require macOS with Numbers.app.**
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.**
134
142
  - **Node.js 20+** — Required for the MCP server
135
143
  - **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`).
136
- - **Automation permission (writes only)** — Reads and exports need no special permission, but write/format tools 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](docs/AUTOMATION-PERMISSION.md). Run the **`doctor`** tool to verify your setup.
144
+ - **Automation permission (writes only)** — Reads and exports need no special permission, but write/format tools 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.
137
145
 
138
146
  ## Features
139
147
 
@@ -179,7 +187,7 @@ The entrypoint is written as:
179
187
  |---------|-------------|
180
188
  | **Import CSV/TSV/JSON** | Convert a tabular file into a new `.numbers` spreadsheet |
181
189
  | **Health Check** | Verify Python 3 and numbers-parser are installed |
182
- | **Doctor** | Richer setup diagnostic — read sidecar, Numbers.app, and Automation permission, each reported ok / warn / fail with actionable advice |
190
+ | **Doctor** | Richer setup diagnostic — Python interpreter (path + version), read sidecar, Numbers.app, and Automation permission, each reported ok / warn / fail with actionable advice |
183
191
 
184
192
  All tools also return **structured JSON** (`structuredContent`) alongside the human-readable text, so agents can consume results without parsing prose.
185
193
 
@@ -210,11 +218,11 @@ Verify Python 3 and `numbers-parser` are installed and reachable from the server
210
218
 
211
219
  #### `doctor`
212
220
 
213
- Run a full setup diagnostic with three separate checks: `numbers_parser` (the read sidecar — required for all reads/exports), `numbers_app` (Numbers.app present — required for write/format tools), and `automation_permission` (an informational reminder that write 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.
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 write/format tools), and `automation_permission` (an informational reminder that write 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.
214
222
 
215
223
  **Parameters:** None
216
224
 
217
- **Returns:** A per-check report. The `structuredContent` carries the raw `{ healthy, checks[] }`, where each check has `name`, `status` (`ok`/`warn`/`fail`), and `detail`. Reads don't need Automation permission, but writes do — see [docs/AUTOMATION-PERMISSION.md](docs/AUTOMATION-PERMISSION.md).
225
+ **Returns:** A per-check report. The `structuredContent` carries the raw `{ healthy, checks[] }`, where each check has `name`, `status` (`ok`/`warn`/`fail`), and `detail`. Reads don't need Automation permission, but writes do — see [docs/AUTOMATION-PERMISSION.md](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/AUTOMATION-PERMISSION.md).
218
226
 
219
227
  ---
220
228
 
@@ -595,7 +603,7 @@ AI: [calls import-csv with inputPath, outputPath]
595
603
  ### npm (Recommended)
596
604
 
597
605
  ```bash
598
- npm install -g github:sweetrb/apple-numbers-mcp
606
+ npm install -g apple-numbers-mcp
599
607
  pip3 install numbers-parser
600
608
  ```
601
609
 
@@ -609,6 +617,8 @@ pnpm run setup # creates ./venv and installs numbers-parser
609
617
  pnpm run build
610
618
  ```
611
619
 
620
+ Installing the repo directly from GitHub (`npm install -g github:sweetrb/apple-numbers-mcp`) also counts as a from-source install: it runs the repo's build on your machine and **requires pnpm**. Prefer the published npm package above unless you specifically want unreleased code.
621
+
612
622
  If installed from source, use this configuration:
613
623
  ```json
614
624
  {
@@ -683,7 +693,7 @@ This is the same Python-sidecar pattern used by [apple-photos-mcp](https://githu
683
693
  ## Known Limitations
684
694
 
685
695
  For the full rundown — the read-vs-write split, AppleScript-only formulas/styles,
686
- indexing, dates, format lag, and concurrent edits — see **[docs/LIMITATIONS.md](docs/LIMITATIONS.md)**.
696
+ indexing, dates, format lag, and concurrent edits — see **[docs/LIMITATIONS.md](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/LIMITATIONS.md)**.
687
697
  The summary below is the quick version.
688
698
 
689
699
  | Limitation | Reason |
@@ -702,8 +712,10 @@ These are tracked for future releases. The underlying `numbers-parser` library h
702
712
 
703
713
  ## Troubleshooting
704
714
 
705
- ### "numbers-parser not installed. Run: npm run setup"
706
- - Run `pip3 install numbers-parser` (global) or `npm run setup` (project-local venv).
715
+ ### "numbers-parser not installed"
716
+ - **Most common cause: your `python3` is older than 3.11** — stock macOS ships Python 3.9, which `numbers-parser` can't run on. Install a newer Python (`brew install python@3.12`), then simply retry the tool call: the venv rebuilds automatically.
717
+ - Otherwise, install the library directly: `pip3 install numbers-parser` (global Python), or run `scripts/setup.sh` from a repo checkout (`pnpm run setup`) to build the project-local venv.
718
+ - Run the **`doctor`** tool — it reports the resolved Python interpreter path and version, so a too-old stock Python is visible at a glance.
707
719
  - If you used a virtualenv, make sure it's the one at `./venv/` in the project directory.
708
720
 
709
721
  ### `apple-numbers` server fails to connect when run from a clone
@@ -719,7 +731,7 @@ These are tracked for future releases. The underlying `numbers-parser` library h
719
731
  ### Formatting / formula tools fail with "Numbers.app not running" or "Not authorized to send Apple events to Numbers"
720
732
  - Open Numbers.app at least once. macOS will prompt for automation permission — accept it.
721
733
  - Verify in **System Settings → Privacy & Security → Automation**, or reset with `tccutil reset AppleEvents`.
722
- - Run the **`doctor`** tool to confirm your setup — it reports the Numbers.app and Automation-permission checks separately. See the [Automation Permission guide](docs/AUTOMATION-PERMISSION.md). Reads don't need this permission; only writes do.
734
+ - Run the **`doctor`** tool to confirm your setup — it reports the Numbers.app and Automation-permission checks separately. See the [Automation Permission guide](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/AUTOMATION-PERMISSION.md). Reads don't need this permission; only writes do.
723
735
 
724
736
  ### Output looks wrong (dates as ISO, floats with decimals)
725
737
  - The server normalizes dates to ISO 8601 and rounds floats that are within `1e-9` of an integer. If you want raw values, file an issue.
@@ -766,7 +778,7 @@ A software consulting, contracting, and development company.
766
778
 
767
779
  ## License
768
780
 
769
- MIT License - see [LICENSE](LICENSE) for details. This project is not affiliated with Apple Inc. or the [numbers-parser](https://pypi.org/project/numbers-parser/) project.
781
+ MIT License - see [LICENSE](https://github.com/sweetrb/apple-numbers-mcp/blob/main/LICENSE) for details. This project is not affiliated with Apple Inc. or the [numbers-parser](https://pypi.org/project/numbers-parser/) project.
770
782
 
771
783
  ## Contributing
772
784
 
@@ -783,4 +795,4 @@ Part of a family of macOS MCP servers:
783
795
 
784
796
  ## Recurring macOS permission prompts
785
797
 
786
- If macOS keeps re-prompting for Full Disk Access or Automation for `node` (often after a `brew upgrade`), see [docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md](docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md) — the fix is to run this server under the official, Developer-ID-signed Node so the grant survives Node updates.
798
+ If macOS keeps re-prompting for Full Disk Access or Automation for `node` (often after a `brew upgrade`), see [docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md](https://github.com/sweetrb/apple-numbers-mcp/blob/main/docs/NODE-RUNTIME-AND-TCC-PERMISSIONS.md) — the fix is to run this server under the official, Developer-ID-signed Node so the grant survives Node updates.
package/build/index.js CHANGED
@@ -21421,7 +21421,7 @@ function findSystemPython() {
21421
21421
  }
21422
21422
  }
21423
21423
  throw new Error(
21424
- 'Python 3 not found. Run "npm run setup" to create a venv, or ensure python3 is on PATH.'
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. See https://github.com/sweetrb/apple-numbers-mcp#troubleshooting"
21425
21425
  );
21426
21426
  }
21427
21427
  function resolvePython() {
@@ -21496,7 +21496,7 @@ function looksLikeMissingDep(message) {
21496
21496
  return /not installed|No module named|ModuleNotFoundError/i.test(message);
21497
21497
  }
21498
21498
  function setupHint() {
21499
- return `Run: npm run setup (or set ${ENV_PREFIX}_NO_AUTO_SETUP=0 to allow automatic setup).`;
21499
+ return `Install it with: pip3 install numbers-parser (requires 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 (set ${ENV_PREFIX}_NO_AUTO_SETUP=0 to allow automatic setup).`;
21500
21500
  }
21501
21501
  var DEFAULT_MAX_BUFFER_BYTES = 50 * 1024 * 1024;
21502
21502
  function getMaxBuffer() {
@@ -21529,12 +21529,27 @@ function execReader(command, args, timeoutMs) {
21529
21529
  } catch (err) {
21530
21530
  const error2 = err;
21531
21531
  const stderr = error2.stderr?.toString().trim() ?? "";
21532
- if (stderr.includes(`${PACKAGE} not installed`) || looksLikeMissingDep(stderr)) {
21532
+ const stdout = error2.stdout?.toString().trim() ?? "";
21533
+ let sidecarError = null;
21534
+ if (stdout) {
21535
+ try {
21536
+ const parsed = JSON.parse(stdout);
21537
+ if (typeof parsed === "object" && parsed !== null && typeof parsed.error === "string") {
21538
+ sidecarError = parsed.error;
21539
+ }
21540
+ } catch {
21541
+ }
21542
+ }
21543
+ const missingDep = (s) => s.includes(`${PACKAGE} not installed`) || looksLikeMissingDep(s);
21544
+ if (sidecarError !== null && missingDep(sidecarError) || missingDep(stderr)) {
21533
21545
  return { error: `${PACKAGE} not installed. ${setupHint()}` };
21534
21546
  }
21535
21547
  if (error2.message?.includes("ETIMEDOUT") || error2.message?.includes("timed out")) {
21536
21548
  return { error: `Operation timed out after ${timeoutMs}ms. File may be very large.` };
21537
21549
  }
21550
+ if (sidecarError !== null) {
21551
+ return { error: sidecarError };
21552
+ }
21538
21553
  if (stderr) {
21539
21554
  return { error: stderr };
21540
21555
  }
@@ -21551,6 +21566,18 @@ function runNumbersReader(command, args, timeoutMs = 3e4) {
21551
21566
  }
21552
21567
  return result;
21553
21568
  }
21569
+ function getPythonInfo() {
21570
+ try {
21571
+ const python = resolvePython();
21572
+ const version3 = execFileSync(python, ["--version"], {
21573
+ encoding: "utf-8",
21574
+ stdio: ["pipe", "pipe", "pipe"]
21575
+ }).trim();
21576
+ return { path: python, version: version3 };
21577
+ } catch {
21578
+ return null;
21579
+ }
21580
+ }
21554
21581
  function checkDependencies() {
21555
21582
  ensureReady();
21556
21583
  try {
@@ -22067,18 +22094,42 @@ import { existsSync as existsSync3 } from "node:fs";
22067
22094
  var NUMBERS_APP_PATHS = ["/Applications/Numbers.app", "/System/Applications/Numbers.app"];
22068
22095
  function runDoctor(_manager) {
22069
22096
  const checks = [];
22097
+ try {
22098
+ const info = getPythonInfo();
22099
+ if (info) {
22100
+ const m = /Python (\d+)\.(\d+)/.exec(info.version);
22101
+ const tooOld = m !== null && (Number(m[1]) < 3 || Number(m[1]) === 3 && Number(m[2]) < 11);
22102
+ checks.push({
22103
+ name: "python_interpreter",
22104
+ status: tooOld ? "warn" : "ok",
22105
+ detail: tooOld ? `${info.version} at ${info.path} \u2014 numbers-parser requires Python >= 3.11 (stock macOS ships 3.9). Install a newer Python (brew install python@3.12) and retry \u2014 the venv rebuilds automatically. See https://github.com/sweetrb/apple-numbers-mcp#troubleshooting` : `${info.version} (${info.path})`
22106
+ });
22107
+ } else {
22108
+ checks.push({
22109
+ name: "python_interpreter",
22110
+ status: "fail",
22111
+ detail: "Python 3 not found on PATH. Install Python >= 3.11 (e.g. brew install python@3.12). See https://github.com/sweetrb/apple-numbers-mcp#troubleshooting"
22112
+ });
22113
+ }
22114
+ } catch (e) {
22115
+ checks.push({
22116
+ name: "python_interpreter",
22117
+ status: "warn",
22118
+ detail: `could not resolve the Python interpreter: ${String(e)}`
22119
+ });
22120
+ }
22070
22121
  try {
22071
22122
  const dep = checkDependencies();
22072
22123
  checks.push({
22073
22124
  name: "numbers_parser",
22074
22125
  status: dep.ok ? "ok" : "fail",
22075
- detail: dep.ok ? dep.message : `${dep.message} Run: npm run setup`
22126
+ detail: dep.message
22076
22127
  });
22077
22128
  } catch (e) {
22078
22129
  checks.push({
22079
22130
  name: "numbers_parser",
22080
22131
  status: "fail",
22081
- detail: `could not verify numbers-parser: ${String(e)}. Run: npm run setup`
22132
+ detail: `could not verify numbers-parser: ${String(e)}. ${setupHint()}`
22082
22133
  });
22083
22134
  }
22084
22135
  try {
@@ -22279,7 +22330,7 @@ server.registerTool(
22279
22330
  server.registerTool(
22280
22331
  "doctor",
22281
22332
  {
22282
- description: "Use when: a tool returns a permission or setup error, or you want the full setup diagnostic before writing/formatting.\nReturns: three checks \u2014 numbers-parser (read sidecar), Numbers.app (needed for writes), 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.",
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 writes), 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.",
22283
22334
  inputSchema: {},
22284
22335
  outputSchema: {
22285
22336
  healthy: external_exports.boolean().optional(),
@@ -0,0 +1,147 @@
1
+ # Automation Permission for Write & Format Operations
2
+
3
+ Apple Numbers MCP has **two distinct backends**, and only one of them needs a
4
+ special macOS permission:
5
+
6
+ - **Reads** go through [numbers-parser](https://pypi.org/project/numbers-parser/)
7
+ (Python). They open the `.numbers` file **directly** off disk and need **no**
8
+ special permission — just normal file access.
9
+ - **Writes and formatting** go through **AppleScript driving Numbers.app**.
10
+ numbers-parser can't write styles or formulas, so the server scripts Numbers.app
11
+ to do it. macOS gates one app controlling another behind the **Automation**
12
+ permission, so these tools require it.
13
+
14
+ This page explains which tools need Automation permission, how the prompt appears,
15
+ how to grant or reset it, what failure looks like, and how to verify.
16
+
17
+ ## Which tools need Automation permission
18
+
19
+ **Need it** (AppleScript → Numbers.app — writes, formulas, formatting, structure
20
+ changes, CSV import, file creation):
21
+
22
+ - `set-cell`, `set-cells-batch`
23
+ - `add-rows`, `update-rows`, `delete-rows`
24
+ - `add-sheet`, `add-table`, `rename-sheet`, `rename-table`
25
+ - `set-formula`, `set-formulas-batch`
26
+ - `set-cell-style`, `set-cells-style-batch`
27
+ - `set-column-width`, `set-row-height`
28
+ - `merge-cells`, `unmerge-cells`
29
+ - `create-spreadsheet`
30
+ - `import-csv`
31
+
32
+ **Do NOT need it** (pure numbers-parser, file read directly off disk):
33
+
34
+ - `get-file-info`
35
+ - `read-table`
36
+ - `get-cell`
37
+ - `search`
38
+ - `export-table`
39
+ - `health-check` / `doctor` (these only probe the environment)
40
+
41
+ So you can fully inspect, search, and export a spreadsheet with no Automation
42
+ permission at all. The permission only matters the moment you try to **change or
43
+ format** a file.
44
+
45
+ > **Numbers.app must be installed.** The write/format path scripts Numbers.app, so
46
+ > the app has to be present at `/Applications/Numbers.app` (or
47
+ > `/System/Applications/Numbers.app`). Reads don't need it. If Numbers.app is
48
+ > missing, `doctor` reports `numbers_app: warn` and the write tools fail.
49
+
50
+ ## How the permission prompt appears
51
+
52
+ The **first time** a write/format tool runs, macOS shows a one-time dialog:
53
+
54
+ > **"<Host App>" wants access to control "Numbers". Allowing control will provide
55
+ > access to documents and data in "Numbers", and to perform actions within that
56
+ > app.**
57
+
58
+ Click **OK / Allow**. The grant is remembered, so subsequent writes don't prompt
59
+ again. The "host app" is whatever process launched the MCP server — Claude
60
+ Desktop, Terminal, iTerm, or VS Code — **not** `node` and **not** Numbers.app.
61
+
62
+ If the host app runs headless or the dialog is dismissed/denied, the write fails
63
+ (see below) and you must grant the permission manually.
64
+
65
+ ## How to grant it manually
66
+
67
+ 1. Open **System Settings** (or **System Preferences** on older macOS).
68
+ 2. Go to **Privacy & Security → Automation**.
69
+ 3. Find the **host app** that runs the MCP server (Claude, Terminal, iTerm, or
70
+ VS Code) in the list.
71
+ 4. Expand it and enable the toggle next to **Numbers**.
72
+
73
+ | Host | App to allow |
74
+ |------|--------------|
75
+ | Claude Desktop | `Claude.app` |
76
+ | Claude Code in Terminal | `Terminal.app` |
77
+ | Claude Code in iTerm | `iTerm.app` |
78
+ | Claude Code in VS Code | `Visual Studio Code.app` |
79
+
80
+ > **Grant it to the right app.** Automation permission applies to the process that
81
+ > *spawns* the server, not to `node` or to Numbers.app. If you launch Claude Code
82
+ > from iTerm, allow iTerm to control Numbers; if you use Claude Desktop, allow
83
+ > Claude. Granting it to the wrong app has no effect.
84
+
85
+ ## What failure looks like
86
+
87
+ When the host app lacks Automation permission for Numbers, the AppleScript layer
88
+ fails and the write/format tool returns an error containing:
89
+
90
+ ```
91
+ Not authorized to send Apple events to Numbers.
92
+ ```
93
+
94
+ (or, equivalently, an `errAEEventNotPermitted` / `-1743` error.) You may also see
95
+ `"Numbers got an error: ..."` style messages if Numbers.app itself can't be
96
+ driven. In all of these cases the underlying `.numbers` file is **not** modified.
97
+
98
+ If you instead see **"Numbers.app not running"** or a message that Numbers.app
99
+ can't be found, the problem is that the app isn't installed/openable — open
100
+ Numbers.app once and retry.
101
+
102
+ ## Resetting the permission
103
+
104
+ If the grant got into a bad state (e.g. you clicked **Don't Allow** the first
105
+ time, and macOS now won't re-prompt), reset the Automation permissions and let the
106
+ prompt appear again on the next write:
107
+
108
+ ```bash
109
+ tccutil reset AppleEvents
110
+ ```
111
+
112
+ This clears Apple-event (Automation) grants for **all** apps; macOS will re-prompt
113
+ on the next attempt. To be more surgical you can scope it to the host bundle id,
114
+ e.g.:
115
+
116
+ ```bash
117
+ tccutil reset AppleEvents com.apple.Terminal
118
+ ```
119
+
120
+ After resetting, **fully quit and reopen the host app**, then run any write tool
121
+ (e.g. `set-cell`) and click **OK** when the dialog appears.
122
+
123
+ ## Verifying
124
+
125
+ Run the **`doctor`** tool — it's the richest diagnostic and reports four checks
126
+ as `ok` / `warn` / `fail`:
127
+
128
+ - **`python_interpreter`** — the resolved Python's path and version (warns when
129
+ older than 3.11; stock macOS ships 3.9).
130
+ - **`numbers_parser`** — is the Python read sidecar installed (powers all reads).
131
+ - **`numbers_app`** — is Numbers.app present (required for any write/format tool).
132
+ - **`automation_permission`** — an informational reminder that write tools need
133
+ Automation permission, granted on first use.
134
+
135
+ > **Note:** Automation permission can't be probed without actually trying to
136
+ > control Numbers (that would itself trigger the prompt), so `doctor` reports it
137
+ > **informationally** rather than testing it live. The definitive test is to run a
138
+ > real write — e.g. `set-cell` on a scratch file. If it succeeds, the permission
139
+ > is in place; if it returns *"Not authorized to send Apple events to Numbers,"*
140
+ > grant or reset it as above.
141
+
142
+ ## See also
143
+
144
+ - [docs/LIMITATIONS.md](./LIMITATIONS.md) — the full read-vs-write split and other
145
+ limits.
146
+ - [Known Limitations](../README.md#known-limitations) and
147
+ [Security and Privacy](../README.md#security-and-privacy) in the README.
@@ -0,0 +1,145 @@
1
+ # Limitations
2
+
3
+ Apple Numbers MCP is a **hybrid** bridge to `.numbers` spreadsheets: it **reads**
4
+ them offline with [numbers-parser](https://pypi.org/project/numbers-parser/)
5
+ (Python, no app required), and **writes/formats** them by scripting **Numbers.app**
6
+ via AppleScript (because numbers-parser can't write styles or formulas). That split
7
+ is the source of most of the limitations below.
8
+
9
+ This page documents the real limitations — what the server can't do and why — so
10
+ they aren't re-investigated every release. These agree with the README's
11
+ [Known Limitations](../README.md#known-limitations); this page adds the *why* and
12
+ *what to do* for each.
13
+
14
+ ## Writes & formatting require Numbers.app + Automation permission (reads don't)
15
+
16
+ **Why:** numbers-parser can read a `.numbers` file directly off disk, but it does
17
+ **not** write styles, formulas, or cell-dimension changes reliably. To do those,
18
+ the server scripts **Numbers.app** through AppleScript. That means every
19
+ write/format tool needs (a) **Numbers.app installed** and (b) the host app to have
20
+ **Automation** permission to control Numbers. Read tools (`get-file-info`,
21
+ `read-table`, `get-cell`, `search`, `export-table`) use only numbers-parser and
22
+ need **neither** — they work on macOS or Linux with no Automation grant.
23
+
24
+ **What to do:** For inspection/search/export, nothing special is needed. For any
25
+ write — `set-cell(s)`, `add/update/delete-rows`, `add-sheet`/`add-table`,
26
+ `rename-*`, `set-formula(s)`, `set-*-style`, `set-column-width`/`set-row-height`,
27
+ `merge`/`unmerge`, `create-spreadsheet`, `import-csv` — install Numbers.app and
28
+ grant Automation permission. Full steps in
29
+ [AUTOMATION-PERMISSION.md](./AUTOMATION-PERMISSION.md). Verify with `doctor`.
30
+
31
+ ## Formulas and styling are AppleScript-only
32
+
33
+ **Why:** Writing a formula (`set-formula`, `set-formulas-batch`) or a style
34
+ (`set-cell-style`, `set-cells-style-batch`, `set-column-width`, `set-row-height`,
35
+ `merge-cells`/`unmerge-cells`) cannot be done through the Python read path —
36
+ numbers-parser doesn't expose reliable writes for these. They go exclusively
37
+ through Numbers.app via AppleScript, so they inherit the macOS-only + Automation
38
+ requirements above and can't run on Linux.
39
+
40
+ **What to do:** Run formula/format tools on macOS with Numbers.app. If you only
41
+ have the Python path available (e.g. Linux), you can still read formulas and
42
+ formatted values (via `get-cell verbose: true`) — you just can't set them.
43
+
44
+ ## Values vs. formulas
45
+
46
+ **Why:** The value-writing tools (`set-cell`, `set-cells-batch`, `add-rows`,
47
+ `update-rows`) write **computed values**, not formulas. Passing `"=SUM(...)"` as a
48
+ value writes the literal string, not a live formula.
49
+
50
+ **What to do:** Use `set-formula` / `set-formulas-batch` to write actual formulas
51
+ (these require Numbers.app). Use the value tools for plain data.
52
+
53
+ ## No charts, images, or conditional formatting
54
+
55
+ **Why:** numbers-parser doesn't expose charts, embedded images, or
56
+ conditional-formatting rules, and the AppleScript layer here doesn't implement
57
+ them either. There is no tool to create, read, or edit a chart, an image, or a
58
+ conditional-format rule.
59
+
60
+ **What to do:** Build charts / conditional formatting by hand in Numbers.app on the
61
+ data this server writes. The server can populate and format the underlying cells;
62
+ the visualization layer stays manual.
63
+
64
+ ## Sheet deletion is not supported
65
+
66
+ **Why:** numbers-parser doesn't expose sheet removal, so there's no `delete-sheet`
67
+ tool. (Rows can be deleted with `delete-rows`; whole sheets cannot.)
68
+
69
+ **What to do:** Delete a sheet manually in Numbers.app.
70
+
71
+ ## Indexing is 0-based for rows and columns
72
+
73
+ **Why:** Every tool that takes a `row`/`col` (or `startRow`/`endRow`/`startCol`/
74
+ `endCol`) uses **0-based** indices — verified throughout `src/index.ts` (e.g.
75
+ `row: z.number().int().min(0).describe("Row index (0-based)")`). The header row is
76
+ **row 0**, so the first data row is **row 1**. This is why `read-table` defaults
77
+ `startRow` to `1` (i.e. "skip the header"). Ranges are **inclusive** on both ends
78
+ (e.g. `delete-rows startRow: 1 endRow: 3` deletes three rows).
79
+
80
+ **What to do:** Treat the header as row 0. A spreadsheet "row 5" that a human sees
81
+ in Numbers.app (1-based, header included) is index `4` here. When in doubt, call
82
+ `read-table` or `get-cell` first to confirm what's at a given index.
83
+
84
+ ## Dates are ISO 8601
85
+
86
+ **Why:** The Python layer normalizes all dates to **ISO 8601** on read
87
+ (`val.isoformat()`), collapsing a pure date with no time to `YYYY-MM-DD` and
88
+ keeping the full datetime otherwise. On write, a value typed as `date` is parsed
89
+ with `datetime.fromisoformat`, so date inputs must be ISO 8601 too.
90
+
91
+ **What to do:** Pass dates as `"2025-06-01"` or a full ISO datetime, and expect ISO
92
+ 8601 back. Locale-formatted dates (e.g. `"6/1/25"`) are not parsed as dates.
93
+
94
+ ## numbers-parser may lag the newest Numbers file format
95
+
96
+ **Why:** Apple periodically changes the `.numbers` on-disk format with new
97
+ Numbers.app releases. numbers-parser is a third-party library that has to catch up
98
+ to each new format version. A file saved by a very new Numbers.app can occasionally
99
+ read incompletely (or error) on an older numbers-parser.
100
+
101
+ **What to do:** Keep numbers-parser current (`pip3 install -U numbers-parser`, or
102
+ re-run `pnpm run setup`). If a read fails or looks wrong on a freshly-saved file,
103
+ upgrade numbers-parser first. Check the installed version with `health-check` /
104
+ `doctor`.
105
+
106
+ ## Concurrent edits while Numbers.app has the file open
107
+
108
+ **Why:** Two backends can touch the same file. The read path (numbers-parser) reads
109
+ whatever is on disk, and the write path drives Numbers.app. If Numbers.app has the
110
+ file **open with unsaved changes**, what numbers-parser reads off disk can be
111
+ stale, and a write through AppleScript targets the app's in-memory copy — the two
112
+ can disagree, and saves can race.
113
+
114
+ **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. If you must keep it open,
116
+ read back with `read-table` / `get-cell` after writes to confirm the result landed,
117
+ and avoid editing the same cells by hand in Numbers.app at the same time.
118
+
119
+ ## Concurrent writes to the same file are last-writer-wins
120
+
121
+ **Why:** Each write tool opens the `.numbers` file, applies its change to an
122
+ in-memory document, and saves the **whole document** back. The save itself is
123
+ **atomic** — every mutating command writes to a sibling temp file and `os.replace()`s
124
+ it onto the target — so an interrupted or crashed write can never leave a torn or
125
+ half-written file. But atomicity protects against *corruption*, **not** against
126
+ *lost updates*: if two writes to the same file overlap (two agents, two server
127
+ instances, or a server write racing a hand edit in Numbers.app), each loaded the
128
+ document independently, and whichever saves **last** overwrites the other's change
129
+ wholesale. There is no file locking, no merge, and no optimistic-concurrency check.
130
+
131
+ **What to do:** **Serialize writes to a given file** — don't fan out concurrent
132
+ mutations against the same `.numbers` file from multiple agents or sessions. Batch
133
+ related edits into a single call (`set-cells-batch`, `update-rows`,
134
+ `set-formulas-batch`) instead of many overlapping ones, finish one tool call before
135
+ starting the next on the same file, and after a burst of writes read back with
136
+ `read-table` / `get-cell` to confirm the final state is what you intended.
137
+
138
+ ## macOS-only for writes; reads are cross-platform
139
+
140
+ **Why:** Reads run anywhere numbers-parser runs (macOS or Linux). Writes/formatting
141
+ script Numbers.app, which only exists on macOS.
142
+
143
+ **What to do:** Run write/format workloads on a Mac with Numbers.app installed and
144
+ Automation permission granted. See
145
+ [AUTOMATION-PERMISSION.md](./AUTOMATION-PERMISSION.md).
@@ -0,0 +1,109 @@
1
+ # Node runtime & TCC permission stability
2
+
3
+ macOS gates this MCP server's access to your data behind **TCC** permissions —
4
+ **Full Disk Access** (to read app data such as Mail, Notes, or Photos) and
5
+ **Automation / Apple Events** (to drive an app like Mail.app or Notes.app via
6
+ AppleScript). See this repo's Full Disk Access / Automation notes for *which*
7
+ operations need which permission.
8
+
9
+ This page is about a **separate, recurring annoyance**: being asked to approve
10
+ those permissions **over and over**, often right after a routine `brew upgrade`.
11
+
12
+ ## Symptom
13
+
14
+ - You granted Full Disk Access (and/or Automation) to "node", but days later
15
+ macOS prompts again — `"node" wants access to ...` or `"node" wants to control
16
+ "Mail"`.
17
+ - System Settings → Privacy & Security → Full Disk Access shows **several
18
+ identical "node" rows**, usually only one enabled.
19
+ - It tends to happen immediately after you update Node.
20
+
21
+ ## Cause
22
+
23
+ TCC binds a permission grant to the **code identity of the binary that performs
24
+ the access** — here, the `node` executable that launches the MCP server. For a
25
+ binary that is only **ad-hoc signed** (no Developer ID / Team ID), TCC keys the
26
+ grant to the binary's **cdhash**, a hash of its contents.
27
+
28
+ Homebrew's `node` formula is ad-hoc signed:
29
+
30
+ ```bash
31
+ $ codesign -dvvv "$(which node)" 2>&1 | grep -E 'Signature|TeamIdentifier'
32
+ Signature=adhoc
33
+ TeamIdentifier=not set
34
+ ```
35
+
36
+ Every Node update **replaces the binary**, which **changes the cdhash**, so TCC
37
+ no longer recognizes it as the thing you approved — and re-prompts. The extra
38
+ "node" rows are stale cdhashes from previous versions.
39
+
40
+ By contrast, properly signed apps (Chrome, Slack, …) keep their permissions
41
+ across auto-updates because TCC matches them on a stable **Designated
42
+ Requirement** derived from their Developer ID, not on the cdhash.
43
+
44
+ ## Fix: run the MCP under the official, Developer-ID-signed Node
45
+
46
+ Node binaries distributed from **nodejs.org** are signed with a real Developer
47
+ ID (`Node.js Foundation`, Team `HX7739G8FX`), notarized, and self-contained.
48
+ Pointing the MCP server at one gives TCC a **stable** identity to match, so a
49
+ permission you grant **persists across future Node updates**. It also decouples
50
+ the MCP runtime from your Homebrew/dev Node, which can keep updating freely.
51
+
52
+ ### Steps (Apple Silicon shown; use `darwin-x64` on Intel)
53
+
54
+ 1. Install a current LTS to a stable path (kept off `PATH` so it won't shadow
55
+ your dev Node):
56
+
57
+ ```bash
58
+ VER=v24.17.0 ARCH=darwin-arm64
59
+ mkdir -p ~/mcp-runtime && cd ~/mcp-runtime
60
+ curl -O https://nodejs.org/dist/$VER/node-$VER-$ARCH.tar.gz
61
+ curl -O https://nodejs.org/dist/$VER/SHASUMS256.txt
62
+ grep " node-$VER-$ARCH.tar.gz$" SHASUMS256.txt | shasum -a 256 -c - # must print OK
63
+ tar -xzf node-$VER-$ARCH.tar.gz
64
+ ln -sfn node-$VER-$ARCH node-current
65
+ ```
66
+
67
+ 2. Confirm it's Developer-ID signed:
68
+
69
+ ```bash
70
+ codesign -dvvv ~/mcp-runtime/node-current/bin/node 2>&1 | grep -E 'Authority=Developer ID|TeamIdentifier'
71
+ # Authority=Developer ID Application: Node.js Foundation (HX7739G8FX)
72
+ # TeamIdentifier=HX7739G8FX
73
+ ```
74
+
75
+ 3. Point this MCP server's launcher at it. For Claude Desktop, edit
76
+ `~/Library/Application Support/Claude/claude_desktop_config.json` and set this
77
+ server's `command` to the absolute path:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "apple-numbers": {
83
+ "command": "/Users/<you>/mcp-runtime/node-current/bin/node",
84
+ "args": ["/path/to/apple-numbers-mcp/build/index.js"]
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ Servers launched via `npx` that don't need Full Disk Access can stay on
91
+ Homebrew Node.
92
+
93
+ 4. **Restart your MCP client** so the server relaunches under the new Node.
94
+
95
+ 5. **Grant the permissions once** to the new binary:
96
+ - *Full Disk Access*: System Settings → Privacy & Security → Full Disk Access
97
+ → **+** → ⌘⇧G → paste `~/mcp-runtime/node-current/bin/node`.
98
+ - *Automation*: the first time the server drives an app you'll get a one-time
99
+ `"node" wants to control "<App>"` prompt — click **Allow**.
100
+
101
+ Both grants are keyed to the official Node's stable signature, so you should
102
+ not be asked again — including after future Node updates. You can delete the
103
+ stale "node" rows from the Full Disk Access list.
104
+
105
+ ### Updating the dedicated Node later
106
+
107
+ Drop a newer official LTS tarball into `~/mcp-runtime/`, repoint the
108
+ `node-current` symlink, and restart your client. The signing identity is
109
+ unchanged, so existing grants carry over — no re-approval.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-numbers-mcp",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
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",
@@ -12,6 +12,7 @@
12
12
  "src/utils/numbers_reader.py",
13
13
  "scripts/setup.sh",
14
14
  "requirements.txt",
15
+ "docs",
15
16
  "README.md",
16
17
  "LICENSE"
17
18
  ],
package/scripts/setup.sh CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  #
3
3
  # Create a Python venv and install numbers-parser.
4
- # Run via: npm run setup
4
+ # Run via: pnpm run setup (or: bash scripts/setup.sh)
5
5
  #
6
6
  set -euo pipefail
7
7
 
@@ -32,7 +32,7 @@ echo " Using: $PYTHON ($($PYTHON --version))"
32
32
  if ! "$PYTHON" -c 'import sys; sys.exit(0 if sys.version_info >= (3, 11) else 1)'; then
33
33
  echo "ERROR: Python >= 3.11 required, but '$PYTHON' is $($PYTHON --version 2>&1)." >&2
34
34
  echo " macOS ships Python 3.9. Install a newer one, e.g.:" >&2
35
- echo " brew install python@3.12 # then re-run: npm run setup" >&2
35
+ echo " brew install python@3.12 # then re-run: scripts/setup.sh" >&2
36
36
  echo " or from https://www.python.org/downloads/" >&2
37
37
  exit 1
38
38
  fi
@@ -14,7 +14,12 @@ try:
14
14
  from numbers_parser import Document
15
15
  except ImportError:
16
16
  print(json.dumps({
17
- "error": "numbers-parser not installed. Run: npm run setup"
17
+ "error": (
18
+ "numbers-parser not installed. Install it with: pip3 install numbers-parser "
19
+ "(requires Python >= 3.11; stock macOS ships 3.9 - brew install python@3.12), "
20
+ "or run scripts/setup.sh from a repo checkout. Run the doctor tool to diagnose. "
21
+ "See https://github.com/sweetrb/apple-numbers-mcp#troubleshooting"
22
+ )
18
23
  }))
19
24
  sys.exit(1)
20
25