apple-numbers-mcp 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +709 -0
  3. package/build/index.d.ts +3 -0
  4. package/build/index.d.ts.map +1 -0
  5. package/build/index.js +483 -0
  6. package/build/index.js.map +1 -0
  7. package/build/services/fileConfig.d.ts +7 -0
  8. package/build/services/fileConfig.d.ts.map +1 -0
  9. package/build/services/fileConfig.js +52 -0
  10. package/build/services/fileConfig.js.map +1 -0
  11. package/build/services/numbersManager.d.ts +175 -0
  12. package/build/services/numbersManager.d.ts.map +1 -0
  13. package/build/services/numbersManager.js +357 -0
  14. package/build/services/numbersManager.js.map +1 -0
  15. package/build/tools/doctor.d.ts +22 -0
  16. package/build/tools/doctor.d.ts.map +1 -0
  17. package/build/tools/doctor.js +96 -0
  18. package/build/tools/doctor.js.map +1 -0
  19. package/build/tools/resourcesAndPrompts.d.ts +15 -0
  20. package/build/tools/resourcesAndPrompts.d.ts.map +1 -0
  21. package/build/tools/resourcesAndPrompts.js +77 -0
  22. package/build/tools/resourcesAndPrompts.js.map +1 -0
  23. package/build/tools/respond.d.ts +29 -0
  24. package/build/tools/respond.d.ts.map +1 -0
  25. package/build/tools/respond.js +44 -0
  26. package/build/tools/respond.js.map +1 -0
  27. package/build/types.d.ts +128 -0
  28. package/build/types.d.ts.map +1 -0
  29. package/build/types.js +2 -0
  30. package/build/types.js.map +1 -0
  31. package/build/utils/applescript.d.ts +90 -0
  32. package/build/utils/applescript.d.ts.map +1 -0
  33. package/build/utils/applescript.js +248 -0
  34. package/build/utils/applescript.js.map +1 -0
  35. package/build/utils/python.d.ts +12 -0
  36. package/build/utils/python.d.ts.map +1 -0
  37. package/build/utils/python.js +101 -0
  38. package/build/utils/python.js.map +1 -0
  39. package/package.json +91 -0
  40. package/requirements.txt +1 -0
  41. package/scripts/setup.sh +46 -0
  42. package/src/utils/numbers_reader.py +779 -0
