Google Sheets semantics, exactly.
A formula engine that matches Google Sheets, function for function. For Rust, JavaScript, Deno — and your AI agent.
MIT licensed — no GPL, no dual-licence conversation with legal.
Edit any cell — it recalculates here, live
Start here
Pick your runtime. Copy one line.
Same engine everywhere. Pick the one you already write in.
WebAssembly. Runs in the browser, Node and Deno — no server, no network.
npm
npm install @truecalc/coreimport { evaluate, list_functions } from '@truecalc/core';
evaluate('SUM(A1, B1)', { A1: 100, B1: 200 });
// => { type: 'number', value: 300 }
evaluate('IF(A1 > 0, "yes", "no")', { A1: 1 });
// => { type: 'text', value: 'yes' }
list_functions().length;
// => 518Node 24+ — on Node 22 run with `--experimental-wasm-modules`. Bundlers need a WASM plugin: vite-plugin-wasm for Vite, `experiments.asyncWebAssembly` for webpack 5.
The engine itself. Stateless, no allocator surprises, bring your own data.
cargo
cargo add truecalc-coreuse std::collections::HashMap;
use truecalc_core::{Engine, Value};
let engine = Engine::sheets();
let mut vars = HashMap::new();
vars.insert("A1".to_string(), Value::Number(100.0));
vars.insert("B1".to_string(), Value::Number(200.0));
let result = engine.evaluate("=SUM(A1,B1)", &vars);
assert_eq!(result, Value::Number(300.0));Errors are values, not `Err` — a division by zero returns `Value::Error(ErrorKind::DivByZero)`, exactly as a cell would.
Give Claude, Cursor or any MCP client a calc engine it cannot get wrong.
npm
npx -y @truecalc/mcp// claude mcp add truecalc -- npx -y @truecalc/mcp
// …or write it yourself, in .mcp.json:
{
"mcpServers": {
"truecalc": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@truecalc/mcp"]
}
}
}Twelve tools over stdio, including a stateful workbook your agent can keep across a whole conversation. No toolchain — npm resolves a prebuilt binary for your platform.
On JSR, and the only runtime that needs no permission flags at all.
deno
deno add jsr:@truecalc/coreimport { evaluate, createEngine } from '@truecalc/core';
evaluate('=SUM(A1,A2)', { A1: 10, A2: 20 });
// => { type: 'number', value: 30 }
createEngine('google-sheets').evaluate('=UPPER("ok")', {});
// => { type: 'text', value: 'OK' }No --allow-net, no --allow-read. The engine is compiled into the module graph, so `deno run` is the whole command.
A full spreadsheet in the browser. Nothing saved, nothing to sign up for.
Nothing to install.
A complete spreadsheet — 518 functions, multiple sheets, import and export — running entirely in your browser. Nothing is uploaded and nothing is saved to a server.
Why this is hard
Every spreadsheet engine is 95% right. The last 5% is the product.
Rounding, coercion, date edges, error propagation. The places engines quietly disagree are the places TrueCalc is measured.
Expected values come from Google Sheets itself, not from a reading of what it ought to do. They are fixed, and the engine is measured against them on every change.
That is the part that matters. An engine checked against its own output can only ever agree with itself.
| Formula | Result |
|---|---|
=ROUND(2.5, 0) | 3 |
| Sheets rounds half away from zero. Banker’s rounding — the IEEE 754 default, and what Python’s round() does — returns 2. | |
="10" + 5 | 15 |
| Text coerces to a number in arithmetic. It does not in a comparison: ="10" = 10 is FALSE. | |
=1/0 | #DIV/0! |
| An error is a value that flows onward through the sheet, not an exception that stops it. | |
=TEXT(DATE(2026, 13, 1), "yyyy-mm-dd") | 2027-01-01 |
| There is no month 13, so the date rolls forward into the next year rather than erroring. | |
- 518
- Functions
- 12,671 / 12,885
- Conformance cases pass
- MIT
- Licence
- v8.1.1
- Current release
For AI agents
Your agent should not be doing arithmetic in its head.
Models are confident and wrong about dates, calendars and compounding. Give yours twelve tools and a workbook that lasts the whole conversation.
claude mcp add truecalc -- npx -y @truecalc/mcpThe twelve tools
evaluateEvaluate a formula, with optional variable bindings.batch_evaluateEvaluate many formulas over one set of bindings.validateCheck that a formula parses before you trust it.explainDescribe a formula and list every function it reaches for.list_functionsThe whole catalogue, with category, syntax and description.get_statsFunction count, library version, per-category breakdown.workbook_createOpen a workbook. The engine flavour locks at creation.workbook_setWrite a value or a formula to a cell, in A1 notation.workbook_getRead a cell’s effective value, spills resolved.workbook_recalcRecalculate, and get back exactly which cells changed.workbook_exportExport the session as canonical JSON.workbook_importLoad a workbook from canonical JSON.
Reproducible by definition
A workbook is a value, not an application.
Canonical JSON in, canonical JSON out. Commit it, review it in a pull request, recompute it later — and get the same grid back, byte for byte.
The clock is an argument
NOW, TODAY and RAND read from a context you pass in — a timestamp, an IANA timezone and a seed. Same workbook plus same context, same answer, forever.
One schema, every surface
A workbook written in Rust deserializes unchanged in WebAssembly and in the MCP server. There is no second evaluator anywhere.
Timezones are vendored
The engine carries its own timezone tables rather than reading the host’s, so a machine with stale OS data cannot quietly change your results.
import init, { JsWorkbook } from '@truecalc/workbook';
await init();
const wb = new JsWorkbook('sheets');
wb.addSheet('Budget');
wb.set('Budget', 'A1', '1000');
wb.set('Budget', 'A2', '500');
wb.set('Budget', 'A3', '=SUM(A1:A2)');
// Pin the clock, the timezone and the seed.
wb.recalc(JSON.stringify({
timestamp_ms: 1780000000000,
timezone: 'UTC',
rng_seed: 0,
}));
// resolved() hands back a JSON string, not an object.
JSON.parse(wb.resolved('Budget', 'A3'));
// => { type: 'number', value: 1500 }
// Canonical JSON — RFC 8785. Diffable, committable, replayable.
const snapshot = wb.toJSON();Who reaches for this
Four ways in, one engine.
If you just need a spreadsheet
No account, no install, no licence. Open it, type in it, send the link. The formulas behave the way the ones in Google Sheets behave, because that is the whole point of the engine underneath.
Spreadsheets →If your users write formulas
Your users already know VLOOKUP. Embedding an engine that matches the sheet they came from is less work than reimplementing a hundred functions and defending the rounding.
Product teams →If a model is doing your arithmetic
Language models are confident and wrong about dates, business calendars and compounding. None of those live in a model’s weights. Hand it twelve tools over MCP and the arithmetic stops being a guess.
AI agents →If you have to prove the number
Canonical JSON in, canonical JSON out. Put the model in version control, review a change to it the way you review code, and recompute it later with a pinned clock, timezone and seed to get the same grid back.
Finance & audit →Install it in the next thirty seconds.
518 functions, MIT licensed, no account, no server. If a result disagrees with Google Sheets, that is a bug — open an issue and it becomes a fixture.
7,288
npm downloads in the last 30 daysas measured on 2026-08-19 — live figure unavailable
How this is counted
Summed across @truecalc/core, @truecalc/workbook and @truecalc/mcp — the three real packages.
The MCP server's five prebuilt platform binaries are excluded, not added: one `npx @truecalc/mcp` pulls the wrapper and one binary, so summing them counts each install twice.
crates.io and JSR are not folded in. A crates.io download is a per-version fetch dominated by CI and an npm download counts every install — the units are different, so a combined total would mean nothing.
- Conformance cases
- 12,885
- Published failures
- 214
- Version
- 8.1.1
- Licence
- MIT