{"id":34241930,"url":"https://github.com/psu3d0/spreadsheet-mcp","last_synced_at":"2026-03-11T03:02:06.950Z","repository":{"id":315674521,"uuid":"1059818445","full_name":"PSU3D0/spreadsheet-mcp","owner":"PSU3D0","description":"MCP server for spreadsheet analysis and editing. Slim, token-efficient tool surface designed for LLM agents.","archived":false,"fork":false,"pushed_at":"2026-03-04T22:59:37.000Z","size":8181,"stargazers_count":37,"open_issues_count":1,"forks_count":3,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-05T04:26:04.001Z","etag":null,"topics":["spreadsheet","spreadsheet-agent","spreadsheet-ai","spreadsheet-mcp"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PSU3D0.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-19T01:56:38.000Z","updated_at":"2026-03-04T22:59:54.000Z","dependencies_parsed_at":"2025-09-20T02:43:33.588Z","dependency_job_id":null,"html_url":"https://github.com/PSU3D0/spreadsheet-mcp","commit_stats":null,"previous_names":["psu3d0/spreadsheet-read-mcp"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/PSU3D0/spreadsheet-mcp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PSU3D0%2Fspreadsheet-mcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PSU3D0%2Fspreadsheet-mcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PSU3D0%2Fspreadsheet-mcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PSU3D0%2Fspreadsheet-mcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PSU3D0","download_url":"https://codeload.github.com/PSU3D0/spreadsheet-mcp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PSU3D0%2Fspreadsheet-mcp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30368547,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-10T21:41:54.280Z","status":"online","status_checked_at":"2026-03-11T02:00:07.027Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["spreadsheet","spreadsheet-agent","spreadsheet-ai","spreadsheet-mcp"],"created_at":"2025-12-16T04:24:01.488Z","updated_at":"2026-03-11T03:02:06.938Z","avatar_url":"https://github.com/PSU3D0.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# spreadsheet-kit\n\n[![CI](https://github.com/PSU3D0/spreadsheet-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/PSU3D0/spreadsheet-mcp/actions/workflows/ci.yml)\n[![Crates.io](https://img.shields.io/crates/v/spreadsheet-mcp.svg)](https://crates.io/crates/spreadsheet-mcp)\n[![npm](https://img.shields.io/npm/v/agent-spreadsheet.svg)](https://www.npmjs.com/package/agent-spreadsheet)\n[![License](https://img.shields.io/crates/l/spreadsheet-mcp.svg)](https://github.com/PSU3D0/spreadsheet-mcp/blob/main/LICENSE)\n\n![spreadsheet-kit](https://raw.githubusercontent.com/PSU3D0/spreadsheet-mcp/main/assets/banner.jpeg)\n\n**Spreadsheet automation for AI agents.** Read, profile, edit, and recalculate `.xlsx` workbooks with tooling designed to be token-efficient, structurally aware, and agent-friendly.\n\nspreadsheet-kit ships two surfaces:\n\n| Surface | Binary | Mode | Best for |\n| --- | --- | --- | --- |\n| **[agent-spreadsheet](#quickstart-cli)** | `agent-spreadsheet` | Stateless CLI | Scripts, pipelines, one-shot agent tasks |\n| **[spreadsheet-mcp](#quickstart-mcp-server)** | `spreadsheet-mcp` | Stateful MCP server | Multi-turn agent sessions with caching and fork/recalc |\n\nBoth share the same core engine and support `.xlsx` / `.xlsm` (read + write) and `.xls` / `.xlsb` (discovery only).\n\n---\n\n## Install\n\n### npm (recommended for CLI)\n\n```bash\nnpm i -g agent-spreadsheet\nagent-spreadsheet --help\n```\n\nDownloads a prebuilt native binary for your platform. No Rust toolchain required.\n\n### Cargo\n\n```bash\n# CLI\ncargo install agent-spreadsheet\n\n# MCP server\ncargo install spreadsheet-mcp\n```\n\nFormualizer (native Rust recalc engine) is included by default. To build without it, use `--no-default-features`.\n\n### Prebuilt binaries\n\nDownload from [GitHub Releases](https://github.com/PSU3D0/spreadsheet-mcp/releases).\nBuilds are published for Linux x86_64, macOS x86_64/aarch64, and Windows x86_64.\n\n### Docker (MCP server)\n\n```bash\n# Read-only (~15 MB)\ndocker pull ghcr.io/psu3d0/spreadsheet-mcp:latest\n\n# With write/recalc support (~800 MB, includes LibreOffice)\ndocker pull ghcr.io/psu3d0/spreadsheet-mcp:full\n```\n\n---\n\n## Quickstart: CLI\n\nThe CLI is the fastest path to working with spreadsheets from code. Every command is stateless — pass a file, get JSON.\n\n```bash\n# List sheets\nagent-spreadsheet list-sheets data.xlsx\n\n# Profile structure and detected regions\nagent-spreadsheet sheet-overview data.xlsx \"Sheet1\"\n\n# Read a table as structured data\nagent-spreadsheet read-table data.xlsx --sheet \"Sheet1\"\n\n# Read one or more raw ranges\nagent-spreadsheet range-values data.xlsx Sheet1 A1:C20\n\n# Search values directly (default mode is value)\nagent-spreadsheet find-value data.xlsx \"Revenue\" --mode value\n\n# Label lookup: match a label cell, then read an adjacent value\nagent-spreadsheet find-value data.xlsx \"Net Income\" --mode label --label-direction below\n\n# Describe workbook metadata\nagent-spreadsheet describe data.xlsx\n```\n\n### Deterministic pagination loops\n\n```bash\n# sheet-page continuation\nagent-spreadsheet sheet-page data.xlsx Sheet1 --format compact --page-size 200\nagent-spreadsheet sheet-page data.xlsx Sheet1 --format compact --page-size 200 --start-row 201\n\n# read-table continuation\nagent-spreadsheet read-table data.xlsx --sheet \"Sheet1\" --table-format values --limit 200 --offset 0\nagent-spreadsheet read-table data.xlsx --sheet \"Sheet1\" --table-format values --limit 200 --offset 200\n```\n\n### Edit → recalculate → diff\n\n```bash\nagent-spreadsheet copy data.xlsx /tmp/draft.xlsx\nagent-spreadsheet edit /tmp/draft.xlsx Sheet1 \"B2=500\" \"C2==B2*1.1\"\nagent-spreadsheet recalculate /tmp/draft.xlsx\nagent-spreadsheet diff data.xlsx /tmp/draft.xlsx\n```\n\n### Stateless batch writes (`--ops @...`)\n\n```bash\nagent-spreadsheet transform-batch data.xlsx --ops @ops.json --dry-run\nagent-spreadsheet style-batch data.xlsx --ops @style_ops.json --dry-run\nagent-spreadsheet apply-formula-pattern data.xlsx --ops @formula_ops.json --in-place\nagent-spreadsheet structure-batch data.xlsx --ops @structure_ops.json --dry-run\nagent-spreadsheet column-size-batch data.xlsx --ops @column_size_ops.json --output resized.xlsx\nagent-spreadsheet sheet-layout-batch data.xlsx --ops @layout_ops.json --dry-run\nagent-spreadsheet rules-batch data.xlsx --ops @rules_ops.json --output ruled.xlsx --force\n```\n\n#### Batch payload examples (JSON body passed via `--ops @file.json`)\n\nAll batch payloads use a top-level envelope object. Most commands require `{\"ops\":[...]}`; `column-size-batch` prefers `{\"sheet_name\":\"...\",\"ops\":[...]}` and also accepts per-op `sheet_name` inside `{\"ops\":[...]}`.\n\n##### transform-batch payloads (`@transform_ops.json`)\n- Minimal: `{\"ops\":[{\"kind\":\"fill_range\",\"sheet_name\":\"Sheet1\",\"target\":{\"kind\":\"range\",\"range\":\"B2:B4\"},\"value\":\"0\"}]}`\n- Advanced: `{\"ops\":[{\"kind\":\"replace_in_range\",\"sheet_name\":\"Sheet1\",\"target\":{\"kind\":\"region\",\"region_id\":1},\"find\":\"N/A\",\"replace\":\"\",\"match_mode\":\"contains\",\"case_sensitive\":false,\"include_formulas\":true}]}`\n\n##### style-batch payloads (`@style_ops.json`)\n- Minimal: `{\"ops\":[{\"sheet_name\":\"Sheet1\",\"target\":{\"kind\":\"range\",\"range\":\"B2:B2\"},\"patch\":{\"font\":{\"bold\":true}}}]}`\n- Advanced: `{\"ops\":[{\"sheet_name\":\"Sheet1\",\"target\":{\"kind\":\"cells\",\"cells\":[\"B2\",\"B3\"]},\"patch\":{\"number_format\":\"$#,##0.00\",\"alignment\":{\"horizontal\":\"right\"}},\"op_mode\":\"merge\"}]}`\n\n##### apply-formula-pattern payloads (`@formula_ops.json`)\n- Minimal: `{\"ops\":[{\"sheet_name\":\"Sheet1\",\"target_range\":\"C2:C4\",\"anchor_cell\":\"C2\",\"base_formula\":\"B2*2\"}]}`\n- Advanced: `{\"ops\":[{\"sheet_name\":\"Sheet1\",\"target_range\":\"C2:E4\",\"anchor_cell\":\"C2\",\"base_formula\":\"B2*2\",\"fill_direction\":\"both\",\"relative_mode\":\"excel\"}]}`\n- `relative_mode` valid values: `excel`, `abs_cols`, `abs_rows`\n\n##### structure-batch payloads (`@structure_ops.json`)\n- Minimal: `{\"ops\":[{\"kind\":\"rename_sheet\",\"old_name\":\"Summary\",\"new_name\":\"Dashboard\"}]}`\n- Advanced: `{\"ops\":[{\"kind\":\"copy_range\",\"sheet_name\":\"Sheet1\",\"dest_sheet_name\":\"Summary\",\"src_range\":\"A1:C4\",\"dest_anchor\":\"A1\",\"include_styles\":true,\"include_formulas\":true}]}`\n\n##### column-size-batch payloads (`@column_size_ops.json`)\n- Minimal (preferred): `{\"sheet_name\":\"Sheet1\",\"ops\":[{\"range\":\"A:A\",\"size\":{\"kind\":\"width\",\"width_chars\":12.0}}]}`\n- Advanced (preferred): `{\"sheet_name\":\"Sheet1\",\"ops\":[{\"target\":{\"kind\":\"columns\",\"range\":\"A:C\"},\"size\":{\"kind\":\"auto\",\"min_width_chars\":8.0,\"max_width_chars\":24.0}}]}`\n- Also accepted (harmonized shape): `{\"ops\":[{\"sheet_name\":\"Sheet1\",\"range\":\"A:A\",\"size\":{\"kind\":\"width\",\"width_chars\":12.0}}]}`\n\n##### sheet-layout-batch payloads (`@layout_ops.json`)\n- Minimal: `{\"ops\":[{\"kind\":\"freeze_panes\",\"sheet_name\":\"Sheet1\",\"freeze_rows\":1,\"freeze_cols\":1}]}`\n- Advanced: `{\"ops\":[{\"kind\":\"set_page_setup\",\"sheet_name\":\"Sheet1\",\"orientation\":\"landscape\",\"fit_to_width\":1,\"fit_to_height\":1}]}`\n\n##### rules-batch payloads (`@rules_ops.json`)\n- Minimal: `{\"ops\":[{\"kind\":\"set_data_validation\",\"sheet_name\":\"Sheet1\",\"target_range\":\"B2:B4\",\"validation\":{\"kind\":\"list\",\"formula1\":\"\\\"A,B,C\\\"\"}}]}`\n- Advanced: `{\"ops\":[{\"kind\":\"set_conditional_format\",\"sheet_name\":\"Sheet1\",\"target_range\":\"C2:C10\",\"rule\":{\"kind\":\"expression\",\"formula\":\"C2\u003e100\"},\"style\":{\"fill_color\":\"#FFF2CC\",\"bold\":true}}]}`\n\n`apply-formula-pattern` clears cached results for touched formula cells; run `recalculate` to refresh computed values.\n\nAll output is JSON by default.\nUse `--shape canonical|compact` (default: `canonical`) to control response shape.\n\n### Formula parse policy\n\nCommands that tokenize or validate formulas accept `--formula-parse-policy \u003cfail|warn|off\u003e`:\n\n| Mode | Behavior | Default for |\n| --- | --- | --- |\n| **fail** | Abort on any formula parse error | `edit` (single-write) |\n| **warn** | Continue; attach `formula_parse_diagnostics` to the response | `scan-volatiles`, `formula-map`, `formula-trace`, `transform-batch`, `structure-batch`, `rules-batch` |\n| **off** | Silently skip parse errors | — |\n\nWhen the policy is `warn` (or `fail` with errors), the response includes a `formula_parse_diagnostics` object:\n\n```json\n{\n  \"formula_parse_diagnostics\": {\n    \"policy\": \"warn\",\n    \"total_errors\": 400,\n    \"groups_truncated\": false,\n    \"groups\": [\n      {\n        \"error_code\": \"FORMULA_PARSE_FAILED\",\n        \"error_message\": \"No matching opener for closer at position 161\",\n        \"sheet_name\": \"Assessments\",\n        \"formula_preview\": \"=IF(C4=\\\"\\\",\\\"\\\",IF(C4=\\\"N/A\\\",…\",\n        \"count\": 400,\n        \"sample_addresses\": [\"D4\", \"D5\", \"D10\", \"D11\", \"D12\"]\n      }\n    ]\n  }\n}\n```\n\nErrors are grouped by structural pattern — formulas that differ only in cell references (e.g., `=IF(C4=…)` and `=IF(C5=…)`) collapse into a single group with a count and sample addresses.\n\nShape policy:\n- **Canonical (default/omitted):** preserve the full response schema.\n- **range-values:** returns a stable `values: [...]` envelope in both canonical and compact modes.\n- **range-values default encoding:** dense JSON (`dense.encoding = \"dense_v1\"`) with `dictionary` + run-length `row_runs`.\n- **range-values `--include-formulas`:** includes sparse formula coordinates in dense mode (`dense.formulas`), or a matrix in explicit `json` format.\n- **read-table and sheet-page: compact preserves the active branch and continuation fields (`next_offset`, `next_start_row`)**.\n- **formula-trace compact:** omits per-layer `highlights` while preserving `layers` and `next_cursor`.\n\n#### `sheet-page` machine contract\n- Inspect top-level `format` before reading payload fields.\n- `format=full`: read top-level `rows` plus optional `header_row` and `next_start_row`.\n- `format=compact`: read `compact.headers`, `compact.header_row`, `compact.rows` plus optional `next_start_row`.\n- `format=values_only`: read `values_only.rows` plus optional `next_start_row`.\n- Continuation is always driven by top-level `next_start_row` when present.\n- Global `--shape compact` preserves the active `sheet-page` branch; it does not flatten `sheet-page` payloads.\n\nMachine continuation example:\n1. Request page 1 without `--start-row`.\n2. If `next_start_row` is present, call `sheet-page` again with `--start-row \u003cnext_start_row\u003e`.\n3. Stop when `next_start_row` is omitted.\n\nJSON output is compact by default; use `--quiet` to suppress warnings.\nGlobal `--output-format csv` is currently unsupported; use command-specific CSV options like `read-table --table-format csv`.\n\n### CLI command reference\n\n| Command | Description |\n| --- | --- |\n| `list-sheets \u003cfile\u003e` | List sheets with summaries |\n| `sheet-overview \u003cfile\u003e \u003csheet\u003e` | Region detection + orientation |\n| `describe \u003cfile\u003e` | Workbook metadata |\n| `read-table \u003cfile\u003e [--sheet S] [--range R] [--table-name T] [--region-id ID] [--limit N] [--offset N] [--sample-mode first\\|last\\|distributed] [--table-format json\\|values\\|csv]` | Structured table read with deterministic offset pagination |\n| `sheet-page \u003cfile\u003e \u003csheet\u003e --format \u003cfull|compact|values_only\u003e [--start-row ROW] [--page-size N]` | Deterministic row paging with `next_start_row` continuation |\n| `range-values \u003cfile\u003e \u003csheet\u003e \u003crange\u003e [range...] [--format dense\\|json\\|values\\|csv] [--include-formulas]` | Raw cell values in dense JSON encoding by default, optionally with sparse formula metadata |\n| `inspect-cells \u003cfile\u003e \u003csheet\u003e \u003ctarget\u003e [target...] [--include-empty]` | Detail-view per-cell formula/value/cached/style snapshots (max 25 cells per request) |\n| `find-value \u003cfile\u003e \u003cquery\u003e [--sheet S] [--mode value\\|label] [--label-direction right\\|below\\|any]` | Search cell values (`value`) or match labels and return adjacent values (`label`) |\n| `named-ranges \u003cfile\u003e [--sheet S] [--name-prefix P]` | List named ranges/tables/formula items |\n| `find-formula \u003cfile\u003e \u003cquery\u003e [--sheet S] [--limit N] [--offset N]` | Formula text search with continuation |\n| `scan-volatiles \u003cfile\u003e [--sheet S] [--limit N] [--offset N] [--formula-parse-policy P]` | Scan formulas for volatile functions |\n| `sheet-statistics \u003cfile\u003e \u003csheet\u003e` | Per-sheet density and type stats |\n| `formula-map \u003cfile\u003e \u003csheet\u003e [--sort-by complexity\\|count] [--limit N] [--formula-parse-policy P]` | Formula inventory summary |\n| `formula-trace \u003cfile\u003e \u003csheet\u003e \u003ccell\u003e \u003cprecedents\\|dependents\u003e [--depth N] [--page-size N] [--cursor-depth N --cursor-offset N] [--formula-parse-policy P]` | Trace formula dependencies with cursor continuation |\n| `table-profile \u003cfile\u003e [--sheet S]` | Column types, cardinality, distributions |\n| `create-workbook \u003cpath\u003e [--sheets Inputs,Calc,...] [--overwrite]` | Create a blank workbook with configurable initial sheets |\n| `copy \u003csource\u003e \u003cdest\u003e` | Copy workbook (for edit workflows) |\n| `edit \u003cfile\u003e \u003csheet\u003e [--dry-run\\|--in-place\\|--output PATH] [--force] \u003cedits...\u003e [--formula-parse-policy P]` | Apply cell edits with preview/output safety modes (`A1=42` literal, `B2==SUM(...)` formula) |\n| `transform-batch \u003cfile\u003e --ops @ops.json (--dry-run\\|--in-place\\|--output PATH) [--formula-parse-policy P]` | Generic stateless transform batch pipeline |\n| `style-batch \u003cfile\u003e --ops @ops.json (--dry-run|--in-place|--output PATH)` | Stateless style operations |\n| `apply-formula-pattern \u003cfile\u003e --ops @ops.json (--dry-run|--in-place|--output PATH)` | Stateless formula fill/pattern operations (clears touched formula caches; run `recalculate`) |\n| `structure-batch \u003cfile\u003e --ops @ops.json (--dry-run\\|--in-place\\|--output PATH) [--formula-parse-policy P]` | Stateless structure operations (sheet rows/columns) |\n| `column-size-batch \u003cfile\u003e --ops @ops.json (--dry-run|--in-place|--output PATH)` | Stateless column sizing operations |\n| `sheet-layout-batch \u003cfile\u003e --ops @ops.json (--dry-run|--in-place|--output PATH)` | Stateless layout operations (freeze/split/hide/view) |\n| `rules-batch \u003cfile\u003e --ops @ops.json (--dry-run\\|--in-place\\|--output PATH) [--formula-parse-policy P]` | Stateless validation/conditional-format operations |\n| `recalculate \u003cfile\u003e [--output PATH] [--force]` | Recalculate formulas via backend (in-place or to output) |\n| `diff \u003coriginal\u003e \u003cmodified\u003e [--details --limit N --offset N] [--sheet S] [--range A1:C10]` | Summary-first workbook diff with optional paged details and filters |\n\n#### Formula write-path provenance (`write_path_provenance`)\nFormula-writing commands emit optional provenance metadata for troubleshooting:\n- `written_via`: write path (`edit`, `transform_batch`, `apply_formula_pattern`)\n- `formula_targets`: sheet/cell or sheet/range targets touched by formula writes\n\nDebug compare workflow (same-target by write path):\n1. Apply the same formula target via two paths (for example `edit` vs `apply-formula-pattern`).\n2. Compare `write_path_provenance.written_via` and `formula_targets` in responses.\n3. Use `inspect-cells` plus `recalculate` to compare resulting behavior.\n\n#### Financial presentation starter defaults\n- Keep label columns (often column A) explicitly sized (roughly `24–36` chars) to prevent clipping.\n- Apply consistent number formats by semantic type:\n  - Currency: `\"$\"#,##0.00_);[Red](\"$\"#,##0.00)`\n  - Percent: `0.0%`\n  - Integer/count: `#,##0`\n- Apply `sheet-layout-batch` freeze panes after header layout stabilizes.\n\n---\n\n## Quickstart: MCP Server\n\nThe MCP server provides agents a stateful session with workbook caching, fork management, and recalculation. Connect any MCP-compatible client.\n\n### Claude Code / Claude Desktop\n\nAdd to `~/.claude.json` or project `.mcp.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"spreadsheet\": {\n      \"command\": \"spreadsheet-mcp\",\n      \"args\": [\"--workspace-root\", \"/path/to/workbooks\", \"--transport\", \"stdio\"]\n    }\n  }\n}\n```\n\nOr with Docker:\n\n```json\n{\n  \"mcpServers\": {\n    \"spreadsheet\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\", \"-i\", \"--rm\",\n        \"-v\", \"/path/to/workbooks:/data\",\n        \"ghcr.io/psu3d0/spreadsheet-mcp:latest\",\n        \"--transport\", \"stdio\"\n      ]\n    }\n  }\n}\n```\n\n### Cursor / VS Code\n\n```json\n{\n  \"mcp.servers\": {\n    \"spreadsheet\": {\n      \"command\": \"spreadsheet-mcp\",\n      \"args\": [\"--workspace-root\", \"${workspaceFolder}\", \"--transport\", \"stdio\"]\n    }\n  }\n}\n```\n\n### HTTP mode\n\n```bash\nspreadsheet-mcp --workspace-root /path/to/workbooks\n# → http://127.0.0.1:8079 — POST /mcp\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eMore MCP client configurations\u003c/summary\u003e\n\n**Claude Code — Docker with VBA:**\n```json\n{\n  \"mcpServers\": {\n    \"spreadsheet\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-v\", \"/path/to/workbooks:/data\", \"ghcr.io/psu3d0/spreadsheet-mcp:latest\", \"--transport\", \"stdio\", \"--vba-enabled\"]\n    }\n  }\n}\n```\n\n**Claude Code — Docker with write/recalc:**\n```json\n{\n  \"mcpServers\": {\n    \"spreadsheet\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-v\", \"/path/to/workbooks:/data\", \"ghcr.io/psu3d0/spreadsheet-mcp:full\", \"--transport\", \"stdio\", \"--recalc-enabled\"]\n    }\n  }\n}\n```\n\n**Cursor / VS Code — Docker read-only:**\n```json\n{\n  \"mcp.servers\": {\n    \"spreadsheet\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-v\", \"${workspaceFolder}:/data\", \"ghcr.io/psu3d0/spreadsheet-mcp:latest\", \"--transport\", \"stdio\"]\n    }\n  }\n}\n```\n\n**Cursor / VS Code — Docker write/recalc:**\n```json\n{\n  \"mcp.servers\": {\n    \"spreadsheet\": {\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-v\", \"${workspaceFolder}:/data\", \"ghcr.io/psu3d0/spreadsheet-mcp:full\", \"--transport\", \"stdio\", \"--recalc-enabled\"]\n    }\n  }\n}\n```\n\n\u003c/details\u003e\n\n---\n\n## When to use what\n\n| You want to… | Use |\n| --- | --- |\n| One-shot reads from scripts or pipelines | **CLI** (`agent-spreadsheet`) |\n| Agent sessions with caching across calls | **MCP** (`spreadsheet-mcp`) |\n| Fork → edit → recalc → diff workflows | **MCP** (fork lifecycle + recalc backend) |\n| Embed in an LLM tool-use loop | **MCP** (designed for multi-turn agent use) |\n| Quick CLI edits without running a server | **CLI** (`copy` → `edit` → `diff`) |\n| npm/npx install with zero Rust toolchain | **npm** (`npm i -g agent-spreadsheet`) |\n\n---\n\n## Token efficiency\n\nDumping a 50,000-row spreadsheet into context is expensive and usually unnecessary. spreadsheet-kit tools are built around **discover → profile → extract** — agents get structural awareness without burning tokens on cells they don't need.\n\n### Output profiles\n\nThe server defaults to **token-dense** output:\n\n- `read_table` → CSV format (flat string, minimal overhead)\n- `range_values` → values array (no metadata wrapper)\n- `sheet_page` → compact format (no formulas/styles unless requested)\n- `table_profile` / `sheet_statistics` → summary only (no samples unless requested)\n- Pagination fields (`next_offset`, `next_start_row`) only appear when more data exists\n\nSwitch to verbose output with `--output-profile verbose` or `SPREADSHEET_MCP_OUTPUT_PROFILE=verbose`.\n\n### Recommended agent workflow\n\n![Token Efficiency Workflow](https://raw.githubusercontent.com/PSU3D0/spreadsheet-mcp/main/assets/token_efficiency.jpeg)\n\n1. `list_workbooks` → `list_sheets` → `workbook_summary` for orientation\n2. `sheet_overview` to get detected regions (ids, bounds, kind, confidence)\n3. `table_profile` → `read_table` with `region_id` and small `limit`\n4. `find_value` (label mode) or `range_values` for targeted pulls\n5. Reserve `sheet_page` for unknown layouts; prefer `compact` format\n6. Page and filter — avoid full-sheet reads\n\n---\n\n## Tool surface (MCP)\n\n### Read-only tools (always available)\n\n| Tool | Purpose |\n| --- | --- |\n| `list_workbooks` | List spreadsheet files in workspace |\n| `describe_workbook` | Workbook metadata |\n| `list_sheets` | Sheets with summaries |\n| `workbook_summary` | Summary + optional entry points / named ranges |\n| `sheet_overview` | Orientation + region detection (cached) |\n| `sheet_page` | Page through cells (compact / full / values_only) |\n| `read_table` | Structured region/table read (csv / values / json) |\n| `range_values` | Raw cell values for specific ranges |\n| `table_profile` | Column types, cardinality, sample distributions |\n| `find_value` | Search values or labels |\n| `find_formula` | Search formulas with paging |\n| `sheet_statistics` | Density, nulls, duplicates |\n| `sheet_formula_map` | Formulas by complexity / count |\n| `formula_trace` | Precedent / dependent tracing |\n| `scan_volatiles` | Find volatile formulas (NOW, RAND, etc.) |\n| `named_ranges` | Defined names + tables |\n| `sheet_styles` | Style inspection (sheet-scoped) |\n| `workbook_style_summary` | Workbook-wide style summary |\n| `get_manifest_stub` | Generate SheetPort manifest scaffold |\n| `close_workbook` | Evict from cache |\n\n### VBA tools (opt-in: `--vba-enabled`)\n\n| Tool | Purpose |\n| --- | --- |\n| `vba_project_summary` | VBA project metadata + module list |\n| `vba_module_source` | Paged VBA module source extraction |\n\nRead-only — does not execute macros, only extracts source text from `.xlsm` files.\n\n### Write \u0026 recalc tools (opt-in: `--recalc-enabled`)\n\n| Tool | Purpose |\n| --- | --- |\n| `create_fork` / `list_forks` / `discard_fork` | Fork lifecycle |\n| `checkpoint_fork` / `restore_checkpoint` / `list_checkpoints` / `delete_checkpoint` | Snapshot + rollback |\n| `edit_batch` | Cell value / formula edits |\n| `transform_batch` | Range-first clear / fill / replace |\n| `style_batch` | Batch style edits (range / region / cells) |\n| `apply_formula_pattern` | Autofill-like formula fills |\n| `structure_batch` | Rows / cols / sheets + copy / move ranges |\n| `column_size_batch` | Column width operations |\n| `sheet_layout_batch` | Freeze panes, zoom, print area, page setup |\n| `rules_batch` | Data validation + conditional formatting |\n| `recalculate` | Trigger formula recalculation |\n| `get_changeset` | Diff fork vs original (paged, filterable) |\n| `screenshot_sheet` | Render range to PNG (max 100x30 cells) |\n| `save_fork` | Export fork to file |\n| `list_staged_changes` / `apply_staged_change` / `discard_staged_change` | Manage previewed changes |\n| `get_edits` | List applied edits on a fork |\n\n---\n\n## Recalc backends\n\nFormula recalculation is handled by a pluggable backend. Two are supported:\n\n| Backend | How | Default | When to use |\n| --- | --- | --- | --- |\n| **Formualizer** | Native Rust engine (320+ functions) | **Yes** (default feature) | Fast, zero external deps. Ships with every `cargo install`. |\n| **LibreOffice** | Headless `soffice` process | Docker `:full` only | Full Excel formula compatibility. Used by the `:full` Docker image. |\n\n**Default behavior:** `cargo install` includes Formualizer out of the box — recalculation works immediately with no extra setup. LibreOffice is only used in the Docker `:full` image, which bundles `soffice` with pre-configured macros for maximum formula coverage.\n\nCompile-time feature flags:\n- `recalc-formualizer` (**default**) — pure Rust, bundled via Formualizer\n- `recalc-libreoffice` — uses LibreOffice (requires `soffice` on PATH)\n- `--no-default-features` — read-only mode, no recalc support\n\nIf recalc is disabled, all read, edit, and diff operations still work — only `recalculate` requires a backend.\n\n---\n\n## Deployment modes\n\n| Mode | Binary | State | Recalc | Transport |\n| --- | --- | --- | --- | --- |\n| **CLI** | `agent-spreadsheet` | Stateless | Yes (Formualizer, default) | stdin → stdout JSON |\n| **MCP (stdio)** | `spreadsheet-mcp` | Stateful (LRU cache) | Optional (`--recalc-enabled`) | MCP over stdio |\n| **MCP (HTTP)** | `spreadsheet-mcp` | Stateful (LRU cache) | Optional (`--recalc-enabled`) | Streamable HTTP `POST /mcp` |\n| **Docker slim** | `spreadsheet-mcp` | Stateful | No | HTTP (default) or stdio |\n| **Docker full** | `spreadsheet-mcp` | Stateful | Yes (LibreOffice) | HTTP (default) or stdio |\n| **WASM** | — | — | — | *Planned* |\n\n---\n\n## Workspace layout\n\n```\nspreadsheet-kit/\n├── crates/\n│   ├── spreadsheet-kit/       # Shared engine + agent-spreadsheet CLI binary\n│   └── spreadsheet-mcp/       # Stateful MCP server adapter\n├── npm/\n│   └── agent-spreadsheet/     # npm binary distribution package\n├── formualizer/               # Formula recalc engine (default backend)\n├── docs/                      # Architecture and design docs\n└── .github/workflows/         # CI, release, Docker builds\n```\n\n| Crate | Role |\n| --- | --- |\n| [`spreadsheet-kit`](crates/spreadsheet-kit/) | Shared engine + `agent-spreadsheet` CLI binary |\n| [`spreadsheet-mcp`](crates/spreadsheet-mcp/) | MCP server adapter + transport layer |\n| [`agent-spreadsheet` (npm)](npm/agent-spreadsheet/) | npm wrapper — downloads prebuilt native binary on install |\n\n---\n\n## Region detection\n\n![Region Detection Visualization](https://raw.githubusercontent.com/PSU3D0/spreadsheet-mcp/main/assets/region_detection_viz.jpeg)\n\nSpreadsheets often contain multiple logical tables, parameter blocks, and output areas on a single sheet. The server detects these automatically:\n\n1. **Gutter detection** — scans for empty rows/columns separating content blocks\n2. **Recursive splitting** — subdivides areas along detected gutters\n3. **Border trimming** — removes sparse edges to tighten bounds\n4. **Header detection** — identifies header rows (including multi-row merged headers)\n5. **Classification** — labels each region: `data`, `parameters`, `outputs`, `calculator`, `metadata`\n6. **Confidence scoring** — higher scores for well-structured regions with clear headers\n\nRegions are cached per sheet. Tools like `read_table` accept a `region_id` to scope reads without manually specifying ranges. See [docs/HEURISTICS.md](docs/HEURISTICS.md) for details.\n\n---\n\n## Architecture\n\n![Architecture Overview](https://raw.githubusercontent.com/PSU3D0/spreadsheet-mcp/main/assets/architecture_overview.jpeg)\n\n- **LRU cache** keeps recently-accessed workbooks in memory (configurable capacity)\n- **Lazy sheet metrics** computed once per sheet, reused across tools\n- **Region detection on demand** runs for `sheet_overview` and is cached for `region_id` lookups\n- **Fork isolation** — write operations work on copies, never mutate originals in-place\n- **Sampling modes** — `distributed` sampling reads evenly across rows without loading everything\n- **Output caps** — truncated by default; use tool params to expand\n\n---\n\n## Configuration reference\n\nAll flags can also be set via environment variables prefixed with `SPREADSHEET_MCP_`.\n\n| Flag | Env | Default | Description |\n| --- | --- | --- | --- |\n| `--workspace-root \u003cDIR\u003e` | `SPREADSHEET_MCP_WORKSPACE` | cwd | Directory to scan for workbooks |\n| `--cache-capacity \u003cN\u003e` | `SPREADSHEET_MCP_CACHE_CAPACITY` | `5` | LRU workbook cache size |\n| `--extensions \u003ccsv\u003e` | `SPREADSHEET_MCP_EXTENSIONS` | `xlsx,xlsm,xls,xlsb` | Allowed file extensions |\n| `--workbook \u003cFILE\u003e` | `SPREADSHEET_MCP_WORKBOOK` | — | Single-workbook mode |\n| `--enabled-tools \u003ccsv\u003e` | `SPREADSHEET_MCP_ENABLED_TOOLS` | all | Whitelist exposed tools |\n| `--transport \u003cT\u003e` | `SPREADSHEET_MCP_TRANSPORT` | `http` | `http` or `stdio` |\n| `--http-bind \u003cADDR\u003e` | `SPREADSHEET_MCP_HTTP_BIND` | `127.0.0.1:8079` | HTTP bind address |\n| `--output-profile \u003cP\u003e` | `SPREADSHEET_MCP_OUTPUT_PROFILE` | `token-dense` | `token-dense` or `verbose` |\n| `--recalc-enabled` | `SPREADSHEET_MCP_RECALC_ENABLED` | `false` | Enable write/recalc tools |\n| `--max-concurrent-recalcs \u003cN\u003e` | `SPREADSHEET_MCP_MAX_CONCURRENT_RECALCS` | `2` | Parallel recalc limit |\n| `--tool-timeout-ms \u003cMS\u003e` | `SPREADSHEET_MCP_TOOL_TIMEOUT_MS` | `30000` | Per-tool timeout (0 = disabled) |\n| `--max-response-bytes \u003cN\u003e` | `SPREADSHEET_MCP_MAX_RESPONSE_BYTES` | `1000000` | Max response size (0 = disabled) |\n| `--allow-overwrite` | `SPREADSHEET_MCP_ALLOW_OVERWRITE` | `false` | Allow `save_fork` to overwrite originals |\n| `--vba-enabled` | `SPREADSHEET_MCP_VBA_ENABLED` | `false` | Enable VBA inspection tools |\n| `--screenshot-dir \u003cDIR\u003e` | `SPREADSHEET_MCP_SCREENSHOT_DIR` | `\u003cworkspace\u003e/screenshots` | Screenshot output directory |\n| `--path-map \u003cMAP\u003e` | `SPREADSHEET_MCP_PATH_MAP` | — | Docker path remapping (`/data=/host/path`) |\n\n---\n\n## Docker deployment\n\n### Image variants\n\n| Image | Size | Recalc | Use case |\n| --- | --- | --- | --- |\n| `ghcr.io/psu3d0/spreadsheet-mcp:latest` | ~15 MB | No | Read-only analysis |\n| `ghcr.io/psu3d0/spreadsheet-mcp:full` | ~800 MB | Yes (LibreOffice) | Write + recalc + screenshots |\n\n### Basic usage\n\n```bash\n# Read-only\ndocker run -v /path/to/workbooks:/data -p 8079:8079 \\\n  ghcr.io/psu3d0/spreadsheet-mcp:latest\n\n# With VBA tools\ndocker run -v /path/to/workbooks:/data -p 8079:8079 \\\n  -e SPREADSHEET_MCP_VBA_ENABLED=true \\\n  ghcr.io/psu3d0/spreadsheet-mcp:latest\n\n# Write + recalc\ndocker run -v /path/to/workbooks:/data -p 8079:8079 \\\n  ghcr.io/psu3d0/spreadsheet-mcp:full\n```\n\n### Path mapping\n\nWhen the server runs in Docker but your agent reads files from the host, configure path mapping so responses include host-visible paths:\n\n```bash\ndocker run \\\n  -v /path/to/workbooks:/data \\\n  -e SPREADSHEET_MCP_PATH_MAP=\"/data=/path/to/workbooks\" \\\n  -p 8079:8079 \\\n  ghcr.io/psu3d0/spreadsheet-mcp:full\n```\n\nThis adds `client_path`, `client_output_path`, and `client_saved_to` fields to tool responses.\n\n### Separate screenshot output\n\n```bash\ndocker run \\\n  -v /path/to/workbooks:/data \\\n  -v /path/to/screenshots:/screenshots \\\n  -e SPREADSHEET_MCP_SCREENSHOT_DIR=/screenshots \\\n  -e SPREADSHEET_MCP_PATH_MAP=\"/data=/path/to/workbooks,/screenshots=/path/to/screenshots\" \\\n  -p 8079:8079 \\\n  ghcr.io/psu3d0/spreadsheet-mcp:full\n```\n\n### Privilege handling\n\nThe `:full` image entrypoint drops privileges to match the owner of the mounted workspace directory. Override with `SPREADSHEET_MCP_RUN_UID` / `SPREADSHEET_MCP_RUN_GID` or `docker run --user`.\n\n---\n\n## Write \u0026 recalc workflows\n\nWrite tools use a **fork-based** model for safety. Edits never mutate the original file — work on a fork, inspect changes, and export when satisfied.\n\n```\ncreate_fork → edit_batch / transform_batch → recalculate → get_changeset → save_fork\n                    ↑                                              |\n             checkpoint_fork ←──── restore_checkpoint ←────────────┘\n```\n\n### Edit shorthand\n\n`edit_batch` accepts both canonical objects and shorthand strings:\n\n```json\n{\n  \"edits\": [\n    { \"address\": \"A1\", \"value\": \"Revenue\" },\n    { \"address\": \"B2\", \"formula\": \"SUM(B3:B10)\" },\n    \"C1=100\",\n    \"D1==SUM(A1:A2)\"\n  ]\n}\n```\n\n`\"A1=100\"` sets a value. `\"A1==SUM(...)\"` sets a formula (double `=`).\n\n\u003cdetails\u003e\n\u003csummary\u003eWrite tool shapes reference\u003c/summary\u003e\n\n**edit_batch**\n```json\n{\n  \"tool\": \"edit_batch\",\n  \"arguments\": {\n    \"fork_id\": \"fork-123\",\n    \"sheet_name\": \"Inputs\",\n    \"edits\": [\n      { \"address\": \"A1\", \"value\": \"Financial Model Inputs\" },\n      { \"address\": \"B2\", \"formula\": \"SUM(B3:B10)\" },\n      \"C1=100\",\n      \"D1==SUM(A1:A2)\"\n    ]\n  }\n}\n```\n\n**style_batch**\n```json\n{\n  \"tool\": \"style_batch\",\n  \"arguments\": {\n    \"fork_id\": \"fork-123\",\n    \"ops\": [\n      {\n        \"sheet_name\": \"Accounts\",\n        \"target\": { \"kind\": \"range\", \"range\": \"A2:F2\" },\n        \"patch\": {\n          \"font\": { \"bold\": true },\n          \"fill\": { \"kind\": \"pattern\", \"pattern_type\": \"solid\", \"foreground_color\": \"FFF5F7FA\" }\n        }\n      },\n      {\n        \"sheet_name\": \"Accounts\",\n        \"range\": \"A3:F3\",\n        \"style\": { \"fill\": { \"color\": \"#F5F7FA\" } }\n      }\n    ]\n  }\n}\n```\n\n**structure_batch**\n```json\n{\n  \"tool\": \"structure_batch\",\n  \"arguments\": {\n    \"fork_id\": \"fork-123\",\n    \"ops\": [\n      { \"kind\": \"create_sheet\", \"name\": \"Inputs\" },\n      { \"kind\": \"insert_rows\", \"sheet_name\": \"Data\", \"start\": 5, \"count\": 3 },\n      { \"kind\": \"copy_range\", \"sheet_name\": \"Data\", \"source\": \"A1:D1\", \"target\": \"A5\" }\n    ]\n  }\n}\n```\n\n**column_size_batch**\n```json\n{\n  \"tool\": \"column_size_batch\",\n  \"arguments\": {\n    \"fork_id\": \"fork-123\",\n    \"sheet_name\": \"Accounts\",\n    \"mode\": \"apply\",\n    \"ops\": [\n      { \"target\": { \"kind\": \"columns\", \"range\": \"A:C\" }, \"size\": { \"kind\": \"auto\", \"max_width_chars\": 40.0 } },\n      { \"range\": \"D:D\", \"size\": { \"kind\": \"width\", \"width_chars\": 24.0 } }\n    ]\n  }\n}\n```\n\n**sheet_layout_batch**\n```json\n{\n  \"tool\": \"sheet_layout_batch\",\n  \"arguments\": {\n    \"fork_id\": \"fork-123\",\n    \"mode\": \"preview\",\n    \"ops\": [\n      { \"kind\": \"freeze_panes\", \"sheet_name\": \"Dashboard\", \"freeze_rows\": 1, \"freeze_cols\": 1 },\n      { \"kind\": \"set_zoom\", \"sheet_name\": \"Dashboard\", \"zoom_percent\": 110 },\n      { \"kind\": \"set_print_area\", \"sheet_name\": \"Dashboard\", \"range\": \"A1:G30\" },\n      { \"kind\": \"set_page_setup\", \"sheet_name\": \"Dashboard\", \"orientation\": \"landscape\", \"fit_to_width\": 1, \"fit_to_height\": 1 }\n    ]\n  }\n}\n```\n\n**rules_batch**\n```json\n{\n  \"tool\": \"rules_batch\",\n  \"arguments\": {\n    \"fork_id\": \"fork-123\",\n    \"mode\": \"apply\",\n    \"ops\": [\n      {\n        \"kind\": \"set_data_validation\",\n        \"sheet_name\": \"Inputs\",\n        \"target_range\": \"B3:B100\",\n        \"validation\": { \"kind\": \"list\", \"formula1\": \"=Lists!$A$1:$A$10\", \"allow_blank\": false }\n      },\n      {\n        \"kind\": \"set_conditional_format\",\n        \"sheet_name\": \"Dashboard\",\n        \"target_range\": \"D3:D100\",\n        \"rule\": { \"kind\": \"cell_is\", \"operator\": \"less_than\", \"formula\": \"0\" },\n        \"style\": { \"fill_color\": \"#FFE0E0\", \"font_color\": \"#8A0000\", \"bold\": true }\n      }\n    ]\n  }\n}\n```\n\n\u003c/details\u003e\n\n### Screenshot tool\n\n`screenshot_sheet` renders a range to a cropped PNG via LibreOffice (requires `:full` image or `--recalc-enabled`).\n\n- Max range: **100 rows x 30 columns** per screenshot\n- Pixel guard: **4096 px** per side, **12 MP** area (override via `SPREADSHEET_MCP_MAX_PNG_DIM_PX` / `SPREADSHEET_MCP_MAX_PNG_AREA_PX`)\n- On rejection, the tool returns suggested sub-range splits\n\nSee [docs/RECALC.md](docs/RECALC.md) for architecture details.\n\n---\n\n## Example\n\n**Request:** Profile a detected region\n\n```json\n{\n  \"tool\": \"table_profile\",\n  \"arguments\": {\n    \"workbook_id\": \"wb-23456789ab\",\n    \"sheet_name\": \"Q1 Actuals\",\n    \"region_id\": 1,\n    \"sample_size\": 10,\n    \"sample_mode\": \"distributed\"\n  }\n}\n```\n\n**Response:**\n\n```json\n{\n  \"sheet_name\": \"Q1 Actuals\",\n  \"headers\": [\"Date\", \"Category\", \"Amount\", \"Notes\"],\n  \"column_types\": [\n    { \"name\": \"Date\", \"inferred_type\": \"date\", \"nulls\": 0, \"distinct\": 87 },\n    { \"name\": \"Category\", \"inferred_type\": \"text\", \"nulls\": 2, \"distinct\": 12, \"top_values\": [\"Payroll\", \"Marketing\", \"Infrastructure\"] },\n    { \"name\": \"Amount\", \"inferred_type\": \"number\", \"nulls\": 0, \"min\": 150.0, \"max\": 84500.0, \"mean\": 12847.32 },\n    { \"name\": \"Notes\", \"inferred_type\": \"text\", \"nulls\": 45, \"distinct\": 38 }\n  ],\n  \"row_count\": 1247\n}\n```\n\nThe agent now knows column types, cardinality, and value distributions — without reading 1,247 rows.\n\n---\n\n## Development\n\n```bash\n# Run tests\ncargo test\n\n# Build all crates\ncargo build --release\n\n# Test local binary with an MCP client\n```\n\nPoint your MCP client config at the local binary:\n\n```json\n{\n  \"mcpServers\": {\n    \"spreadsheet\": {\n      \"command\": \"./target/release/spreadsheet-mcp\",\n      \"args\": [\"--workspace-root\", \"/path/to/workbooks\", \"--transport\", \"stdio\"]\n    }\n  }\n}\n```\n\nOr use the Docker rebuild script for live iteration:\n\n```bash\nWORKSPACE_ROOT=/path/to/workbooks ./scripts/local-docker-mcp.sh\n```\n\n---\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsu3d0%2Fspreadsheet-mcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpsu3d0%2Fspreadsheet-mcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpsu3d0%2Fspreadsheet-mcp/lists"}