package/README.md ADDED
@@ -0,0 +1,709 @@
1
+ # Apple Numbers MCP Server
2
+
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that enables AI assistants like Claude to read, write, search, and modify Apple Numbers (`.numbers`) spreadsheet files. Backed by the [numbers-parser](https://pypi.org/project/numbers-parser/) Python library.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/apple-numbers-mcp)](https://www.npmjs.com/package/apple-numbers-mcp)
6
+ [![CI](https://github.com/sweetrb/apple-numbers-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/sweetrb/apple-numbers-mcp/actions/workflows/ci.yml)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
8
+
9
+ ## What is This?
10
+
11
+ This server acts as a bridge between AI assistants and Apple Numbers spreadsheets. Once configured, you can ask Claude (or any MCP-compatible AI) to:
12
+
13
+ - "What's in this spreadsheet?" — inspect sheets, tables, dimensions
14
+ - "Find every row where the customer is Acme Corp"
15
+ - "Export the Q3 Results table to CSV"
16
+ - "Set cell B5 to =SUM(B2:B4)"
17
+ - "Append these three rows to the Inventory table"
18
+ - "Bold and center the header row"
19
+ - "Import this CSV into a new spreadsheet"
20
+
21
+ The AI assistant communicates with this server, which uses [numbers-parser](https://pypi.org/project/numbers-parser/) to read and write `.numbers` files directly (no Numbers.app required for most operations). All data stays local on your machine.
22
+
23
+ ## Quick Start
24
+
25
+ ### Using Claude Code (Easiest)
26
+
27
+ If you're using [Claude Code](https://claude.com/product/claude-code) (in Terminal or VS Code), just ask Claude to install it:
28
+
29
+ ```
30
+ Install the sweetrb/apple-numbers-mcp MCP server so you can help me work with my Numbers spreadsheets
31
+ ```
32
+
33
+ Claude will handle the installation and configuration automatically. After install, you'll need to install `numbers-parser` (Python) — see [Requirements](#requirements) below.
34
+
35
+ ### Using the Plugin Marketplace
36
+
37
+ Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
38
+
39
+ ```bash
40
+ /plugin marketplace add sweetrb/apple-numbers-mcp
41
+ /plugin install apple-numbers
42
+ ```
43
+
44
+ This method also installs a **skill** that teaches Claude when and how to use Apple Numbers effectively.
45
+
46
+ ### Manual Installation
47
+
48
+ **1. Install the server:**
49
+ ```bash
50
+ npm install -g github:sweetrb/apple-numbers-mcp
51
+ ```
52
+
53
+ **2. Install numbers-parser** (the Python library this server depends on):
54
+ ```bash
55
+ pip3 install numbers-parser
56
+ ```
57
+
58
+ Or, if you cloned the repo, run `npm run setup` to create a project-local Python venv with `numbers-parser` pre-installed.
59
+
60
+ **3. Add to Claude Desktop** (`~/Library/Application Support/Claude/claude_desktop_config.json`):
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "apple-numbers": {
65
+ "command": "npx",
66
+ "args": ["apple-numbers-mcp"]
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ **4. Restart Claude Desktop** and start using natural language:
73
+ ```
74
+ "What sheets are in ~/Documents/budget.numbers?"
75
+ ```
76
+
77
+ #### Running from a clone in Claude Code (project-scope `.mcp.json`)
78
+
79
+ This repo ships a `.mcp.json` at its root so that, when you run `claude` from inside a clone, the server is registered automatically as a **project-scope** server — no manual config needed. Before launching, run **both**:
80
+
81
+ ```bash
82
+ npm run build # compile TypeScript to build/
83
+ npm run setup # create the ./venv the Python sidecar needs
84
+ ```
85
+
86
+ `npm run setup` is required because this is a Python-sidecar server: it shells out to `./venv/bin/python3` running `numbers-parser`. Without the venv, the server starts but every tool call fails. Then launch Claude Code from the repo directory and approve the server when prompted.
87
+
88
+ The entrypoint is written as:
89
+
90
+ ```json
91
+ "args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
92
+ ```
93
+
94
+ `CLAUDE_PROJECT_DIR` is the variable Claude Code injects into a project/user-scoped server's environment, and it resolves to the repo root. **You must launch `claude` from inside the repo** for this to work — the bare `.` fallback is only a last resort and is *not* reliable, because it resolves against the launching process's working directory, not the repo.
95
+
96
+ > **Why not `${CLAUDE_PLUGIN_ROOT}`?** `CLAUDE_PLUGIN_ROOT` is set **only** for marketplace plugin installs, never for a project-scope clone, so it can't drive the clone workflow. Conversely, a plugin install can't use `CLAUDE_PROJECT_DIR` (in a plugin, that points at the *user's* project, not the plugin's own directory). Claude Code does **not** support nested defaults like `${CLAUDE_PLUGIN_ROOT:-${CLAUDE_PROJECT_DIR:-.}}`, so a single entrypoint string cannot serve both contexts. The two distribution paths are therefore decoupled: the **plugin** carries its own MCP config in `.claude-plugin/plugin.json` (using `${CLAUDE_PLUGIN_ROOT}`), while the root `.mcp.json` is dedicated to the **clone** workflow (using `${CLAUDE_PROJECT_DIR:-.}`). Because `plugin.json` declares its own `mcpServers`, the plugin does not also auto-load the root `.mcp.json`, so there is no double-registration.
97
+
98
+ > **Heads-up on scope precedence:** project-scope (`.mcp.json`) outranks user-scope. If you *also* have an `apple-numbers` entry registered at user scope (e.g. an absolute path in `~/.claude.json`), the project-scope entry wins and the user-scope one is ignored entirely. Pick one — for local development on this repo, the project-scope `.mcp.json` is the intended source. To pin a specific local build instead, register it at **local** scope (`claude mcp add apple-numbers -s local -- node /abs/path/build/index.js`), which outranks project scope.
99
+
100
+ ## Requirements
101
+
102
+ - **macOS or Linux** — `numbers-parser` reads the file format directly, so most tools work anywhere. **Formatting and formula tools require macOS with Numbers.app.**
103
+ - **Node.js 20+** — Required for the MCP server
104
+ - **Python 3.9+ with numbers-parser** — Install via `pip3 install numbers-parser` or via `npm run setup` if installing from source
105
+ - **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.
106
+
107
+ ## Features
108
+
109
+ ### Read
110
+
111
+ | Feature | Description |
112
+ |---------|-------------|
113
+ | **File Inspection** | List sheets, tables, dimensions, and header rows |
114
+ | **Table Read** | Read data with optional row range and column filtering |
115
+ | **Cell Read** | Single cells by 0-based index, with optional formula/format/merge metadata |
116
+ | **Search** | Case-insensitive text search across every cell, optionally scoped to one sheet |
117
+ | **Export** | Export a table to CSV, TSV, or JSON |
118
+
119
+ ### Write
120
+
121
+ | Feature | Description |
122
+ |---------|-------------|
123
+ | **Create Spreadsheet** | New `.numbers` file with headers and optional initial rows |
124
+ | **Set Cell** | Write a value to a single cell, with optional type coercion |
125
+ | **Set Cells Batch** | Write many cells in one operation (more efficient than multiple set-cell calls) |
126
+ | **Add / Update / Delete Rows** | Append, replace, or remove rows by index |
127
+ | **Sheets and Tables** | Add new sheets or tables to an existing file; rename either |
128
+
129
+ ### Formulas (requires Numbers.app)
130
+
131
+ | Feature | Description |
132
+ |---------|-------------|
133
+ | **Set Formula** | Write a formula like `=SUM(B2:B10)` to a cell |
134
+ | **Set Formulas Batch** | Write many formulas at once |
135
+
136
+ ### Formatting (requires Numbers.app)
137
+
138
+ | Feature | Description |
139
+ |---------|-------------|
140
+ | **Cell Styles** | Font, size, colors, number format, alignment |
141
+ | **Cell Styles Batch** | Style many cells at once |
142
+ | **Column Width / Row Height** | Set dimensions in pixels |
143
+ | **Merge / Unmerge** | Merge a range of cells, or undo a merge |
144
+
145
+ ### Import / Diagnostics
146
+
147
+ | Feature | Description |
148
+ |---------|-------------|
149
+ | **Import CSV/TSV/JSON** | Convert a tabular file into a new `.numbers` spreadsheet |
150
+ | **Health Check** | Verify Python 3 and numbers-parser are installed |
151
+ | **Doctor** | Richer setup diagnostic — read sidecar, Numbers.app, and Automation permission, each reported ok / warn / fail with actionable advice |
152
+
153
+ All tools also return **structured JSON** (`structuredContent`) alongside the human-readable text, so agents can consume results without parsing prose.
154
+
155
+ ### MCP resources & prompts
156
+
157
+ Resources expose read-only context the client can attach without a tool call:
158
+ the `numbers://file/{path}` template (file structure — sheets, tables, dimensions,
159
+ headers) and the `numbers://table/{path}` template (the default table's data).
160
+ Both are templated by a URL-encoded `.numbers` file path. Prompts package common
161
+ workflows: `analyze-spreadsheet`, `bulk-edit`, `import-csv-guide`.
162
+
163
+ ---
164
+
165
+ ## Tool Reference
166
+
167
+ This section documents all available tools. AI agents should use these tool names and parameters exactly as specified.
168
+
169
+ ### Read
170
+
171
+ #### `health-check`
172
+
173
+ Verify Python 3 and `numbers-parser` are installed and reachable from the server.
174
+
175
+ **Parameters:** None
176
+ **Returns:** `numbers-parser` version, or an error explaining how to install.
177
+
178
+ ---
179
+
180
+ #### `doctor`
181
+
182
+ 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.
183
+
184
+ **Parameters:** None
185
+
186
+ **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).
187
+
188
+ ---
189
+
190
+ #### `get-file-info`
191
+
192
+ Get the structure of a `.numbers` file: sheets, tables, dimensions, header rows.
193
+
194
+ | Parameter | Type | Required | Description |
195
+ |-----------|------|----------|-------------|
196
+ | `path` | string | Yes | Absolute or `~`-relative path to the `.numbers` file |
197
+
198
+ **Returns:** Default sheet name, plus each sheet with its tables (name, dimensions, header row).
199
+
200
+ ---
201
+
202
+ #### `read-table`
203
+
204
+ Read data from a table. Returns headers and rows. Defaults to the first sheet and first table if not specified.
205
+
206
+ | Parameter | Type | Required | Description |
207
+ |-----------|------|----------|-------------|
208
+ | `path` | string | Yes | Path to the `.numbers` file |
209
+ | `sheet` | string | No | Sheet name (default: first sheet) |
210
+ | `table` | string | No | Table name (default: first table) |
211
+ | `startRow` | number | No | 0-based start row, inclusive (default: 1, after header) |
212
+ | `endRow` | number | No | 0-based end row, inclusive (default: last row) |
213
+ | `columns` | (string \| number)[] | No | Column filter: header names or 0-based indices |
214
+
215
+ ---
216
+
217
+ #### `get-cell`
218
+
219
+ Read a single cell value by row and column index (0-based).
220
+
221
+ | Parameter | Type | Required | Description |
222
+ |-----------|------|----------|-------------|
223
+ | `path` | string | Yes | Path to the `.numbers` file |
224
+ | `sheet` | string | Yes | Sheet name |
225
+ | `table` | string | Yes | Table name |
226
+ | `row` | number | Yes | Row index (0-based) |
227
+ | `col` | number | Yes | Column index (0-based) |
228
+ | `verbose` | boolean | No | Include formula, formatted value, and merge info |
229
+
230
+ ---
231
+
232
+ #### `search`
233
+
234
+ Case-insensitive partial match across every cell in a `.numbers` file.
235
+
236
+ | Parameter | Type | Required | Description |
237
+ |-----------|------|----------|-------------|
238
+ | `path` | string | Yes | Path to the `.numbers` file |
239
+ | `query` | string | Yes | Text to search for |
240
+ | `sheet` | string | No | Limit search to one sheet |
241
+
242
+ **Returns:** Each match with sheet, table, row, column header, and value.
243
+
244
+ ---
245
+
246
+ #### `export-table`
247
+
248
+ Export a table to CSV, TSV, or JSON.
249
+
250
+ | Parameter | Type | Required | Description |
251
+ |-----------|------|----------|-------------|
252
+ | `path` | string | Yes | Path to the `.numbers` file |
253
+ | `format` | `"csv" \| "tsv" \| "json"` | Yes | Output format |
254
+ | `outputPath` | string | Yes | Path for the output file |
255
+ | `sheet` | string | No | Sheet name (default: first sheet) |
256
+ | `table` | string | No | Table name (default: first table) |
257
+
258
+ ---
259
+
260
+ ### Write
261
+
262
+ #### `create-spreadsheet`
263
+
264
+ Create a new `.numbers` file with one sheet and table.
265
+
266
+ | Parameter | Type | Required | Description |
267
+ |-----------|------|----------|-------------|
268
+ | `path` | string | Yes | Path for the new file |
269
+ | `headers` | string[] | Yes | Column header names |
270
+ | `rows` | (string \| number \| boolean \| null)[][] | No | Initial data rows |
271
+ | `sheetName` | string | No | Default: `"Sheet 1"` |
272
+ | `tableName` | string | No | Default: `"Table 1"` |
273
+
274
+ ---
275
+
276
+ #### `set-cell`
277
+
278
+ Write a value to a single cell.
279
+
280
+ | Parameter | Type | Required | Description |
281
+ |-----------|------|----------|-------------|
282
+ | `path` | string | Yes | Path to the `.numbers` file |
283
+ | `row` | number | Yes | Row index (0-based) |
284
+ | `col` | number | Yes | Column index (0-based) |
285
+ | `value` | string \| number \| boolean \| null | Yes | Value to write |
286
+ | `sheet` | string | No | Sheet name (default: first sheet) |
287
+ | `table` | string | No | Table name (default: first table) |
288
+ | `type` | `"string" \| "number" \| "boolean" \| "date"` | No | Force value type (default: auto-detect) |
289
+
290
+ ---
291
+
292
+ #### `set-cells-batch`
293
+
294
+ Write multiple cells in a single operation. Much more efficient than multiple `set-cell` calls.
295
+
296
+ | Parameter | Type | Required | Description |
297
+ |-----------|------|----------|-------------|
298
+ | `path` | string | Yes | Path to the `.numbers` file |
299
+ | `updates` | array | Yes | Array of `{row, col, value, type?}` objects |
300
+ | `sheet` | string | No | Sheet name |
301
+ | `table` | string | No | Table name |
302
+
303
+ ---
304
+
305
+ #### `add-rows`
306
+
307
+ Append rows of data after the last existing row.
308
+
309
+ | Parameter | Type | Required | Description |
310
+ |-----------|------|----------|-------------|
311
+ | `path` | string | Yes | Path to the `.numbers` file |
312
+ | `rows` | array[] | Yes | Rows to append (one array per row) |
313
+ | `sheet` | string | No | Sheet name |
314
+ | `table` | string | No | Table name |
315
+
316
+ ---
317
+
318
+ #### `update-rows`
319
+
320
+ Write full rows by index. Each update is a complete row replacement.
321
+
322
+ | Parameter | Type | Required | Description |
323
+ |-----------|------|----------|-------------|
324
+ | `path` | string | Yes | Path to the `.numbers` file |
325
+ | `updates` | array | Yes | Array of `{row, values}` objects |
326
+ | `sheet` | string | No | Sheet name |
327
+ | `table` | string | No | Table name |
328
+
329
+ ---
330
+
331
+ #### `delete-rows`
332
+
333
+ Delete a range of rows by 0-based inclusive indices.
334
+
335
+ | Parameter | Type | Required | Description |
336
+ |-----------|------|----------|-------------|
337
+ | `path` | string | Yes | Path to the `.numbers` file |
338
+ | `startRow` | number | Yes | First row to delete (0-based, inclusive) |
339
+ | `endRow` | number | Yes | Last row to delete (0-based, inclusive) |
340
+ | `sheet` | string | No | Sheet name |
341
+ | `table` | string | No | Table name |
342
+
343
+ ---
344
+
345
+ ### Sheets and Tables
346
+
347
+ #### `add-sheet`
348
+
349
+ Add a new sheet, optionally with headers for the default table.
350
+
351
+ | Parameter | Type | Required | Description |
352
+ |-----------|------|----------|-------------|
353
+ | `path` | string | Yes | Path to the `.numbers` file |
354
+ | `sheetName` | string | Yes | Name for the new sheet |
355
+ | `tableName` | string | No | Name for the default table |
356
+ | `headers` | string[] | No | Headers for the default table |
357
+
358
+ ---
359
+
360
+ #### `add-table`
361
+
362
+ Add a new table to an existing sheet.
363
+
364
+ | Parameter | Type | Required | Description |
365
+ |-----------|------|----------|-------------|
366
+ | `path` | string | Yes | Path to the `.numbers` file |
367
+ | `sheet` | string | No | Sheet name (default: first sheet) |
368
+ | `tableName` | string | No | Name for the new table |
369
+ | `headers` | string[] | No | Column headers |
370
+
371
+ ---
372
+
373
+ #### `rename-sheet` / `rename-table`
374
+
375
+ Rename a sheet or table.
376
+
377
+ | Parameter | Type | Required | Description |
378
+ |-----------|------|----------|-------------|
379
+ | `path` | string | Yes | Path to the `.numbers` file |
380
+ | (rename-sheet) `oldName` / `newName` | string | Yes | Current and new sheet name |
381
+ | (rename-table) `sheet` | string | No | Sheet containing the table |
382
+ | (rename-table) `oldName` / `newName` | string | Yes | Current and new table name |
383
+
384
+ ---
385
+
386
+ ### Formulas (requires Numbers.app)
387
+
388
+ #### `set-formula`
389
+
390
+ Write a formula to a cell.
391
+
392
+ | Parameter | Type | Required | Description |
393
+ |-----------|------|----------|-------------|
394
+ | `path` | string | Yes | Path to the `.numbers` file |
395
+ | `sheet` | string | Yes | Sheet name |
396
+ | `table` | string | Yes | Table name |
397
+ | `row` | number | Yes | 0-based row |
398
+ | `col` | number | Yes | 0-based column |
399
+ | `formula` | string | Yes | Formula text including the leading `=` |
400
+
401
+ ---
402
+
403
+ #### `set-formulas-batch`
404
+
405
+ Write multiple formulas in a single operation.
406
+
407
+ | Parameter | Type | Required | Description |
408
+ |-----------|------|----------|-------------|
409
+ | `path` | string | Yes | Path to the `.numbers` file |
410
+ | `sheet` | string | Yes | Sheet name |
411
+ | `table` | string | Yes | Table name |
412
+ | `entries` | array | Yes | Array of `{row, col, formula}` objects |
413
+
414
+ ---
415
+
416
+ ### Formatting (requires Numbers.app)
417
+
418
+ #### `set-cell-style`
419
+
420
+ Apply font, color, number format, and alignment to a single cell.
421
+
422
+ | Parameter | Type | Required | Description |
423
+ |-----------|------|----------|-------------|
424
+ | `path` | string | Yes | Path to the `.numbers` file |
425
+ | `sheet` | string | Yes | Sheet name |
426
+ | `table` | string | Yes | Table name |
427
+ | `row` | number | Yes | 0-based row |
428
+ | `col` | number | Yes | 0-based column |
429
+ | `style` | object | Yes | `{font?, fontSize?, color?, fillColor?, bold?, italic?, alignment?, numberFormat?, ...}` |
430
+
431
+ ---
432
+
433
+ #### `set-cells-style-batch`
434
+
435
+ Apply styles to multiple cells in a single operation.
436
+
437
+ | Parameter | Type | Required | Description |
438
+ |-----------|------|----------|-------------|
439
+ | `path` | string | Yes | Path to the `.numbers` file |
440
+ | `sheet` | string | Yes | Sheet name |
441
+ | `table` | string | Yes | Table name |
442
+ | `entries` | array | Yes | Array of `{row, col, style}` objects |
443
+
444
+ ---
445
+
446
+ #### `set-column-width` / `set-row-height`
447
+
448
+ Set the width of a column or height of a row in pixels.
449
+
450
+ | Parameter | Type | Required | Description |
451
+ |-----------|------|----------|-------------|
452
+ | `path` | string | Yes | Path to the `.numbers` file |
453
+ | `sheet` | string | Yes | Sheet name |
454
+ | `table` | string | Yes | Table name |
455
+ | `col` (column) / `row` (row) | number | Yes | 0-based index |
456
+ | `width` (column) / `height` (row) | number | Yes | Size in pixels |
457
+
458
+ ---
459
+
460
+ #### `merge-cells` / `unmerge-cells`
461
+
462
+ Merge a rectangular range of cells, or undo a merge.
463
+
464
+ | Parameter | Type | Required | Description |
465
+ |-----------|------|----------|-------------|
466
+ | `path` | string | Yes | Path to the `.numbers` file |
467
+ | `sheet` | string | Yes | Sheet name |
468
+ | `table` | string | Yes | Table name |
469
+ | `startRow`, `startCol`, `endRow`, `endCol` | number | Yes | 0-based corners (inclusive) |
470
+
471
+ ---
472
+
473
+ ### Import
474
+
475
+ #### `import-csv`
476
+
477
+ Import a CSV, TSV, or JSON file into a new `.numbers` spreadsheet. Auto-detects format from extension, or pass `format` explicitly.
478
+
479
+ | Parameter | Type | Required | Description |
480
+ |-----------|------|----------|-------------|
481
+ | `inputPath` | string | Yes | Path to the CSV/TSV/JSON input |
482
+ | `outputPath` | string | Yes | Path for the output `.numbers` file |
483
+ | `format` | `"auto" \| "csv" \| "tsv" \| "json"` | No | Default: auto-detect from extension |
484
+ | `sheetName` | string | No | Default: `"Sheet 1"` |
485
+ | `tableName` | string | No | Default: `"Table 1"` |
486
+
487
+ ---
488
+
489
+ ## Usage Patterns
490
+
491
+ ### Basic Workflow
492
+
493
+ ```
494
+ User: "What's in ~/Documents/budget.numbers?"
495
+ AI: [calls get-file-info]
496
+ "It has 3 sheets: Q1, Q2, Q3. Q1 contains a 'Budget' table with 12 rows × 5 columns..."
497
+
498
+ User: "Show me Q3"
499
+ AI: [calls read-table with sheet='Q3']
500
+ "Q3 / Budget (12 rows × 5 cols): ..."
501
+
502
+ User: "Find every line about Acme Corp"
503
+ AI: [calls search with query='Acme Corp']
504
+ "Found 4 matches in Q1/Budget and Q3/Vendors..."
505
+ ```
506
+
507
+ ### Iterative Edits
508
+
509
+ ```
510
+ User: "Set B5 to =SUM(B2:B4) and bold the header row"
511
+ AI: [calls set-formula] [calls set-cells-style-batch with bold:true on row 0]
512
+ "Done — B5 is =SUM(B2:B4) and the header row is bold."
513
+ ```
514
+
515
+ ### Importing CSV
516
+
517
+ ```
518
+ User: "Import ~/Downloads/customers.csv into a new spreadsheet"
519
+ AI: [calls import-csv with inputPath, outputPath]
520
+ "Imported 240 rows (6 columns) into ~/Documents/customers.numbers"
521
+ ```
522
+
523
+ ---
524
+
525
+ ## Installation Options
526
+
527
+ ### npm (Recommended)
528
+
529
+ ```bash
530
+ npm install -g github:sweetrb/apple-numbers-mcp
531
+ pip3 install numbers-parser
532
+ ```
533
+
534
+ ### From Source (with Project-Local venv)
535
+
536
+ ```bash
537
+ git clone https://github.com/sweetrb/apple-numbers-mcp.git
538
+ cd apple-numbers-mcp
539
+ npm install
540
+ npm run setup # creates ./venv and installs numbers-parser
541
+ npm run build
542
+ ```
543
+
544
+ If installed from source, use this configuration:
545
+ ```json
546
+ {
547
+ "mcpServers": {
548
+ "apple-numbers": {
549
+ "command": "node",
550
+ "args": ["/absolute/path/to/apple-numbers-mcp/build/index.js"]
551
+ }
552
+ }
553
+ }
554
+ ```
555
+
556
+ The server prefers a project-local venv at `./venv/bin/python3` if present, and falls back to system `python3`. Global npm install works fine as long as `numbers-parser` is on the system Python.
557
+
558
+ ---
559
+
560
+ ## Configuration
561
+
562
+ All configuration is optional — the server works out of the box.
563
+
564
+ ### Environment variables
565
+
566
+ | Variable | Default | Description |
567
+ |----------|---------|-------------|
568
+ | `APPLE_NUMBERS_MCP_MAX_BUFFER` | 50 MB (Python reader) / 64 MB (AppleScript) | Max bytes captured from a subprocess's stdout, applied to both the Python reader and the AppleScript layer. Raise it if a very large spreadsheet is truncated; lower it to cap memory. |
569
+ | `APPLE_NUMBERS_MCP_CONFIG_FILE` | `~/Library/Application Support/apple-numbers-mcp/config.json` | Path to the JSON config file (see below). |
570
+
571
+ ### Configuration file (when the host strips `env`)
572
+
573
+ Some host apps (e.g. Claude Desktop) launch the MCP server with a scrubbed
574
+ environment and ignore the `env` block in their server config, so there's no way
575
+ to pass `APPLE_NUMBERS_MCP_*` settings through it. In that case, put them in a JSON
576
+ file the host doesn't manage — `APPLE_NUMBERS_MCP_CONFIG_FILE`, or by default
577
+ `~/Library/Application Support/apple-numbers-mcp/config.json`:
578
+
579
+ ```json
580
+ {
581
+ "APPLE_NUMBERS_MCP_MAX_BUFFER": "104857600"
582
+ }
583
+ ```
584
+
585
+ The server reads it at startup and merges string values into the environment
586
+ **without overriding** anything already set there (so an explicit `env` still
587
+ wins). Keep only non-secret config here.
588
+
589
+ ---
590
+
591
+ ## Architecture
592
+
593
+ This package is a **TypeScript MCP server with a Python sidecar**:
594
+
595
+ - The MCP server (Node) speaks the Model Context Protocol over stdio.
596
+ - A bundled Python script (`src/utils/numbers_reader.py`) uses `numbers-parser` to read and write `.numbers` files and returns JSON.
597
+ - For formatting, formulas, and cell-dimension changes, an AppleScript layer (`src/utils/applescript.ts`) drives Numbers.app directly — `numbers-parser` doesn't write styles.
598
+ - TypeScript spawns Python via `child_process.execFileSync`.
599
+
600
+ This is the same Python-sidecar pattern used by [apple-photos-mcp](https://github.com/sweetrb/apple-photos-mcp) for the `osxphotos` library.
601
+
602
+ ---
603
+
604
+ ## Security and Privacy
605
+
606
+ - **Local only** — All operations happen on the local machine. No data is sent to external servers.
607
+ - **No credential storage** — The server doesn't store any passwords or authentication tokens.
608
+ - **File-system access** — Tools take explicit paths; the server only reads or writes files you name.
609
+ - **Numbers.app automation** — Formatting and formula tools drive Numbers.app via AppleScript. macOS will prompt for automation permission on first use.
610
+
611
+ ---
612
+
613
+ ## Known Limitations
614
+
615
+ For the full rundown — the read-vs-write split, AppleScript-only formulas/styles,
616
+ indexing, dates, format lag, and concurrent edits — see **[docs/LIMITATIONS.md](docs/LIMITATIONS.md)**.
617
+ The summary below is the quick version.
618
+
619
+ | Limitation | Reason |
620
+ |------------|--------|
621
+ | Formatting / formulas / dimensions need Numbers.app | `numbers-parser` doesn't write styles or formulas; the AppleScript layer fills that gap (macOS only) |
622
+ | No conditional formatting | Not exposed by `numbers-parser` |
623
+ | No charts or images | Not exposed by `numbers-parser` |
624
+ | Sheet deletion not supported | Not exposed by `numbers-parser` |
625
+ | Computed-value writes only | `set-cell` / `add-rows` etc. write computed values; use `set-formula` to write formulas |
626
+ | Date filter format | ISO 8601 (`YYYY-MM-DD` or full ISO datetime) |
627
+
628
+ These are tracked for future releases. The underlying `numbers-parser` library has partial support for styles via its `Style` API, which provides a path forward for some of these.
629
+
630
+ ---
631
+
632
+ ## Troubleshooting
633
+
634
+ ### "numbers-parser not installed. Run: npm run setup"
635
+ - Run `pip3 install numbers-parser` (global) or `npm run setup` (project-local venv).
636
+ - If you used a virtualenv, make sure it's the one at `./venv/` in the project directory.
637
+
638
+ ### `apple-numbers` server fails to connect when run from a clone
639
+ - Launch `claude` from inside the repo directory so `CLAUDE_PROJECT_DIR` resolves to the repo root (the bare `.` fallback is unreliable).
640
+ - Run **both** `npm run build` and `npm run setup` first — `build` compiles the entrypoint, `setup` creates the `./venv` the Python sidecar needs.
641
+ - Run `claude mcp list` to check for conflicting scopes; project-scope `.mcp.json` outranks a user-scope `apple-numbers` entry, and a local-scope entry outranks both.
642
+ - If the server is listed as pending, approve the project-scope server when Claude Code prompts.
643
+
644
+ ### "File not found"
645
+ - Check the path; expand `~` if your shell isn't doing it.
646
+ - Ensure the file extension is `.numbers`.
647
+
648
+ ### Formatting / formula tools fail with "Numbers.app not running" or "Not authorized to send Apple events to Numbers"
649
+ - Open Numbers.app at least once. macOS will prompt for automation permission — accept it.
650
+ - Verify in **System Settings → Privacy & Security → Automation**, or reset with `tccutil reset AppleEvents`.
651
+ - 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.
652
+
653
+ ### Output looks wrong (dates as ISO, floats with decimals)
654
+ - 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.
655
+
656
+ ---
657
+
658
+ ## Development
659
+
660
+ ```bash
661
+ npm install # Install dependencies
662
+ npm run setup # Create ./venv with numbers-parser
663
+ npm run build # Compile TypeScript
664
+ npm test # Unit tests (mocked)
665
+ npm run test:integration # Integration tests against real .numbers fixtures
666
+ npm run test:all # Both
667
+ npm run test:coverage # Unit tests with coverage report
668
+ npm run typecheck # Type-check without emitting
669
+ npm run lint # Check code style
670
+ npm run format # Format code
671
+ ```
672
+
673
+ ### Integration tests
674
+
675
+ Integration tests exercise the full pipeline against real `.numbers` fixture files. Generate the fixtures first, then run:
676
+
677
+ ```bash
678
+ npm run setup
679
+ ./venv/bin/python3 test/fixtures/generate-fixtures.py
680
+ npm run test:all
681
+ ```
682
+
683
+ Unit tests run everywhere; integration tests auto-skip when fixtures or `numbers-parser` are not available.
684
+
685
+ ---
686
+
687
+ ## Author
688
+
689
+ **Rob Sweet** - President, [Superior Technologies Research](https://www.superiortech.io)
690
+
691
+ A software consulting, contracting, and development company.
692
+
693
+ - Email: rob@superiortech.io
694
+ - GitHub: [@sweetrb](https://github.com/sweetrb)
695
+
696
+ ## License
697
+
698
+ 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.
699
+
700
+ ## Contributing
701
+
702
+ Contributions are welcome! Please open an issue or PR at [github.com/sweetrb/apple-numbers-mcp](https://github.com/sweetrb/apple-numbers-mcp).
703
+
704
+ ## Related Projects
705
+
706
+ - [apple-mail-mcp](https://github.com/sweetrb/apple-mail-mcp) — MCP server for Apple Mail
707
+ - [apple-notes-mcp](https://github.com/sweetrb/apple-notes-mcp) — MCP server for Apple Notes
708
+ - [apple-photos-mcp](https://github.com/sweetrb/apple-photos-mcp) — MCP server for Apple Photos
709
+ - [numbers-parser](https://pypi.org/project/numbers-parser/) — The Python library that powers this server