logisky

logisheets-mcp

Community logisky
Updated

give your AI agent a spreadsheet it can actually think in: deterministic Excel-compatible math + structured memory (blocks), real .xlsx out. Open source, self-hostable.

logisheets-mcp

A real spreadsheet engine your AI agent can think in.

An MCP server that gives any LLM agent areal, Excel-compatible calculation engine — with structured memory it canaddress semantically, and a genuine .xlsx at the end that a human can open,audit, and keep using.

Built on LogiSheets, a spreadsheetengine written in Rust. MIT licensed, self-hostable, no cloud dependency.

Why

Agents are doing real work that is spreadsheet-shaped — financial models, datareconciliation, analysis — and they are bad at exactly the parts a spreadsheetengine is good at.

Arithmetic. Agents mis-sum and mis-multiply. Here they don't have to: theywrite a formula and a deterministic engine evaluates it.

Memory. Across a thirty-step task, intermediate state has to livesomewhere structured. A context window is lossy and expensive; a codesandbox's variables vanish. This server gives the agent an external structureddisk it reads and writes across the whole task.

Addressing. Agents are bad at spatial reasoning, so a raw grid is a fragilesurface — they lose track of where things are, and their own edits break theirreferences. So the agent doesn't address C7. It addresses(block, row_key, field):

set the price field of the 2025 record in the revenue block

Insert a row, move the block, add a column — that address still resolves. Thisis the whole point: memory that survives the agent's own edits.

vs. a Python sandbox

A code interpreter can compute, but you get a throwaway script result. Here youget a real .xlsx with live formulas still in it — open it in Excel, changean input, and the model recalculates. It round-trips the human's existing files,and it runs on your machine, which matters when the data can't leave.

Install

Requires Node 20+.

npm install -g logisheets-mcp

Claude Desktop

Add to claude_desktop_config.json:

{
    "mcpServers": {
        "logisheets": {
            "command": "npx",
            "args": ["-y", "logisheets-mcp"]
        }
    }
}

On macOS that file lives at~/Library/Application Support/Claude/claude_desktop_config.json; on Windows,%APPDATA%\Claude\claude_desktop_config.json. Restart Claude Desktopafterwards.

Cursor / Cline / other hosts

Any MCP host that can spawn a stdio server works — point it at thelogisheets-mcp command. For Cursor, add the same block to~/.cursor/mcp.json.

Try it

Build me a three-year revenue model: 100 units at $9.50 growing 40% a year,with a 30% cost of goods. Then save it to ~/model.xlsx.

The agent creates a block, fills it, writes the formulas, and hands back a file.The numbers are the engine's, not the model's guesses — and the .xlsx has realformulas in it, so you can change an assumption in Excel and watch it recompute.

See it work

npm run build && npm run demo

Builds a small revenue model over real MCP-on-stdio against dist/cli.js — thesame code path Claude Desktop drives — and checks every claim as it goes: totalsthe engine computed, a rule that reaches rows added later, blocks that keepresolving after the model grows underneath them, and a real .xlsx whoseformulas are verified by reading the file's own bytes. No LLM is involved; theengine is the subject, and hard-coding the calls is what makes the guaranteescheckable rather than a story about a chat session.

The agent loop

list_blocks                     orient: what do I have?
create_block                    open a structured workspace
add_block_rows / set_block_cells    fill it, addressed by (block, key, field)
eval_formula / a stored formula      the engine does the math
describe_block                  read structured results back
save_workbook                   hand the human a real .xlsx

Tools

The default surface is deliberately small — 20 tools. Tool-selection accuracyfalls as the list grows, and every description costs context on every turn.

Tool What it does
open_workbook Start a fresh workbook, or load an existing .xlsx from disk. Optional — one appears on first use.
save_workbook Write to a real .xlsx file. This is how work gets handed back.
export_xlsx The file as base64, for hosts with no shared filesystem.
list_blocks Every sheet and block, plus where the next block should go.
describe_block A block's schema, keys, and (optionally) its current values.
eval_formula Evaluate an Excel formula and return the value. Nothing is stored.
create_block Create a named, structured table. First field is the row key.
convert_to_block Turn a table that is already in ordinary cells into a block, in place.
add_block_rows Add records — at the end, or after_key / before_key to place them.
delete_block_rows Remove records.
move_block_row Reorder rows, by key. Presentation only: no computed value changes.
set_block_cells Write cells by (block, row_key, field). Batched, atomic.
set_field_rule Give a field a formula, a validation rule, or an editability rule.
list_violations Which cells break their field's validation rule, and why.
preview_changes What edits would do, without doing them. One hypothetical, or a whole grid of scenarios in a single call.
trace What a cell reads, and what reads it — from the engine's dependency graph.
goal_seek What input makes a chosen output equal a target. Searches inside the engine; changes nothing.
create_sheet Add a sheet.
get_cells / set_cells Raw-cell escape hatch for data with no structure.

