apple-numbers-mcp
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Uyari
- process.env — Environment variable access in .github/workflows/dependabot-rebuild.yml
- fs module — File system access in .github/workflows/dependabot-rebuild.yml
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
MCP server for Apple Numbers - read, write, search, and format .numbers spreadsheets via Claude and other AI assistants
Apple Numbers MCP Server
A Model Context Protocol (MCP) server that enables AI assistants like Claude to read, write, search, and modify Apple Numbers (.numbers) spreadsheet files. Backed by the numbers-parser Python library.
What is This?
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:
- "What's in this spreadsheet?" — inspect sheets, tables, dimensions
- "Find every row where the customer is Acme Corp"
- "Export the Q3 Results table to CSV"
- "Set cell B5 to =SUM(B2:B4)"
- "Append these three rows to the Inventory table"
- "Bold and center the header row"
- "Import this CSV into a new spreadsheet"
The AI assistant communicates with this server, which uses numbers-parser to read and write .numbers files directly (no Numbers.app required for most operations). All data stays local on your machine.
Quick Start
Using Claude Code (Easiest)
If you're using Claude Code (in Terminal or VS Code), just ask Claude to install it:
Install the sweetrb/apple-numbers-mcp MCP server so you can help me work with my Numbers spreadsheets
Claude will handle the installation and configuration automatically. After install, you'll need to install numbers-parser (Python) — see Requirements below.
Or register it yourself in one deterministic line (no clone needed):
claude mcp add apple-numbers -s user -- npx -y apple-numbers-mcp
Using the Plugin Marketplace
Install as a Claude Code plugin for automatic configuration and enhanced AI behavior:
/plugin marketplace add sweetrb/apple-numbers-mcp
/plugin install apple-numbers
This method also installs a skill that teaches Claude when and how to use Apple Numbers effectively.
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 [email protected] first). To pre-warm it instead of waiting on the first call, run scripts/setup.sh inside that install directory.
Using the Codex Marketplace
Install the same public marketplace in Codex:
codex plugin marketplace add sweetrb/apple-numbers-mcp
codex plugin add apple-numbers@apple-numbers-mcp
The Codex package registers the apple-numbers MCP server throughnpx -y apple-numbers-mcp — the same published-package invocation documented for
Claude Desktop below — and bundles the Apple Numbers skill guidance. As with every
install path, the numbers-parser Python sidecar must be available (pip3 install numbers-parser); see Requirements.
Other Hosts (Hermes, Antigravity)
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.
Hermes Agent (NousResearch) — Hermes has no plugin/marketplace drop-in, so there is nothing in this repo to install from. Register the server with the CLI:
hermes mcp add apple-numbers --command npx --args -y apple-numbers-mcpOr add it to
~/.hermes/config.yamlby hand:mcp_servers: apple-numbers: command: npx args: ["-y", "apple-numbers-mcp"]Restart your Hermes session afterward so the tools load.
Antigravity (Google) — add the server entry from
.antigravity-plugin/mcp_config.jsonto~/.gemini/config/mcp_config.json(or via Antigravity's MCP settings).
Manual Installation
1. Install the server (from the npm registry):
npm install -g apple-numbers-mcp
2. Install numbers-parser (the Python library this server depends on):
pip3 install numbers-parser
Or, if you cloned the repo, run pnpm run setup to create a project-local Python venv with numbers-parser pre-installed.
3. Add to Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"apple-numbers": {
"command": "npx",
"args": ["apple-numbers-mcp"]
}
}
}
4. Restart Claude Desktop and start using natural language:
"What sheets are in ~/Documents/budget.numbers?"
Running from a clone in Claude Code (project-scope .mcp.json)
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:
pnpm run build # compile TypeScript to build/
pnpm run setup # create the ./venv the Python sidecar needs
pnpm 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.
The entrypoint is written as:
"args": ["${CLAUDE_PROJECT_DIR:-.}/build/index.js"]
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.
Why not
${CLAUDE_PLUGIN_ROOT}?CLAUDE_PLUGIN_ROOTis 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 useCLAUDE_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.jsonis dedicated to the clone workflow (using${CLAUDE_PROJECT_DIR:-.}). Becauseplugin.jsondeclares its ownmcpServers, the plugin does not also auto-load the root.mcp.json, so there is no double-registration.
Heads-up on scope precedence: project-scope (
.mcp.json) outranks user-scope. If you also have anapple-numbersentry 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.jsonis 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.
Requirements
- macOS — the npm package is macOS-only (it declares
os: ["darwin"]). Reads and value/structure writes go throughnumbers-parserdirectly (no Numbers.app needed); formatting and formula tools additionally require Numbers.app. - Node.js 20+ — Required for the MCP server
- Python 3.11+ — the
numbers-parserlibrary installs automatically into a project-local venv on first use (or pre-warm withpnpm run setup). numbers-parser requires Python ≥ 3.10; macOS ships 3.9, so install a newer Python first (e.g.brew install [email protected]). - 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. Run thedoctortool to verify your setup.
Features
Read
| Feature | Description |
|---|---|
| File Inspection | List sheets, tables, dimensions, and header rows |
| Table Read | Read data with optional row range and column filtering |
| Cell Read | Single cells by 0-based index, with optional formula/format/merge metadata |
| Search | Case-insensitive text search across every cell, optionally scoped to one sheet |
| Export | Export a table to CSV, TSV, or JSON |
Write
| Feature | Description |
|---|---|
| Create Spreadsheet | New .numbers file with headers and optional initial rows |
| Set Cell | Write a value to a single cell, with optional type coercion |
| Set Cells Batch | Write many cells in one operation (more efficient than multiple set-cell calls) |
| Add / Update / Delete Rows | Append, replace, or remove rows by index |
| Sheets and Tables | Add new sheets or tables to an existing file; rename either |
Formulas (requires Numbers.app)
| Feature | Description |
|---|---|
| Set Formula | Write a formula like =SUM(B2:B10) to a cell |
| Set Formulas Batch | Write many formulas at once |
Formatting (requires Numbers.app)
| Feature | Description |
|---|---|
| Cell Styles | Font, size, colors, number format, alignment |
| Cell Styles Batch | Style many cells at once |
| Column Width / Row Height | Set dimensions in pixels |
| Merge / Unmerge | Merge a range of cells, or undo a merge |
Import / Diagnostics
| Feature | Description |
|---|---|
| Import CSV/TSV/JSON | Convert a tabular file into a new .numbers spreadsheet |
| Health Check | Verify Python 3 and numbers-parser are installed |
| Doctor | Richer setup diagnostic — Python interpreter (path + version), read sidecar, Numbers.app, and Automation permission, each reported ok / warn / fail with actionable advice |
All tools also return structured JSON (structuredContent) alongside the human-readable text, so agents can consume results without parsing prose.
MCP resources & prompts
Resources expose read-only context the client can attach without a tool call:
the numbers://file/{path} template (file structure — sheets, tables, dimensions,
headers) and the numbers://table/{path} template (the default table's data).
Both are templated by a URL-encoded .numbers file path. Prompts package common
workflows: analyze-spreadsheet, bulk-edit, import-csv-guide.
Tool Reference
This section documents all available tools. AI agents should use these tool names and parameters exactly as specified.
Read
health-check
Verify Python 3 and numbers-parser are installed and reachable from the server.
Parameters: None
Returns: numbers-parser version, or an error explaining how to install.
doctor
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.
Parameters: None
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.
get-file-info
Get the structure of a .numbers file: sheets, tables, dimensions, header rows.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Absolute or ~-relative path to the .numbers file |
Returns: Default sheet name, plus each sheet with its tables (name, dimensions, header row).
read-table
Read data from a table. Returns headers and rows. Defaults to the first sheet and first table if not specified.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | No | Sheet name (default: first sheet) |
table |
string | No | Table name (default: first table) |
startRow |
number | No | 0-based start row, inclusive (default: 1, after header) |
endRow |
number | No | 0-based end row, inclusive (default: last row) |
columns |
(string | number)[] | No | Column filter: header names or 0-based indices |
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 for the table's real dimensions.
get-cell
Read a single cell value by row and column index (0-based).
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
row |
number | Yes | Row index (0-based) |
col |
number | Yes | Column index (0-based) |
verbose |
boolean | No | Include formula, formatted value, and merge info |
search
Case-insensitive partial match across every cell in a .numbers file.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
query |
string | Yes | Text to search for |
sheet |
string | No | Limit search to one sheet |
Returns: Each match with sheet, table, row, column header, and value.
export-table
Export a table to CSV, TSV, or JSON.
⚠️ 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. Paths are bounded: outputPath must resolve — after ~ expansion and symlink resolution — to a location under your home directory, /tmp, /private/tmp, or /Volumes; anything else is rejected with an error naming those roots.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
format |
"csv" | "tsv" | "json" |
Yes | Output format |
outputPath |
string | Yes | Path for the output file |
sheet |
string | No | Sheet name (default: first sheet) |
table |
string | No | Table name (default: first table) |
Write
create-spreadsheet
Create a new .numbers file with one sheet and table.
⚠️ Safety: Overwrites the file at path if it already exists — confirm the destination first. Paths are bounded: path must resolve — after ~ expansion and symlink resolution — to a location under your home directory, /tmp, /private/tmp, or /Volumes; anything else is rejected with an error naming those roots.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path for the new file |
headers |
string[] | Yes | Column header names |
rows |
(string | number | boolean | null)[][] | No | Initial data rows |
sheetName |
string | No | Default: "Sheet 1" |
tableName |
string | No | Default: "Table 1" |
set-cell
Write a value to a single cell.
⚠️ Safety: Overwrites the existing cell value in place in the .numbers file.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
row |
number | Yes | Row index (0-based) |
col |
number | Yes | Column index (0-based) |
value |
string | number | boolean | null | Yes | Value to write |
sheet |
string | No | Sheet name (default: first sheet) |
table |
string | No | Table name (default: first table) |
type |
"string" | "number" | "boolean" | "date" |
No | Force value type (default: auto-detect) |
set-cells-batch
Write multiple cells in a single operation. Much more efficient than multiple set-cell calls.
⚠️ Safety: Overwrites the existing cell values in place in the .numbers file.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
updates |
array | Yes | Array of {row, col, value, type?} objects |
sheet |
string | No | Sheet name |
table |
string | No | Table name |
add-rows
Append rows of data after the last existing row.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
rows |
array[] | Yes | Rows to append (one array per row) |
sheet |
string | No | Sheet name |
table |
string | No | Table name |
update-rows
Write full rows by index. Each update is a complete row replacement.
⚠️ Safety: Overwrites the existing row data in place in the .numbers file.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
updates |
array | Yes | Array of {row, values} objects |
sheet |
string | No | Sheet name |
table |
string | No | Table name |
delete-rows
Delete a range of rows by 0-based inclusive indices.
⚠️ Safety: Destructive and not undoable — requires explicit user confirmation. Modifies the .numbers file in place; verify the row range first.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
startRow |
number | Yes | First row to delete (0-based, inclusive) |
endRow |
number | Yes | Last row to delete (0-based, inclusive) |
sheet |
string | No | Sheet name |
table |
string | No | Table name |
Sheets and Tables
add-sheet
Add a new sheet, optionally with headers for the default table.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheetName |
string | Yes | Name for the new sheet |
tableName |
string | No | Name for the default table |
headers |
string[] | No | Headers for the default table |
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.
add-table
Add a new table to an existing sheet.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | No | Sheet name (default: first sheet) |
tableName |
string | No | Name for the new table |
headers |
string[] | No | Column headers |
Table geometry: same rule as add-sheet — with headers the table is 1 × headers.length, without them 12 × 8.
rename-sheet / rename-table
Rename a sheet or table. You identify the target by its current name via the optional sheet / table parameters (omit them to target the first sheet / first table); there is no oldName input — the old name is returned in the result, not passed in.
rename-sheet
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
newName |
string | Yes | New name for the sheet |
sheet |
string | No | Current sheet name (default: first sheet) |
rename-table
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
newName |
string | Yes | New name for the table |
sheet |
string | No | Sheet containing the table (default: first sheet) |
table |
string | No | Current table name (default: first table) |
Formulas (requires Numbers.app)
set-formula
Write a formula to a cell.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
row |
number | Yes | 0-based row |
col |
number | Yes | 0-based column |
formula |
string | Yes | Formula text including the leading = |
set-formulas-batch
Write multiple formulas in a single operation.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
formulas |
array | Yes | Array of {row, col, formula} objects |
Formatting (requires Numbers.app)
set-cell-style
Apply font, color, number format, and alignment to a single cell.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
row |
number | Yes | 0-based row |
col |
number | Yes | 0-based column |
style |
object | Yes | Style properties to set (all optional) — see the table below |
style object fields (all optional; omit a field to leave it unchanged):
| Field | Type | Description |
|---|---|---|
fontName |
string | Font name, e.g. "Helvetica-Bold", "HelveticaNeue". There is no separate bold/italic flag — pick a font face that already encodes the weight/style. |
fontSize |
number | Font size in points |
textColor |
{red, green, blue} |
Text color, RGB with each channel 0–65535 |
backgroundColor |
{red, green, blue} |
Cell fill color, RGB with each channel 0–65535 |
format |
enum | Cell number format: "automatic", "number", "currency", "date and time", "duration", "fraction", "scientific", "numeral system", "checkbox", "star rating", "text" |
alignment |
enum | Horizontal alignment: "auto align", "left", "center", "right", "justify" |
verticalAlignment |
enum | Vertical alignment: "top", "center", "bottom" |
textWrap |
boolean | Enable text wrapping |
Example: { "fontName": "Helvetica-Bold", "fontSize": 14, "textColor": { "red": 65535, "green": 0, "blue": 0 }, "alignment": "center" }
set-cells-style-batch
Apply styles to multiple cells in a single operation.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
entries |
array | Yes | Array of {row, col, style} objects — each style uses the same fields documented under set-cell-style |
set-column-width / set-row-height
Set the width of a column or height of a row in pixels.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
col (column) / row (row) |
number | Yes | 0-based index |
width (column) / height (row) |
number | Yes | Size in pixels |
merge-cells / unmerge-cells
Merge a rectangular range of cells, or undo a merge.
| Parameter | Type | Required | Description |
|---|---|---|---|
path |
string | Yes | Path to the .numbers file |
sheet |
string | Yes | Sheet name |
table |
string | Yes | Table name |
startRow, startCol, endRow, endCol |
number | Yes | 0-based corners (inclusive) |
Import
import-csv
Import a CSV, TSV, or JSON file into a new .numbers spreadsheet. Auto-detects format from extension, or pass format explicitly.
⚠️ Safety: Overwrites the file at outputPath if it already exists — confirm the destination first. Paths are bounded: both inputPath and outputPath must resolve — after ~ expansion and symlink resolution — to locations under your home directory, /tmp, /private/tmp, or /Volumes; anything else is rejected with an error naming those roots.
| Parameter | Type | Required | Description |
|---|---|---|---|
inputPath |
string | Yes | Path to the CSV/TSV/JSON input |
outputPath |
string | Yes | Path for the output .numbers file |
format |
"auto" | "csv" | "tsv" | "json" |
No | Default: auto-detect from extension |
sheetName |
string | No | Default: "Sheet 1" |
tableName |
string | No | Default: "Table 1" |
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".
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.
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.
Usage Patterns
Basic Workflow
User: "What's in ~/Documents/budget.numbers?"
AI: [calls get-file-info]
"It has 3 sheets: Q1, Q2, Q3. Q1 contains a 'Budget' table with 12 rows × 5 columns..."
User: "Show me Q3"
AI: [calls read-table with sheet='Q3']
"Q3 / Budget (12 rows × 5 cols): ..."
User: "Find every line about Acme Corp"
AI: [calls search with query='Acme Corp']
"Found 4 matches in Q1/Budget and Q3/Vendors..."
Iterative Edits
User: "Set B5 to =SUM(B2:B4) and bold the header row"
AI: [calls set-formula] [calls set-cells-style-batch with style {fontName: "Helvetica-Bold"} on row 0]
"Done — B5 is =SUM(B2:B4) and the header row is bold."
Importing CSV
User: "Import ~/Downloads/customers.csv into a new spreadsheet"
AI: [calls import-csv with inputPath, outputPath]
"Imported 240 rows (6 columns) into ~/Documents/customers.numbers"
Installation Options
npm (Recommended)
npm install -g apple-numbers-mcp
pip3 install numbers-parser
From Source (with Project-Local venv)
git clone https://github.com/sweetrb/apple-numbers-mcp.git
cd apple-numbers-mcp
pnpm install
pnpm run setup # creates ./venv and installs numbers-parser
pnpm run build
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.
If installed from source, use this configuration:
{
"mcpServers": {
"apple-numbers": {
"command": "node",
"args": ["/absolute/path/to/apple-numbers-mcp/build/index.js"]
}
}
}
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.
Configuration
All configuration is optional — the server works out of the box.
Environment variables
| Variable | Default | Description |
|---|---|---|
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. |
APPLE_NUMBERS_MCP_CONFIG_FILE |
~/Library/Application Support/apple-numbers-mcp/config.json |
Path to the JSON config file (see below). |
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). |
APPLE_NUMBERS_MCP_EXTRA_ROOTS |
unset | Colon-separated absolute directories to add to the allowed-path roots, e.g. /Data/Finance:/srv/shared. The built-in roots already cover your home directory, the temp dirs and /Volumes (where external and network mounts appear), so this is for genuinely unusual layouts rather than routine configuration. Entries are canonicalized like the built-ins, so a symlinked entry cannot smuggle in a wider parent; relative and empty entries are ignored. Widening a security boundary — set it deliberately. |
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. |
Per-call timeouts are not configurable. Every
numbers-parsersidecar call
runs under a fixed 30-second timeout and every AppleScript call under a fixed
60-second one; there is no environment variable for either.APPLE_NUMBERS_MCP_SETUP_TIMEOUTabove governs only the one-time venv bootstrap,
andAPPLE_NUMBERS_MCP_MAX_BUFFERcaps stdout size, not wall-clock time. If you
hitOperation timed out after 30000ms, narrow the request — useread-table'sstartRow/endRowandcolumns, split large batches — rather than looking for
a knob.
Configuration file (when the host strips env)
Some host apps (e.g. Claude Desktop) launch the MCP server with a scrubbed
environment and ignore the env block in their server config, so there's no way
to pass APPLE_NUMBERS_MCP_* settings through it. In that case, put them in a JSON
file the host doesn't manage — APPLE_NUMBERS_MCP_CONFIG_FILE, or by default~/Library/Application Support/apple-numbers-mcp/config.json:
{
"APPLE_NUMBERS_MCP_MAX_BUFFER": "104857600"
}
The server reads it at startup and merges string values into the environment
without overriding anything already set there (so an explicit env still
wins). Keep only non-secret config here.
Architecture
This package is a TypeScript MCP server with a Python sidecar:
- The MCP server (Node) speaks the Model Context Protocol over stdio.
- A bundled Python script (
src/utils/numbers_reader.py) usesnumbers-parserto read and write.numbersfiles and returns JSON. - For formatting, formulas, and cell-dimension changes, an AppleScript layer (
src/utils/applescript.ts) drives Numbers.app directly —numbers-parserdoesn't write styles. - TypeScript spawns Python via
child_process.execFileSync.
This is the same Python-sidecar pattern used by apple-photos-mcp for the osxphotos library.
Security and Privacy
- Local only — All operations happen on the local machine. No data is sent to external servers.
- No credential storage — The server doesn't store any passwords or authentication tokens.
- File-system access — Tools take explicit paths; the server only reads or writes files you name.
- 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.
Known Limitations
For the full rundown — the values-vs-formatting backend split, AppleScript-only
formulas/styles, import-csv auto-typing, fixed per-call timeouts, indexing,
dates, format lag, and concurrent edits — see docs/LIMITATIONS.md.
The summary below is the quick version.
| Limitation | Reason |
|---|---|
| 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 |
import-csv auto-types CSV/TSV fields |
Leading zeros are lost (01234 → 1234); JSON input is passed through untouched |
| Per-call timeouts are fixed | 30 s for sidecar calls, 60 s for AppleScript; neither is configurable — narrow the request instead |
| No conditional formatting | Not exposed by numbers-parser |
| No charts or images | Not exposed by numbers-parser |
| Sheet deletion not supported | Not exposed by numbers-parser |
| Computed-value writes only | set-cell / add-rows etc. write computed values; use set-formula to write formulas |
| Concurrent writes are last-writer-wins | Saves are atomic (no torn files), but overlapping writes to the same file can lose updates — serialize writes per file |
| Date filter format | ISO 8601 (YYYY-MM-DD or full ISO datetime) |
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.
Troubleshooting
"numbers-parser not installed"
- Most common cause: your
python3is older than 3.11 — stock macOS ships Python 3.9, whichnumbers-parsercan't run on. Install a newer Python (brew install [email protected]), then simply retry the tool call: the venv rebuilds automatically. - Otherwise, install the library directly:
pip3 install numbers-parser(global Python), or runscripts/setup.shfrom a repo checkout (pnpm run setup) to build the project-local venv. - Run the
doctortool — it reports the resolved Python interpreter path and version, so a too-old stock Python is visible at a glance. - If you used a virtualenv, make sure it's the one at
./venv/in the project directory.
apple-numbers server fails to connect when run from a clone
- Launch
claudefrom inside the repo directory soCLAUDE_PROJECT_DIRresolves to the repo root (the bare.fallback is unreliable). - Run both
pnpm run buildandpnpm run setupfirst —buildcompiles the entrypoint,setupcreates the./venvthe Python sidecar needs. - Run
claude mcp listto check for conflicting scopes; project-scope.mcp.jsonoutranks a user-scopeapple-numbersentry, and a local-scope entry outranks both. - If the server is listed as pending, approve the project-scope server when Claude Code prompts.
Every tool is rejected with "invalid outputSchema … unsupported dialect"
- Full symptom:
Tool '<name>' has an invalid outputSchema: JSON Schema declares an unsupported dialect ("$schema": "http://json-schema.org/draft-07/schema#"). The default validator supports JSON Schema 2020-12 only.The host refuses every tool, so the server looks completely dead even though it started fine. - Upgrade to 1.1.17 or newer. MCP standardized on JSON Schema 2020-12, and hosts now reject any other dialect; releases before 1.1.17 advertised draft-07 because the MCP SDK emits it. 1.1.17 advertises
https://json-schema.org/draft/2020-12/schemaon every tool'sinputSchemaandoutputSchema. - If you installed via
npx -y apple-numbers-mcp, restart the host to pick up the new version; if you run from a clone,git pull && pnpm install && pnpm run build.
"File not found"
- Check the path; expand
~if your shell isn't doing it. - Ensure the file extension is
.numbers.
"Output path … is outside the allowed roots" / "Input path … is outside the allowed roots"
- Every tool that touches a file on disk is bounded to your home directory,
/tmp,/private/tmp, and/Volumes— the server will not write into/etc,/Library, an app bundle, or any other system location, and will not read from one. - As of 1.2.0 this covers the
.numbersfile itself, not just export/import destinations.get-file-info,read-table,searchand the in-place writes (set-cell,add-rows,set-formula, …) previously accepted a path anywhere on disk. - Symlinks are resolved before the check, so a link inside an allowed directory that points outside it is rejected too; the error reports the resolved path, which is usually the quickest way to see where a path really went.
- Fix: choose a path under one of those roots (
~/Documents/report.numbers,/tmp/report.csv). If your spreadsheets genuinely live somewhere else, add that directory toAPPLE_NUMBERS_MCP_EXTRA_ROOTS— see Environment variables.
Formatting / formula tools fail with "Numbers.app not running" or "Not authorized to send Apple events to Numbers"
- Open Numbers.app at least once. macOS will prompt for automation permission — accept it.
- Verify in System Settings → Privacy & Security → Automation, or reset with
tccutil reset AppleEvents. - Run the
doctortool to confirm your setup — it reports the Numbers.app and Automation-permission checks separately. See the Automation Permission guide. Reads don't need this permission; only writes do.
Output looks wrong (dates as ISO, floats with decimals)
- The server normalizes dates to ISO 8601 and rounds floats that are within
1e-9of an integer. If you want raw values, file an issue.
Development
pnpm install # Install dependencies
pnpm run setup # Create ./venv with numbers-parser
pnpm run build # Compile TypeScript
pnpm test # Unit tests (mocked)
pnpm run test:integration # Integration tests against real .numbers fixtures
pnpm run test:all # Both
pnpm run test:coverage # Unit tests with coverage report
pnpm run typecheck # Type-check without emitting
pnpm run lint # Check code style
pnpm run format # Format code
Integration tests
Integration tests exercise the full pipeline against real .numbers fixture files. Generate the fixtures first, then run:
pnpm run setup
./venv/bin/python3 test/fixtures/generate-fixtures.py
pnpm run test:all
Unit tests run everywhere; integration tests auto-skip when fixtures or numbers-parser are not available.
Author
Rob Sweet - President, Superior Technologies Research
A software consulting, contracting, and development company.
- Email: [email protected]
- GitHub: @sweetrb
License
MIT License - see LICENSE for details. This project is not affiliated with Apple Inc. or the numbers-parser project.
Contributing
Contributions are welcome! Please open an issue or PR at github.com/sweetrb/apple-numbers-mcp.
Related Projects
Part of a family of macOS MCP servers:
- apple-mail-mcp — MCP server for Apple Mail (read, search, send, and organize email)
- apple-notes-mcp — MCP server for Apple Notes (create, search, update, and export notes)
- apple-photos-mcp — MCP server for Apple Photos (query metadata and export originals)
- numbers-parser — The Python library that powers this server
Recurring macOS permission prompts
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 — the fix is to run this server under the official, Developer-ID-signed Node so the grant survives Node updates.
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi