MCP Spreadsheet Tool Server For WorkPaper Agents
June 4, 2026 ยท View on GitHub
This page is for agent builders who want workbook formulas behind a Model Context Protocol surface. The useful boundary is small: list the tools, read the workbook context resources, invoke a reusable workflow prompt, call one tool, return exact cell readback, and include enough structured output for the agent to verify the edit.
@bilig/workpaper is the public agent-facing package for WorkPaper MCP. MCP
stays as the transport and discovery layer around ordinary Node functions; the
lower-level runtime implementation still lives in @bilig/headless.
If you need the short agent decision path before the protocol details, start with the headless WorkPaper agent handbook.
Runnable MCP-Style Example
Run the dependency-free example from a clean checkout:
git clone https://github.com/proompteng/bilig.git
cd bilig
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-tools
For a local stdio transport, pipe newline-delimited JSON-RPC requests into the stdio entrypoint:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' |
npm run --silent agent:mcp-stdio
Copy-Paste JSON-RPC Transcript
Use the maintained transcript smoke when reviewing the server from an MCP client, directory submission, or HN-style launch thread:
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-transcript
The script starts the stdio server, sends initialize, tools/list, and
tools/call, parses the JSON-RPC responses, asserts the formula readback, and
prints a compact transcript summary. The important response is the tools/call
result. A passing run returns structured content like this:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"structuredContent": {
"editedCell": "Inputs!B3",
"before": {
"expectedCustomers": 5,
"expectedArr": 60000,
"expansionArr": 66000,
"targetGap": -34000
},
"after": {
"expectedCustomers": 8,
"expectedArr": 96000,
"expansionArr": 105600,
"targetGap": 5600
},
"restored": {
"expectedCustomers": 8,
"expectedArr": 96000,
"expansionArr": 105600,
"targetGap": 5600
},
"formulaContracts": {
"expectedCustomers": "=Inputs!B2*Inputs!B3",
"expectedArr": "=B2*Inputs!B4",
"expansionArr": "=B3*Inputs!B5",
"targetGap": "=B4-100000"
},
"checks": {
"previousValue": 0.25,
"newValue": 0.4,
"formulasPersisted": true,
"restoredMatchesAfter": true,
"expectedArrChanged": true,
"serializedBytes": 1163
}
},
"isError": false
}
}
That single response proves the tool changed one input cell, recalculated dependent formulas, preserved the formulas through WorkPaper JSON serialization, restored the document, and returned machine-checkable readback.
MCP Inspector Smoke
Use the official MCP Inspector when a directory reviewer, agent host, or
cautious local user wants to inspect the packaged stdio server with a neutral
MCP client before adding it to Cursor, Claude, VS Code, or another tool host.
The Inspector project documents this command shape at
https://modelcontextprotocol.io/docs/tools/inspector and the current package
is @modelcontextprotocol/inspector.
List the default demo tools:
npx -y @modelcontextprotocol/inspector@latest --cli \
npm exec --yes --package @bilig/workpaper@latest -- bilig-workpaper-mcp \
--method tools/list
Expected tool names:
read_workpaper_summary
set_workpaper_input_cell
Then call the write/readback tool:
npx -y @modelcontextprotocol/inspector@latest --cli \
npm exec --yes --package @bilig/workpaper@latest -- bilig-workpaper-mcp \
--method tools/call \
--tool-name set_workpaper_input_cell \
--tool-arg sheetName=Inputs \
--tool-arg address=B3 \
--tool-arg value=0.4
The useful output is the structuredContent object. A passing Inspector call
returns this shape:
{
"editedCell": "Inputs!B3",
"before": { "expectedArr": 60000 },
"after": { "expectedArr": 96000 },
"restored": { "expectedArr": 96000 },
"checks": {
"formulasPersisted": true,
"restoredMatchesAfter": true,
"expectedArrChanged": true,
"serializedBytes": 1162
}
}
The top-level Inspector result should also report "isError": false.
This Inspector smoke launches default demo mode, which intentionally has two
demo tools. For private workbook files and persistent project state, use the
file-backed config in .mcp.json, .cursor/mcp.json, .vscode/mcp.json, or
mcp/bilig-workpaper.mcp.json; that mode exposes the eight general WorkPaper
tools listed below.
Keep the Inspector proxy on localhost. It can spawn local processes, so do not expose it to untrusted networks or disable its auth token for ordinary inspection.
If you want the raw newline-delimited JSON-RPC request stream instead of the maintained transcript wrapper, use:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"set_workpaper_input_cell","arguments":{"sheetName":"Inputs","address":"B3","value":0.4}}}' |
NODE_NO_WARNINGS=1 npm run --silent agent:mcp-stdio
The npm package exposes the demo server as bilig-workpaper-mcp by default:
npm exec --package @bilig/workpaper@latest -- bilig-workpaper-mcp
Cursor demo server config
For a Cursor smoke that matches the lower-level runtime package, add this
server to .cursor/mcp.json:
{
"mcpServers": {
"bilig-workpaper": {
"command": "npm",
"args": ["exec", "--yes", "--package", "@bilig/headless@latest", "--", "bilig-workpaper-mcp"]
}
}
}
That default demo mode exposes two tools: read_workpaper_summary and
set_workpaper_input_cell.
Ask Cursor for a concrete write/readback proof:
Use the `bilig-workpaper` MCP server. Read `Summary!A1:B5`, then set
`Inputs!B3` to `0.4` with `set_workpaper_input_cell`, and report
`expectedArr` before and after.
A passing result reports expectedArr 60000 before the edit and 96000
after the edit. Use the file-backed config below when Cursor needs persistent
project WorkPaper state instead of the packaged demo workbook.
For Cursor, use the project-local .cursor/mcp.json shape in the
MCP client setup guide. That setup uses
@bilig/workpaper@latest in file-backed mode, so Cursor sees the general
WorkPaper tools such as list_sheets, read_range,
set_cell_contents_and_readback, and export_workpaper_document.
Remote Stateless Endpoint
The hosted app runtime also exposes a JSON-only Streamable HTTP MCP endpoint for clients that cannot launch a local stdio process:
https://bilig.proompteng.ai/mcp
There is also a compatibility alias:
https://bilig.proompteng.ai/mcp/workpaper
The endpoint is stateless and request-local. It loads the packaged demo
WorkPaper for each JSON-RPC request, exposes the same file-backed tool catalog,
resources, and prompts, and returns write/readback proof without writing user
files or issuing an MCP session id. Use set_cell_contents_and_readback when a
hosted client needs to write one input and read dependent formula output in the
same request. Use local file-backed stdio when an agent needs to persist a real
project WorkPaper JSON file.
Protocol smoke:
curl -fsS https://bilig.proompteng.ai/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2025-11-25' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
For server-to-server clients, omit Origin. Browser-based clients must send an
allowed Origin; Claude and ChatGPT origins such as https://chatgpt.com are
allowed by default. For ChatGPT Developer Mode setup, use the
ChatGPT Apps WorkPaper MCP page.
For a real agent workflow, point the same binary at a persisted WorkPaper JSON document:
npm exec --package @bilig/workpaper@latest -- bilig-mcp-challenge --json
npm exec --package @bilig/workpaper@latest -- bilig-workpaper-mcp --workpaper ./pricing.workpaper.json --init-demo-workpaper --writable
bilig-mcp-challenge is the one-command evaluator path. It initializes the
file-backed MCP server, lists tools/resources/prompts, edits Inputs!B3, reads
recalculated Summary!B3, exports WorkPaper JSON, restarts from disk, and
prints verified: true.
File-backed mode loads ./pricing.workpaper.json, exposes list_sheets,
read_range, read_cell, set_cell_contents,
set_cell_contents_and_readback, get_cell_display_value,
export_workpaper_document, and validate_formula, then writes the updated
WorkPaper JSON back to the same file after set_cell_contents or
set_cell_contents_and_readback when --writable is present. It also exposes
resources/list, resources/read,
prompts/list, and prompts/get so clients can discover the live workbook
manifest, agent handoff instructions, current document JSON, and reusable edit
or formula-debug prompts. Omit --writable for read-only inspection.
The high-signal runtime resources are:
bilig://workpaper/manifestbilig://workpaper/agent-handoffbilig://workpaper/sheetsbilig://workpaper/current-document
The reusable prompts are:
edit_and_verify_workpaperdebug_workpaper_formula
Every file-backed tool includes an MCP outputSchema, parameter descriptions,
and safety annotations (readOnlyHint, destructiveHint, idempotentHint,
and openWorldHint). That is deliberate: directory scanners and coding agents
should be able to pick the workbook read, write, display, export, or formula
validation tool without treating the description as a vague demo.
Use the maintained file-backed transcript when a directory reviewer or agent builder needs proof that the packaged binary mutates a real WorkPaper JSON file:
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-file-transcript
A passing run starts npm exec --package @bilig/workpaper@latest -- bilig-workpaper-mcp --workpaper pricing.workpaper.json --init-demo-workpaper --writable, lists the
file-backed tool surface, writes Inputs!B3, persists the JSON file, reads
Summary!B3, and asserts that the recalculated value is 96000.
XLSX Risk Preflight Before Edits
When the agent starts from a real .xlsx, run the file-backed XLSX path before
opening Excel, LibreOffice, Google Sheets, or a browser grid:
pnpm --dir examples/headless-workpaper install --ignore-workspace
pnpm --dir examples/headless-workpaper run agent:mcp-xlsx-risk-preflight
The example starts the published binary this way:
npm exec --package @bilig/workpaper@latest -- \
bilig-workpaper-mcp \
--from-xlsx pricing-risk-preflight.xlsx \
--workpaper pricing-risk-preflight.workpaper.json \
--writable
For an agent or MCP client, the required JSON-RPC sequence is:
initializetools/listtools/callanalyze_workbook_riskwithinspectLimit: "all"tools/callset_cell_contents_and_readbackfor one small input edittools/callexport_workpaper_document
The maintained transcript proves the sequence with
schemaVersion: "bilig-agent-xlsx-risk-preflight.v1",
analyze_workbook_risk, Inputs!B3, Summary!B3, 60000 -> 96000,
restoredReadbackMatchesAfter: true, exported WorkPaper JSON, and
verified: true.
This preflight is local and read-only until a write tool is called. It reports
workbook risk indicators and keeps excelParity: "not_proven"; it does not
certify Excel compatibility.
Docker Target For Directory Introspection
MCP directories such as Glama need to start the server and run tools/list
without cloning the monorepo or building the web app. The root Dockerfile keeps
the production web image as --target bilig-runtime and adds a separate MCP
target for directory scanners:
docker build --target bilig-workpaper-mcp -t bilig-workpaper-mcp:local .
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize"}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' |
docker run --rm -i bilig-workpaper-mcp:local
The target installs @bilig/workpaper from npm, seeds
/workpaper/pricing.workpaper.json, and starts
bilig-workpaper-mcp --workpaper /workpaper/pricing.workpaper.json --init-demo-workpaper --writable
over stdio. That makes directory introspection see the general WorkPaper tools:
list_sheets, read_range, read_cell, set_cell_contents,
set_cell_contents_and_readback, get_cell_display_value,
export_workpaper_document, and validate_formula.
It also carries the OCI label
io.modelcontextprotocol.server.name=io.github.proompteng/bilig-workpaper, so
registry and directory tooling can match the container target to the official
MCP Registry name.
For crawlers that cannot run Docker or stdio, the docs site also publishes a
static MCP server card at
https://proompteng.github.io/bilig/.well-known/mcp/server-card.json. The card
lists the same list_sheets, read_range, read_cell, set_cell_contents,
set_cell_contents_and_readback, get_cell_display_value,
export_workpaper_document, and validate_formula tools, plus the WorkPaper
resources and prompts, without requiring account auth or a live server
connection.
The hosted endpoint origin serves the same crawler-friendly card at
https://bilig.proompteng.ai/.well-known/mcp/server-card.json, with
streamable-http transport metadata for https://bilig.proompteng.ai/mcp.
That gives Smithery-style scanners a same-origin metadata path when they start
from the remote MCP URL rather than the documentation site.
The @bilig/workpaper package carries
mcpName: io.github.proompteng/bilig-workpaper and a matching server.json.
It is the canonical package metadata for the official MCP Registry entry
io.github.proompteng/bilig-workpaper:
https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.proompteng%2Fbilig-workpaper.
If you already know which client you want to use, start with the MCP client setup guide for Claude, Cursor, Junie, VS Code, and Codex config snippets.
If you are checking a directory listing or preparing one, use the MCP spreadsheet server directory status page for the canonical npm command, official Registry proof, Glama listing, and pending directory-review status.
Before submitting the server to an MCP registry, verify this repo-specific readiness checklist:
packages/workpaper/server.jsonexists and describes the packaged stdio server.packages/workpaper/package.jsonexposesbilig-workpaper-mcpinbin.packages/workpaper/package.jsonincludesmcpName: io.github.proompteng/bilig-workpaper.pnpm publish:runtime:checkpasses against the runtime packages.pnpm workpaper:smoke:externalpasses against packed local runtime packages.
Passing the checklist means the repository metadata and smoke checks are ready for registry submission; it does not mean the package has already been published.
Vercel AI SDK MCP Client Recipe
If your agent loop already uses the Vercel AI SDK, keep the MCP client thin and let the WorkPaper server own the spreadsheet reads and writes:
import { createMCPClient } from '@ai-sdk/mcp'
import { Experimental_StdioMCPTransport } from '@ai-sdk/mcp/mcp-stdio'
import { generateText } from 'ai'
const client = await createMCPClient({
transport: new Experimental_StdioMCPTransport({
command: 'npm',
args: [
'exec',
'--package',
'@bilig/workpaper@latest',
'--',
'bilig-workpaper-mcp',
'--workpaper',
'./pricing.workpaper.json',
'--init-demo-workpaper',
'--writable',
],
}),
})
try {
const tools = await client.tools()
const { text } = await generateText({
model: 'your-model',
tools,
prompt: [
'Read Summary!A1:B5 with read_range.',
'Then set Inputs!B3 to =0.4 with set_cell_contents_and_readback.',
'Use readbackRange Summary!A1:B5 and export the document.',
'Return editedCell, beforeReadback, afterReadback, persisted, and restoredReadbackMatchesAfter.',
].join('\n'),
})
console.log(text)
} finally {
await client.close()
}
The server command is bilig-workpaper-mcp; the npm exec --package @bilig/workpaper -- bilig-workpaper-mcp wrapper only resolves the published npm
package for a clean checkout. The stdio transport receives npm as the command
and the rest as args, so shell parsing does not sit between the AI SDK client
and the MCP server. The two tool calls prove the useful workflow: read a
formula-backed summary, set one input cell, and return computed before/after
readback.
Verify the docs links and discovery metadata after editing this page:
pnpm docs:discovery:check
The script implements the JSON-RPC methods needed for the file-backed WorkPaper agent surface:
tools/listreturnsread_workpaper_summaryandset_workpaper_input_cellwith JSON Schema inputs and MCP tool annotations.tools/callinvokes the requested WorkPaper tool and returns text content plus structured formula readback.resources/listandresources/readexpose the live WorkPaper manifest, sheet summary, current document JSON, and compact agent handoff.prompts/listandprompts/getexpose the edit-and-verify and formula-debug workflows as reusable client prompts.
The packaged binary has two tool sets:
- default demo mode:
read_workpaper_summaryandset_workpaper_input_cell - file-backed mode:
list_sheets,read_range,read_cell,set_cell_contents,set_cell_contents_and_readback,get_cell_display_value,export_workpaper_document, andvalidate_formula
The annotations are explicit for directory reviewers and cautious MCP clients:
read_workpaper_summary is read-only, idempotent, and closed-world.
set_workpaper_input_cell mutates the local WorkPaper state, is idempotent for
the same cell/value arguments, and is closed-world rather than a network or
filesystem tool.
In file-backed mode, set_cell_contents is annotated as destructive only when
the server starts with --writable.
MCP Stdio Troubleshooting
| Symptom | What to check |
|---|---|
Parse error response | Make sure each stdin line is valid JSON before it reaches the server. |
| No response appears | End each JSON-RPC message with a newline; the server waits for newline-delimited input. |
| Notification has no output | notifications/initialized is intentionally one-way and does not produce a JSON-RPC response. |
Invalid params or tool error | Check that tools/call includes a supported name and the required arguments for that tool. |
The example deliberately avoids an MCP SDK dependency so the workbook contract is visible. Put the same handlers behind stdio, HTTP, or your MCP SDK adapter when you wire it into a production agent host.
What A Passing Run Proves
The write tool edits Inputs!B3, recalculates dependent formulas, serializes
the WorkPaper document, restores it, and checks that formulas and computed
values survived the round trip:
{
"editedCell": "Inputs!B3",
"before": {
"expectedCustomers": 5,
"expectedArr": 60000,
"expansionArr": 66000,
"targetGap": -34000
},
"after": {
"expectedCustomers": 8,
"expectedArr": 96000,
"expansionArr": 105600,
"targetGap": 5600
},
"checks": {
"previousValue": 0.25,
"newValue": 0.4,
"formulasPersisted": true,
"restoredMatchesAfter": true,
"expectedArrChanged": true
}
}
That is the part spreadsheet agents need. A tool that only says "updated" is not enough. Return the edited address, previous value, new value, before/after computed values, formula contracts, and persistence proof.
Tool Boundary
Expose only the minimum useful surface first:
read_workpaper_summaryreads a bounded range and returns computed values plus serialized cell contents.set_workpaper_input_cellvalidates the sheet and A1 address before a write, then returns formula readback and persistence checks.- Everything outside that boundary stays in your MCP host: auth, transport, rate limits, logging, and user approval policy.
The official MCP specification describes tool discovery through tools/list,
tool invocation through tools/call, input schemas, and tool annotations:
https://modelcontextprotocol.io/specification/2025-11-25/server/tools.
It also defines server resources through resources/list and
resources/read, and reusable prompt templates through prompts/list and
prompts/get:
https://modelcontextprotocol.io/specification/2025-11-25/server/resources
and
https://modelcontextprotocol.io/specification/2025-11-25/server/prompts.
Files To Inspect
- MCP-style adapter script:
examples/headless-workpaper/mcp-tool-server.ts - stdio adapter script:
examples/headless-workpaper/mcp-stdio-server.ts - official MCP Registry entry:
io.github.proompteng/bilig-workpaper - example README:
examples/headless-workpaper/README.md#mcp-tool-server-shape - SDK-neutral tool-calling recipe:
docs/agent-workpaper-tool-calling-recipe.md - Vercel AI SDK and LangChain wrappers:
docs/vercel-ai-sdk-langchain-spreadsheet-tool.md
Feedback Thread
Use the MCP spreadsheet tool server discussion for adapter feedback. The open questions are deliberately concrete: stdio, HTTP/SSE, or SDK adapter next; which spreadsheet workflow should be proven next; and which structured fields every write tool should return.
When This Is A Good Fit
Use this pattern when an agent needs to edit a forecast, pricing workbook, quote approval rule, budget check, or service-side spreadsheet model and prove the formulas reacted. Keep the MCP layer thin, keep the workbook logic testable, and make every write return structured verification.
Start with the adapter command above. If it almost matches but a gap blocks adoption, use the adoption blocker form: https://github.com/proompteng/bilig/discussions/new?category=general.