Formulas are Excel-compatible, plus BLOCKREF(block, key, field) for reading ablock cell semantically. Inside a field rule, #FIELD("name") is the same row'ssibling and #FIELD("name", "key") is another row of the same block — the onecarrying that key, never a positional offset, so reordering rows cannot changewhat a formula means.

Analysing a model, not just building one

preview_changes takes a list of scenarios and an optional watch, which iswhat turns exploration from dozens of round trips into one:

{
  "scenarios": [
    {"label": "wacc 9%",  "changes": [{"block":"assum","row_key":"wacc","field":"v","value":0.09}]},
    {"label": "wacc 12%", "changes": [{"block":"assum","row_key":"wacc","field":"v","value":0.12}]}
  ],
  "watch": [{"block":"valuation","row_key":"per_share","field":"v"}]
}

Each scenario runs on its own temp branch and is discarded, so the live model isnever touched — no mutate-and-revert, and nothing left behind if a scan failshalf way. A 4×4 sensitivity grid is one call returning sixteen numbers.

goal_seek runs the same trick backwards — "what discount rate gives a value pershare of 30" — with the search inside the engine rather than as a conversation,so it is one call instead of one per bisection step. It says when a target issimply not reachable in the bracket instead of returning the nearest number ithappened to stop on.

trace answers the two audit questions from the engine's dependency graph:what a cell reads, and what reads it. The second one is why it exists — formulatext can be read forwards but not backwards, and "what breaks if I change this"is the question you want before touching an assumption.

Reading a model is semantic too: describe_block returns each field's rule, soan agent learns the model's logic without visiting a cell, and formulas come backnaming what they read (B24 / BLOCKREF("assum","shares","v")) rather than ascoordinate chains you have to chase.

The full surface

Set LOGISHEETS_MCP_TOOLS=full for 50 tools: undo/redo, cell formatting,merges, comments, checkpoints, block move/resize, cross-block links, and rawrow/column structure.

{
    "mcpServers": {
        "logisheets": {
            "command": "npx",
            "args": ["-y", "logisheets-mcp"],
            "env": {"LOGISHEETS_MCP_TOOLS": "full"}
        }
    }
}

Mutating tools are marked with MCP's readOnlyHint / destructiveHintannotations, so a host can gate them behind user approval.

Blocks, briefly

A block is a named, structured region of a sheet — a table with a schema.

  • The first field is the row key: the stable name of each record.
  • Fields can carry a value formula (engine-computed, so the agent can'twrite a stale number into it), a validation rule, or an editabilityrule.
  • Everything is addressed by name. Row and column indices never enter theagent's reasoning.

Because blocks are created by the agent as it works, this needs nopre-prepared file — you can point it at a blank workbook or at a spreadsheetsomeone sent you.

Use as a library

import {createServer} from 'logisheets-mcp'
import {StreamableHTTPServerTransport} from '@modelcontextprotocol/sdk/server/streamableHttp.js'

const {server, session} = createServer({mode: 'full'})
await server.connect(new StreamableHTTPServerTransport(/* … */))

createServer returns the MCP Server, the WorkbookSession, and the tool map,so you can host it over any transport or embed it in an agent framework.

Development

The server is a thin shell over three LogiSheets packages:logisheets-runtime (theheadless engine), logisheets-logician (the agent tool definitions), and theRust/WASM core. Working on the server alone needs nothing special:

git clone https://github.com/logisky/logisheets-mcp.git
cd logisheets-mcp
npm install
npm test

Working on the engine at the same time is the other mode. Check outLogiSheets as a sibling directory,build its packages, then:

npm run link:local     # re-run after any npm install

That symlinks the three packages into node_modules so local engine changestake effect without reinstalling. scripts/release-deps.mjs puts the registryranges back before publishing.

Releasing

A tag does it. .github/workflows/publish.yaml runs the tests, publishes tonpm with provenance, and registers the new version with the MCP Registry:

npm version 0.2.0        # bumps both files, commits, tags v0.2.0
npm run check-release    # optional; CI runs it too
git push --follow-tags

The workflow can also be run by hand from the Actions tab, which takes theversion from package.json instead of a tag. The npm step skips a version thatis already published, so a run that failed at the registry step can just bere-run — the two publishes are not a transaction.

npm version also rewrites server.json, via the version lifecycle script.The registry keeps the version in two places — the server's own version andthe version of the npm package it points at — and hand-editing them is the stepmost likely to be missed.

check-release is the gate. Four things have to agree: the tag, package.json,and both server.json version fields. mcpName also has to equalserver.json's name, because the registry proves ownership by readingmcpName out of the published npm package. npm publish cannot be undone —a version number is spent the moment it lands — so the workflow runs this checkbefore publishing, not after.

Registry auth needs no secret: the workflow authenticates with GitHub OIDC,which is what grants the io.github.logisky/ namespace. The one secret isNPM_TOKEN.

Getting the file back

save_workbook writes a real .xlsx and its result carries an MCPresource link — a uri, media type and size — not the file. The workbook isalso listed as a resource (workbook://current.xlsx), so a host that wants thebytes reads them with resources/read and hands the human a download.

That split is the point: a tool result goes into the model's context, where a200 KB workbook would cost roughly 280 KB of text and teach the model nothing.The link costs a line. export_xlsx still returns base64 for hosts thatimplement no resources at all, but it is the fallback, not the mechanism.

Reads go through the same serialization lane as tool calls, so a host fetchingthe file can never catch a half-applied transaction.

open_workbook and save_workbook read and write wherever the server processcan — normal for a local stdio server, and the same posture as the officialfilesystem server. Both are marked as mutating so a host can prompt before theyrun; if you need tighter limits, run the server as a user with only the accessyou intend it to have.

State model

One MCP session holds one active workbook, alive across tool calls — thatpersistence is what makes it memory rather than a calculator. open_workbookreplaces it. Multiple named workbooks per session may come later.

No network

The server opens no sockets and listens on no ports. "stdio transport" isliteral: your MCP host spawns this as a child process and they exchangenewline-delimited JSON-RPC over its stdin and stdout — the same pipes anycommand-line program gets. The engine is WASM running in that same process,so a formula is a function call, not a request.

Checked rather than asserted. After a full session — create a block, attach afield rule, evaluate a formula, save an .xlsx — the process holds:

fd types: {CHR: 2, DIR: 4, KQUEUE: 3, PIPE: 6, REG: 13}
network files (lsof -a -i):     0
unix sockets  (lsof -a -U):     0
listening ports:                0

Six pipes, no sockets. Nothing is uploaded, no telemetry is collected, and anair-gapped machine is a supported way to run this. The only things it touchesoutside its own memory are the files you name — see the filesystem note underGetting the file back.

That is the logisheets-mcp binary, which is what an MCP host runs. Using itas a library you can attach any transport you like,including an HTTP one — but then the socket is yours, opened deliberately.

License

MIT. Part of the LogiSheets project.

MCP Server · Populars

MCP Server · New

    leonardosepulvedat

    MCP n8n Server

    Complete n8n API integration for Claude Desktop and Cursor - 100 workflow templates with intelligent matching

    Community leonardosepulvedat
    maximhq

    Bifrost AI Gateway

    The Fastest LLM Gateway with built in OTel observability and MCP gateway

    Community maximhq
    crisnahine

    rails-ai-context

    45 MCP tools that give AI coding agents ground truth about your Rails app: schema, models, routes, controllers, views, jobs, conventions. Works with Claude Code, Cursor, GitHub Copilot, OpenCode and Codex CLI. MCP or CLI, in-Gemfile or standalone, and it still answers when the app can't boot.

    Community crisnahine
    VinvAI

    vinvai

    Vinv runs, tests, and finds issues in your services — with zero code changes.

    Community VinvAI
    api7

    AISIX AI Gateway

    An open source, Native AI Gateway and LLM proxy built in Rust

    Community api7