codecalc — universal code & logic calculator for AI models
codecalc is an offline, self-hosted MCP server that gives an AI agent a calculator, a code runner, and a logic checker — so it gets a correct answer instead of a guessed one. It runs code in 31 languages, does exact symbolic math, solves SMT/logic problems, and measures complexity, all exposed as 52 MCP tools.
Fastest path: uvx 'codecalc[full]' setup --write registers codecalc with your MCP client automatically. New to MCP, or want more detail first? See QUICKSTART.md, or the Install section below.
Three things nobody else offers together cleanly:
- Offline-core — ships no model, no API key, no gateway, no telemetry.
The core opens no sockets; network access is opt-in and only where a
specific tool's job needs it (the Piston provider,
install_package, the runtime-update tools, executed code unlessno_net, and a one-time in-process grammar download on firstanalyze_complexity— full breakdown in the network-boundary table below). - Safe execution of untrusted code — an opt-in strict isolation boundary (gVisor+Docker on Linux, AppContainer on Windows) layered above the default rlimit sandbox, fail-closed and attested.
- Verification tools —
verify_translationproves a port to another language behaves identically,verify_optimizationproves an optimization preserved behavior, andz3_checkproves or refutes logic with an SMT solver.
When to use codecalc
Use it when you want a free, local, private, hardened code-runner and verifier that an MCP agent can call directly — no vendor account, no cloud spend, nothing leaving the machine except where a tool's job explicitly requires it.
Reach for something else when you want managed cloud scale instead of self-hosting (a hosted sandbox like E2B or Modal), or when you're not self-hosting at all and the model vendor's built-in code interpreter already covers what you need.
codecalc vs. the alternatives
codecalc is not a general cloud sandbox and not a vendor code interpreter. It overlaps with several things and beats them in only one narrow place — forcing a model to measure a claim instead of asserting it. Where that isn't what you need, one of these is the better tool, and this table says so plainly.
| You want… | Better fit | Why |
|---|---|---|
| To just run some Python/JS quickly, zero setup | Your model vendor's built-in interpreter | Already there, already sandboxed, nothing to install. Anthropic's code-execution tool has internet access "completely disabled" and cannot install packages at runtime; OpenAI's hosted containers have no outbound network access by default, with an org-level network_policy allowlist as an opt-in. Both return output artifacts by reference (Anthropic a file_id via the Files API, OpenAI a container_file_citation) rather than inline (Anthropic code-execution tool docs, https://platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool; OpenAI shell/container tool guide, https://developers.openai.com/api/docs/guides/tools-shell; both retrieved 2026-09-07) |
| Heavy or multi-tenant workloads, managed scale | A cloud sandbox (E2B, Modal, Daytona) | Per-tenant Firecracker/gVisor isolation codecalc does not claim by default |
| Pure arithmetic or symbolic math, nothing else | A small calculator or SymPy MCP | Lower token cost; none of the 31-language runtime machinery |
| A model to stop guessing numbers, equivalence, and speedups — locally, privately, with graded evidence | codecalc | Exact rationals, verify_translation/verify_optimization, and unenforced/grade honesty — offline, no account |
Do not reach for codecalc if you need multi-tenant or network-exposed isolation (its threat model is explicitly single-operator, local, stdio), if zero-setup convenience matters more than measurement, or if a hosted interpreter already covers your case. It earns its keep only when the correctness of the claim — not just "it ran" — is the point.
Install
Quickstart with codecalc setup
The fastest path to a working MCP connection, without reading the rest of this section:
uvx 'codecalc[full]' setup # prints what it would do — nothing on disk changes
uvx 'codecalc[full]' setup --write # applies it: merges your client's config, copies the skill
It detects which MCP client is installed (Claude Desktop, Claude Code,
Cursor, VS Code, Zed — pass --client=NAME if none or several are found),
reuses codecalc doctor's own backend/extras/grammar-cache checks, prints the
exact config block in that client's own JSON shape with absolute paths
already filled in, runs two real canaries (execute_code, evaluate_expression)
to prove the connection would work, and ends in one verdict: ready /
degraded / not-ready. --write is the only mode that changes anything —
it MERGES the codecalc entry into your existing client config (every other
server stays exactly as it was) and backs up the original to
<path>.codecalc-bak first. codecalc --help lists every subcommand.
[!NOTE] Published as
codecalc0.6.0 on PyPI (pip install codecalc) and thecodecalc-exec0.6.0 executor on crates.io (#91). Every release artifact carries a keyless sigstore build-provenance attestation — verify one withgh attestation verify <file> --repo The-40-Thieves/codecalc; PyPI wheels additionally carry PEP 740 attestations.
The published install (simplest — no build step, and what most people want):
uvx 'codecalc[full]' # run it directly, no environment to manage
# or
pip install 'codecalc[full]' # into your own virtualenv
From source, if you would rather build the executor yourself:
git clone https://github.com/The-40-Thieves/codecalc
cd codecalc
uv sync --all-extras # or: pip install -e '.[full]'
cargo build --release --manifest-path executor/Cargo.toml
mkdir -p bin # bin/ is gitignored, so a fresh clone has none
cp executor/target/release/codecalc-exec bin/
uv run codecalc doctor # verify: backend should read `rust`
Without the cargo build, everything still runs on the pure-Python fallback —
doctor will say so, and the network table below says what that costs.
Why [full]. The base install is the MCP surface and the sandbox executor:
31 language runtimes, sessions, packages, ~32 MB. The symbolic half — sympy and
z3 — is 88.6 MB measured, and a caller who only runs code should not download an
SMT solver to do it. So it is an extra:
| install | size | what you get |
|---|---|---|
codecalc | ~32 MB | execute_code, sessions, packages, complexity-free tools |
codecalc[symbolic] | +83 MB | evaluate_expression, solve, limits, truth tables, z3, units |
codecalc[parsing] | +5 MB installed, +89 MB fetched on first use | analyze_complexity via tree-sitter |
codecalc[full] | ~120 MB | everything |
Nothing fails silently: a tool whose extra is missing returns
{"ok": false, "error": "sympy is not installed. It ships in the 'symbolic' extra: pip install 'codecalc[symbolic]' ..."}, and codecalc doctor lists
which extras are present before you make a call.
Editions
Four names for the capability sets above, plus the two that live outside
pyproject.toml entirely — a Docker image and an opt-in isolation boundary.
The invariant that makes "edition" a meaningful word here: in the edition
that lists a tool, that tool is functional — never listed-but-missing-its-extra.
A tool an edition doesn't have returns the dependency_missing contract error
naming the extra that provides it (see above), not a silent failure or a
tool that appears to exist and doesn't work.
| Edition | Install | What you get |
|---|---|---|
| Full | uvx 'codecalc[full]' / pip install 'codecalc[full]' | The recommended local product: the native (Rust) executor, symbolic tools (evaluate_expression, solve_linear, z3_check, …), and parsing (analyze_complexity). Everything this README documents actually runs. |
| Core | uvx codecalc / pip install codecalc | Execution + non-symbolic tools only — the base install in the table above. Every symbolic/parsing tool is still listed by tools/list (MCP doesn't support per-install schemas), but calling one returns dependency_missing naming the extra, before any other work happens. |
| Docker | docker build -f docker/mcp-server.Dockerfile . | The MCP server itself, packaged to run as an ordinary container. Core-shaped by default: ships python3/node/ruby/php/perl/gawk/lua/c/cpp/jq/sqlite3 and the default rlimit sandbox — symbolic/parsing are absent by design (no [full] in the base image; see the Dockerfile's own comment for why, including an arm64 z3-solver wheel gap). --build-arg CODECALC_EXTRA=full adds them. This image cannot nest the Strict Host boundary below inside itself (no privileged docker-in-docker), and codecalc doctor inside it says so rather than claiming a boundary it doesn't have. |
| Strict Host | opt-in; CODECALC_STRICT_URL (client) or the gVisor+Docker host itself (server) — see docs/deployment/README.md | Not an install, a boundary: the gVisor runsc sandbox on Linux, or AppContainer hardening on Windows, layered above whichever install above is already running. Fails closed — no digest pinned, no fallback to unenforced local execution. |
codecalc doctor reports which of these you're actually running (backend,
extras present, strict_runtime prerequisites) — read it before assuming a
capability rather than after a tool call surprises you.
.github/workflows/release.yml publishes a platform-tagged wheel per target
(Linux x86_64/aarch64 musl, macOS x86_64/aarch64, Windows x86_64), each
carrying the matching codecalc-exec binary and — where the platform has
one — its --no-net shim, so executor.backend() == "rust" on install
without a manual build step. No wheel for your platform, or installed from
source instead? Everything still runs; see the network table above for what
falls back and to unenforced in that case.
Point an MCP client at the installed command. The key differs by client —
mcpServers for most, servers for VS Code, context_servers for Zed — so
these are given separately rather than as one snippet to adapt:
Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json
(macOS), %APPDATA%\Claude\claude_desktop_config.json (Windows) · Cursor
(.cursor/mcp.json) and Claude Code (.mcp.json) use the same shape:
{ "mcpServers": { "codecalc": { "command": "uvx", "args": ["codecalc[full]"] } } }
VS Code — .vscode/mcp.json, top-level key is servers:
{ "servers": { "codecalc": { "command": "uvx", "args": ["codecalc[full]"] } } }
Zed — ~/.config/zed/settings.json, key is context_servers:
{ "context_servers": { "codecalc": { "command": "uvx", "args": ["codecalc[full]"], "env": {} } } }
Windows paths need doubled backslashes in JSON. If you installed into a venv
rather than using uvx, point at the interpreter directly:
{ "mcpServers": { "codecalc": {
"command": "C:\\path\\to\\venv\\Scripts\\python.exe",
"args": ["-m", "codecalc"] } } }
Run codecalc doctor to print a config block with the absolute paths of your
install already filled in.
Install the skill too. The tools cannot help a model that never reaches for
them — a model confident about 0.1 + 0.2 does not feel uncertain, it feels
finished. codecalc/SKILL.md ships inside the package and says when calling is
mandatory (any non-integer, any comparison you will state, anything past 2^53,
any number stated as a claim), when it is noise (2 + 3 + 4 needs no tool), and
how results must be reported — passed: true means "equivalent on N inputs",
never "verified". codecalc doctor prints its path; copy it into your client's
skills directory. check_claims.py gates it, so it cannot name a tool that does
not exist or a field no tool returns.
Not sure what your install actually resolved? Ask it, rather than finding out from a tool call later:
codecalc doctor # or: python -m codecalc doctor
This is the install verification step. It exits 0 when the install can
execute — a writable workspace and a resolved backend — and 1 when it cannot,
so it works unchanged in a Dockerfile, a provisioning script or a CI job. A
missing optional extra or an uninstalled Haskell does not fail it: those are
facts about the host, not a broken install, and a check that goes red for them
is one people learn to ignore.
It prints the execution backend and the binary behind it, whether installs are
confined, the status of every one of the 31 runtimes, whether the workspace is
writable, and a client config block with absolute paths filled in. All of that
is otherwise discoverable only by making a tool call and reading backend,
unenforced, or a failure.
codecalc doctor --json # the same report, for scripts
codecalc doctor --deep # actually RUN each runtime, and read its version
--json emits the report and nothing else, against a published schema
(docs/contract/doctor-v1.schema.json)
carrying the same contract_version and the same policy as a tool result.
Each runtime reports one of four states, and the difference between two of them is which measurement was actually taken:
| state | means |
|---|---|
supported | codecalc knows the language; nothing for it resolves here |
installed | its command resolves and is executable — not run |
unhealthy | resolves but cannot run, or was run and failed |
available | actually executed here and answered — --deep only |
status_basis says which pass produced them. Without --deep nothing is ever
reported available, because nothing was executed, and claiming otherwise for a
binary that was merely found on PATH would be a stronger measurement than was
taken.
Building the Rust core yourself, or running from a checkout? See "Build the Rust core" and "Run the server" below.
Use it from an MCP client
One-click install: both buttons register uvx codecalc[full] (the
recommended Full edition) and require uv to be installed.
The shortest version of the config above — this registers codecalc as a
stdio MCP server. The console entry point is codecalc, so uvx codecalc
launches it directly:
{
"mcpServers": {
"codecalc": { "command": "uvx", "args": ["codecalc"] }
}
}
Installed with pip install codecalc instead? Point at the resolved
command with no args:
{
"mcpServers": {
"codecalc": { "command": "codecalc" }
}
}
Network boundary
CodeCalc's core opens no sockets. No model gateway or telemetry is built
in. tests/test_offline.py asserts this for the top-level core modules. The
opt-in Piston provider is the deliberate exception: its wire client lives under
codecalc/provider_adapters/ and is registered only when
CODECALC_PISTON_URL is configured.
That is a claim about the package, not about every tool call, and the difference is worth stating rather than leaving a reader to discover:
| layer | reaches the network? |
|---|---|
| CodeCalc core | No HTTP client, model gateway, or telemetry. One dependency exception: analyze_complexity may download a grammar on first use (see below) |
| configured Piston provider | Yes, explicitly. Calls only the operator-supplied CODECALC_PISTON_URL; credentials stay in its authorization header and are redacted from results |
install_package | Yes, by design. It runs uv / npm / gem / cargo, which fetch from their registries. Installer hooks also run outside the sandbox — see SECURITY.md |
runtimes_status, update_runtimes | Yes. They shell out to mise / rustup / swiftly / npm, which check remote versions |
| code you execute | Yes, unless no_net=True — and that guarantee needs the native executor (seccomp-bpf where the Linux kernel supports it, a symbol shim otherwise; see the guarantee table below), so the pure-Python fallback reports it in unenforced instead of applying it. Set CODECALC_REQUIRE_NATIVE=1 to turn "fallback in use" into a startup failure instead of a result you have to notice by reading unenforced |
execute_code / session_run with declared dependencies | Yes, before the sandboxed step, through the confined install_package path. A PEP 723 block (python3) or the dependencies argument is installed BEFORE the code runs — never inside the sandbox — and refused (capability_not_requested, no fetch attempted) when no_net=True was requested or the capability policy denies or strictly limits network |
These distinctions are stated precisely on purpose: a guarantee described more broadly than it is enforced is exactly the failure mode this project works to avoid, so "offline-core" is scoped to what the structural test can actually support rather than claimed as a blanket "no network calls".
A PEP 723 block alone, with no dependencies argument, can trigger the
install above. execute_code/session_run read the block out of the source
text itself — a caller who passes no dependencies argument at all still gets
a confined uv/npm subprocess and real egress if the code they submit
happens to carry a # /// script block, whenever the refusal rule above does
not apply. This is logged distinctly (dependency_install_implicit in the
audit trail, alongside install_denied) so an operator can tell "source text
alone triggered this" from an explicit install_package/dependencies= call.
To disable it: no_net=True on the call, or a deny-network/strict
CODECALC_CAPABILITY_POLICY — either one refuses before any fetch, block or
no block. execute_code_stream, run_submit, and compare_execution never
read this block at all: none of them accept a dependencies argument, so a
# /// script block in code passed to any of them is inert text, not a
trigger.
Two ceilings govern a dependency-bearing run, not one. The run's own
timeout bounds the sandboxed step; it says nothing about installing
dependencies FIRST, outside the sandbox. A separate, fixed budget
(codecalc.dependencies.DEFAULT_DEPENDENCY_INSTALL_BUDGET_SECONDS, 120s,
aggregate across every dependency of one run) bounds that step instead —
exceeding it refuses the run with a stamped timeout naming the budget,
before the run's own timeout clock even starts. A sessionless run's
dependency workdir is also held to a disk quota — reusing
CODECALC_SESSION_DISK_QUOTA_MB (below), the same cap a session workspace
already has — and a run that grows past it after a successful install is
refused with a stamped resource_exhausted naming the measured size and the
cap.
The grammar download, stated plainly, because it is the one that is easy to
miss. The other three paths above go through a CHILD PROCESS, which is what
tests/test_offline.py says it cannot see. This one does not:
tree-sitter-language-pack ships a ~5 MB extension and fetches each grammar on
first use, in-process, into a local cache — 28 grammars, 89 MB, about 15
seconds on a cold cache. So the first analyze_complexity call for a given
language opens a socket from inside the server.
It is verified (the pack checks a signature and raises on a checksum mismatch), it is cached, and it never happens again for that language. So the offline-core claim is scoped to steady state: this first-use grammar fetch is the one in-process exception, which is why it is called out here rather than glossed over.
For an offline or egress-restricted install, warm the cache first — it is one
command, and afterwards nothing here reaches the network. If you installed
codecalc (pip install/uvx, not a source checkout), scripts/ did not come
with it, so use the shipped console script instead:
codecalc-prefetch-grammars # installed: fetch all 28 grammars
codecalc-prefetch-grammars --print-cache-dir # installed: the directory to copy
Building from source? The script still works and calls the same code:
python scripts/prefetch_grammars.py # fetch all 28 grammars
python scripts/prefetch_grammars.py --print-cache-dir # the directory to copy
codecalc doctor reports whether that cache is populated, so this is
discoverable before it matters rather than after a tool call degrades.
Architecture (language-per-strength)
| Layer | Language | Why |
|---|---|---|
Executor core (executor/) | Rust | Sandbox + rlimits + process-group kill + JSON CLI. No eval() anywhere near user input; memory-safe host; single static binary |
Logic layer (codecalc/logic.py) | Python | sympy (symbolic math, equation solving) and z3 (SMT) have no Rust equivalents |
MCP server (codecalc/server.py) | Python | the official mcp SDK (2.0) generates tool schemas from type hints; protocol 2026-07-28 |
Python orchestrates; Rust executes; sympy/z3 reason. Each layer does what it's best at. The Rust binary is preferred automatically; a pure-Python executor is the fallback if the binary is missing.
Older-computer support
- No modern instruction-set requirements — rustc targets a generic CPU by
default and nothing overrides it. (
executor/.cargo/config.tomlexplains why-C target-cpu=genericis deliberately NOT written there: it would be a no-op that reads like a guarantee.) - Static musl builds run on any Linux regardless of glibc version:
bin/codecalc-exec-x86_64-musl,bin/codecalc-exec-aarch64-musl(~430K each; the exact size moves with every toolchain bump, so it is not pinned here) - Size-optimized profile (
opt-level="z", LTO, panic=abort, stripped) — measured, not assumed: against an otherwise identicalopt-level=3build,zcame out 1.02 ± 0.26 times faster on the executor's own path (i.e. no detectable difference) while being 16% smaller. The executor spends its time in syscalls, not arithmetic, so there was nothing for a higher optimisation level to speed up. - Lazy sympy/z3 imports. Both are imported on first use, so a session that
only executes code never pays for them. This claimed "~40ms, not ~600ms" for a
long time while being wrong in both directions: the server took 1.9s to
start, and sympy was not actually lazy —
units.pyimported it at module scope andserver.pyimportsunits, so every start paid 437ms for it. Deferring that took spawn-to-first-response from 1888ms to 1243ms (measured, median of 7). The remaining ~870ms is themcpSDK's own import, which is not ours to remove. - The fork-bomb measurement is taken once, and only when it is needed.
Sizing
RLIMIT_NPROCmeans reading/proc/<pid>/statusfor every process on the machine. That walk used to run during argument parsing and again for every step: a C compile-and-run opened 1767 status files on a 590-process box to answer one question three times, and--lang notalanguagepaid the full cost to produce a one-line error. Measured lazily and cached, an error costs 1.1ms instead of 13.3ms and a compiled run 78ms instead of 104ms. list_languagesprobes runtime availability and reports which languages actually work on the machine (graceful degradation on minimal installs)
Build the Rust core
cd executor
cargo build --release # native
cargo zigbuild --release --target x86_64-unknown-linux-musl # static x86_64 (uses zig)
cargo zigbuild --release --target aarch64-unknown-linux-musl # static arm64
# Copy the executable AND its --no-net shim together. build.rs rebuilds the
# shim whenever blocknet.c changes, but the executor looks for it beside the
# BINARY, so installing only the binary leaves the previous shim in place — and
# a stale shim silently enforces the old policy while every "is it there?"
# check still passes. Copy both or neither.
mkdir -p ../bin # bin/ is gitignored, so a fresh clone has none
cp target/release/codecalc-exec target/release/blocknet.so ../bin/
Requires: Rust 1.97+, a C compiler for the --no-net shim (the build warns and
carries on without one; on macOS, or a Linux kernel without seccomp support,
--no-net then reports itself in unenforced rather than pretending — a
Linux kernel with seccomp support enforces it in-kernel either way), and
cargo-zigbuild
for the static cross-builds (zig is used as the linker; no x86_64 GCC needed).
MCP tools (52) + MCP resources
Every session file is also exposed as an MCP resource:
codecalc://session/<session_id>/files/<path> — images render inline for the
model, text returns as text, other files download.
Exact arithmetic & programmer-mode: exact rationals, threshold checks, bit analysis, binary64 introspection.
| Tool | Description |
|---|---|
calc_exact | EXACT arithmetic: 0.1+0.2 == 0.3 is True; arbitrary-precision ints, bitwise ops inline, whitelisted math funcs, pi/e/tau |
compare_threshold | Exact threshold verdict with shortfall: ('1/25', '>', '0.05') → False, shortfall 1/100 |
percentage | Exact share and percentage of PART/TOTAL (rationals accepted) |
calc_stats | mean, median, sample stdev, CV (CV > 0.2 = noise swamps the effect) |
percentiles | p50/p90/p95/p99 by nearest-rank AND interpolation; warns n<100 |
collision_probability | Birthday-bound hash collision: 1e5 items/32 bits ≈ 0.69, 1e6/64 ≈ 2.7e-8 |
data_sizes | Byte sizes both ways: KiB/MiB (binary) AND KB/MB (decimal) |
human_duration | Humanised duration + per-day/per-30d rates |
epoch_time | Epoch s/ms/µs/ns → ISO 8601 UTC, implausible readings suppressed |
base_repr | hex/oct/bin + two's complement at WIDTH + signed-overflow detection |
radix_convert | Any base 2..36, fractions included, non-termination flagged (0.1 base 2) |
float_repr | What binary64 actually stores: exact value, raw bits, ULP, neighbours, representable-or-not |
int_widths | Which i8..i64/u8..u64 hold N + wrapped values; 2^53 JS/JSON caveat |
bit_analysis | popcount, bit length, trailing zeros, next pow2, alignment padding |
bitop | Programmer mode: and/or/xor/nand/nor/xnor/not/shl/shr/sar/rol/ror at 8/16/32/64, unsigned+signed+hex+oct+bin; shr vs sar distinction; shift-overflow flagged |
algebraic_equiv | Are (a*b)/c and a*(b/c) identical? refactor verification (with float/truncation caveat) |
solve_expression | Solve roots/crossovers: x**2 - 4 = 0, 2*x + 1 = 7 |
limit_expression | Asymptotic limits: n*log(n)/n**2 → 0 (settles complexity arguments) |
simplify_expression | Simplified + factored + expanded forms |
Core tools
| Tool | Description |
|---|---|
list_languages | 31 languages with extension, compile flag, runtime availability |
list_execution_providers | Execution-provider identity, interface version, host class, and machine-readable capabilities |
execute_code | Run code in any language → stdout/stderr/exit_code/verdict (OK/TLE/MLE/OLE/RTE)/cpu_ms/peak_memory_kb; per-call limits (max_memory_mb, max_output_kb, max_cpu), no_net, compact. With a session and no explicit max_output_kb, oversized output spills into the session workspace (stdout_spill/stderr_spill) instead of just truncating |
execute_code_stream | Provider-selected execution using the same canonical limits as execute_code, with progress + partial output when the provider supports streaming |
run_submit | Submit code for background execution; returns a run_id immediately instead of holding the call open |
run_inspect | Poll a background run: status while running, the full execute_code result shape once terminal |
run_cancel | Cancel a background run; idempotent on an already-terminal run, honest about providers that cannot cancel mid-flight |
session_start | Persistent session; python3/node get a stateful REPL worker (variables/imports persist across calls), other languages a workspace dir |
session_stop / session_list | Session lifecycle |
session_files / session_read_file / session_write_file | Workspace file tools, jailed to the session dir; listings support page_size/cursor, and reads return images inline (as_image) |
session_run | Multi-file programs: execute an entry file that imports other session files (helper.py, data/...) in the workspace |
session_artifacts | List files created by executed code (results, images, CSVs) |
install_package | Install packages (uv pip/npm/gem/go/cargo...) into a session or shared cache |
verify_translation | Prove a port is equivalent: you write the translation, the executor runs both versions on the same inputs and reports match / diverged / inconclusive per input. A pass is graded cross_checked (see Grade vocabulary) |
verify_optimization | Prove an optimisation: you write the candidate, the executor confirms it still agrees with the original AND times both — accepted only if equivalent and measurably faster. Accepted is graded cross_checked |
extract_function | Pull a named function + its dependency closure (imports, referenced helpers) into a standalone program and run it (ast-exact for python3, best-effort elsewhere) |
compare_edge_cases | Run the same logic in N languages on edge-case inputs (empty, zero, negative, float precision) and flag behavioral divergence |
convert_units | Dimensional unit conversion via sympy: length, mass, time, speed, energy, power, force, pressure, temperature (°C/°F/K), volume, area, data, frequency |
physical_constants | 22 physical constants with values (c, h, N_A, k_B, G, g, m_e, R, ...) |
list_units | All 140+ unit aliases for convert_units |
evaluate_expression | Symbolic math: integrate(x**2, x), sqrt(144) + 2**10 |
truth_table | Boolean algebra: a and b or not c, p xor q, a implies b |
z3_check | SMT-LIB2 satisfiability + model. An unsat verdict is graded solver_proven; sat is graded ungraded (decided, but not proof-shaped — see Grade vocabulary) |
solve_linear | Systems of equations: x + y = 10; x - y = 2 |
matrix | Structured matrix ops: det/inverse/eigenvalues/transpose/rank/trace on a rows array — never a caller string through sympify, so evaluate_expression's [/] RCE screen never applies. Each entry screened individually |
analyze_complexity | Static Big-O estimate from code structure, parsed with tree-sitter (every supported language). Reports analysis: tree-sitter|regex-fallback so you can tell a parse from a guess |
benchmark | Empirical Big-O: runs code at increasing N, fits growth curve |
compare_execution | Same code across N languages side-by-side |
runtimes_status | Non-mutating update check: current vs latest for every language runtime, which package manager owns it, and the command that would run |
update_runtimes | Update runtimes. Dry-run by default (apply=False returns the commands); apply=True executes them |
Grade vocabulary
verify_translation, verify_optimization and z3_check return grade +
grade_basis (+ grade_rules_version) on top of their own result. The grade
names how strong the evidence for a success actually is; it is derived from
evidence those tools already emit, in codecalc/grades.py — the verifiers
never assign their own grade.
| Grade | Means | Emitted by |
|---|---|---|
cross_checked | Two independently authored programs were both actually run and their outputs agreed. grade_basis names the runtime(s) that did the checking. | verify_translation (source vs. port), verify_optimization (original vs. candidate) |
solver_proven | Z3 returned unsat within its timeout — a machine-checked refutation, not a heuristic. grade_basis names the engine version and the timeout bound. Not sat: see below. | z3_check |
executed | Reserved: the claimed computation ran and produced the reported result, with no independent second opinion. Not currently emitted by any tool above — every one of them also clears the cross_checked/solver_proven bar. | — |
ungraded | Explicit non-grade for a mismatch, an inconclusive comparison, a rejected optimisation candidate, a measurement failure, a Z3 unknown verdict, and — deliberately — a Z3 sat verdict. A real value on grade, never an absent key. Never a softened stand-in for one of the three grades above. | any of the above, on a non-success |
z3_check's sat verdicts are graded ungraded, not solver_proven, even
though sat is just as decisive a verdict as unsat. The ticket's motivating
pattern is proving a property P by asserting not-P and checking unsat; a
caller running that pattern who gets sat back has learned P is FALSE, and
solver_proven on that result would let a reader who skims grade without
result mistake a counterexample for a proof. sat's grade_basis says so
explicitly: satisfiability was decided, but solver_proven is reserved for
unsat so a counterexample can never wear a proof grade. Widening sat back
into solver_proven later is additive; narrowing it after callers depend on
the wider behaviour would not be, so this ships narrow now. Full reasoning:
codecalc/grades.py's module docstring.
algebraic_equiv is deliberately NOT graded: it compares two expressions via
sympy.simplify(a - b) == 0, a CAS transformation rather than a decision
procedure with a checkable certificate, and it is one simplifier's opinion
rather than two independent implementations agreeing. None of the three
grades describes that evidence honestly.
Runtime self-update
Every language is mapped to its package manager, and codecalc can update its own runtimes:
| Manager | Languages | Update command |
|---|---|---|
| mise | python3, node, bun, deno, ruby, go, erlang, elixir, gleam, zig, java, kotlin, sqlite, duckdb, gradle | mise up |
| rustup | rust (stable/nightly toolchains) | rustup update |
| swiftly | swift | swiftly update |
| apt | c, c++, fortran, csharp, php, perl, lua, tcl, r, jq, bash, zsh | apt-get install --only-upgrade (language packages only) |
| npm | typescript/tsc | npm update -g |
| uv | mojo | uv tool upgrade mojo |
| nix | haskell (on-demand) | nothing persistent |
runtimes_status is always safe. update_runtimes refuses to mutate unless
apply=True is passed explicitly — and it only touches the package manager
that owns each language (never the Rust sandbox, which has no update powers).
One of those managers is elevated: apt updates system packages, so its command
starts with sudo. apply=True is an argument a connected model controls, so
that branch takes a second key the model does not have — the host must set
CODECALC_ALLOW_RUNTIME_APPLY=1. Without it the apt command is reported as
skipped with ok: false and the variable named, while the unprivileged managers
still run. sudo -n already fails closed where a password is required; this
covers the passwordless-sudo rule common on developer machines and CI images,
which is exactly where -n does not stop it.
Run the server
cd /path/to/codecalc && .venv/bin/python -m codecalc.server
# stdio transport — register with any MCP client
# The identical tool/resource registry over stateless Streamable HTTP:
.venv/bin/python -m codecalc.server serve-http --host 127.0.0.1 --port 8000
Streamable HTTP binds to loopback by default. Bearer-token auth
(CODECALC_HTTP_TOKEN) is required for any non-loopback bind — serve-http
refuses to start on a routable address if the token is unset, and the token
comparison is constant-time — and optional on loopback, where an MCP
client spawning the process is already inside the trust boundary. Setting a
token does not change the single-operator threat model: put an authenticating
reverse proxy and the stronger process/container isolation described in
SECURITY.md in front of it before exposing it beyond one operator's own
machine.
Point an MCP client at it:
{ "mcpServers": { "codecalc": { "command": "/path/to/codecalc/.venv/bin/python",
"args": ["-m", "codecalc.server"],
"env": {
"PYTHONPATH": "/path/to/codecalc",
"CODECALC_RUNTIME_PATH": "/path/to/mise/shims:/usr/local/bin:/usr/bin:/bin"
} } } }
MCP protocol
Protocol revision 2026-07-28, on the official mcp SDK 2.0. Not fastmcp:
fastmcp 3.x pins mcp>=1.24,<2.0 and so cannot reach this revision at all.
Verifying that is less obvious than it looks. mcp.types.LATEST_PROTOCOL_VERSION
reads 2026-07-28 regardless of what a given connection negotiated, and the
same server answers on either protocol depending only on how you connect:
| client | negotiated | cache hints |
|---|---|---|
ClientSession.initialize() | 2025-11-25 | dropped |
Client(..., mode="auto") | 2026-07-28 | applied |
So tests/test_mcp_protocol.py asserts the negotiated value from a real
connection. The legacy path still works — backward compatibility is a feature —
it just must not be mistaken for the new protocol.
Worth noting for anyone reading the spec's headline change: 2026-07-28 removes
protocol-level sessions, and directs servers needing cross-call state to use
"explicit, server-minted handles passed as ordinary tool arguments". That is
exactly what codecalc's session_id already is.
The result contract
Every result carries contract_version, currently 1.4.0. The published
schema is docs/contract/result-v1.schema.json
and the policy behind it — what MAJOR/MINOR/PATCH may change, the twelve-month
deprecation window, worked success/failure/timeout examples, and the migration
path from unversioned servers — is in
docs/contract/README.md.
For in-process Python use, the supported protocol-neutral service boundary—and
the session/storage internals that are deliberately not public—is documented in
docs/embedding.md.
Two things a caller should know before reading anything else:
okmeans "ran and exited 0". A program that behaves exactly as intended and exits 3 comes backok: false,exit_code: 3,verdict: "RTE". To tell a failed program from a failed request, readverdict— a request that never reached a runtime has noverdictat all, and has acodeinstead.codeis the branch target, noterror. Eight stable values; the prose inerroris free to improve and is not a contract. An unrecognisedcodemust be treated asinternal— that is what lets a1.xclient survive a2.0.0server, though adding a code is still a MAJOR change, because the published enum is closed and a strict validator rejects the result first.- Truncation reports a size, not just a flag.
output_truncatedsays output was cut;stdout_bytes/stderr_bytessay by how much — the bytes the program actually produced, before the cap. A 200 000-characterprintundermax_output_kb=1returns 1 039 bytes ofstdoutandstdout_bytes: 200001, so a caller can size a retry instead of guessing.nullthere means not measured (nothing ran); a program that printed nothing reports0.
The schema is JSON Schema 2020-12 — the dialect MCP 2026-07-28 defaults tool
outputSchema to — so a client can validate our results with it directly.
scripts/check_contract.py regenerates it from codecalc/contract.py and fails
on a diff, and separately re-derives both backends' verdict vocabularies from
main.rs and executor.py: check_parity.py compares the two backends' key
sets and is structurally blind to a new verdict value, which would leave the
published enum short and make a strictly validating client reject a good result.
Configuration
All optional. codecalc runs with none of these set.
| Variable | Default | What it does |
|---|---|---|
CODECALC_HTTP_TOKEN | (unset) | Bearer token for the Streamable HTTP transport (serve-http). Unset, the transport is loopback-only — binding a non-loopback address without this set is refused outright. Set, the token gates every request via a constant-time comparison; stdio ignores this entirely. |
CODECALC_HTTP_URL | http://127.0.0.1:8000 | What the HTTP transport's auth metadata advertises as its own URL. Only consulted when CODECALC_HTTP_TOKEN is set; the loopback default matches the offline-by-default posture rather than guessing a public one. |
CODECALC_RUNTIME_PATH | the server's own PATH, else /usr/local/bin:/usr/bin:/bin | The PATH executed code resolves runtimes on. Set this when an MCP client spawns the server: clients often launch with a stripped environment, so an inherited PATH can miss a toolchain manager's shims entirely and most languages silently become unavailable. list_languages reports what actually resolved. |
CODECALC_EXEC_BIN | bin/codecalc-exec (arch-matched) | Override the sandbox binary. Without one, codecalc falls back to a pure-Python executor — list_languages and execute_code still work, but the Rust path is the production one. |
CODECALC_REQUIRE_NATIVE | (unset) | Fail-closed: refuse to start if no usable codecalc-exec binary was found (checked at import, so this is also a server-start check), instead of silently answering every call on the weaker Python fallback. Raises naming CODECALC_REQUIRE_NATIVE and the paths that were checked. |
CODECALC_EXECUTION_PROVIDER | local | Default execution-provider ID. Explicit execute_code(provider=...) selection still wins. Setting this to an unregistered provider fails explicitly; it never falls back. |
CODECALC_PISTON_URL | (unset) | Register the non-local open-source Piston v2 provider at this absolute HTTP(S) base URL. No public service is contacted by default. |
CODECALC_PISTON_AUTHORIZATION | (unset) | Exact value for Piston's Authorization header. It is scoped to the Piston transport and redacted from normalized results, descriptors, health, and receipts. |
CODECALC_STRICT_URL | (unset) | Activate the current OS's <host>-strict provider as an authenticated client of the Linux strict execution service. Without it, strict selection fails closed. The adapter verifies the remote enforcement handshake before sending source. |
CODECALC_STRICT_AUTHORIZATION | (unset) | Exact value for the strict service's Authorization header. It is never published in descriptors, doctor output, errors, or receipts. |
CODECALC_RUN_STATE_DIR | ~/.codecalc/runs | Durable metadata-only journal backing run_submit/run_inspect/run_cancel, for every provider (not only managed strict runs). Source, stdin, output, and credentials are never written there. On restart, recorded orphan runs are cancelled and cleaned through their owning provider where it supports that; where it does not (the built-in local provider), there is nothing to signal and the record is simply marked recovered. |
CODECALC_MAX_ACTIVE_RUNS | 64 | Admission cap for run_submit: how many runs may be running/cancelling at once before further submissions are refused with a resource_exhausted error. Bounds the in-memory run table and its thread pool against an unbounded burst or a caller that never inspects/cancels what it starts. An empty, non-numeric or non-positive value falls back to 64 with a message on stderr — a set-but-empty variable is a shell and compose-file commonplace, and it used to abort the server's import. |
CODECALC_ALLOW_RUNTIME_APPLY | (unset) | Permit update_runtimes(apply=True) to run the elevated update commands (apt, via sudo). Unset, they are skipped with ok: false naming this variable, and the unprivileged managers still run. Deliberately an environment variable rather than a tool argument: apply is something a connected model can flip, and this is not. Accepts 1/true/yes/on; an empty value is not consent. |
CODECALC_SESSION_ROOT | ~/.codecalc/sessions | Where session workspaces live. Keep this codecalc-private. codecalc cleanup --write --include-unmarked removes plain, session-shaped subdirectories under it on a heuristic (name shape + age) that is a loose filter, not a strong one — never point it at a directory anything else writes into. |
CODECALC_CLEANUP_ABANDONED_AGE_HOURS | 24 | How old (and untouched) a marker-less, session-shaped directory must be before codecalc cleanup --include-unmarked will consider it abandoned. Only consulted with --include-unmarked; the default cleanup invocation never reads it. |
CODECALC_PACKAGE_ALLOWLIST | (unset) | Deny-by-default allowlist for install_package. Unset, any syntactically valid package name may be installed (today's behaviour). Set, only listed packages install — anything else is refused before any subprocess or network work, with the stable permission_denied code. Comma-separated; each entry is <language>:<name> (scoped to one ecosystem) or a bare <name> (every ecosystem). Matches the bare name, ignoring [extras] and ==version pins. |
CODECALC_SESSION_IDLE_TTL_SECONDS | (unset) | Idle-expiry for stateful (python3/node) session workers: a session untouched for longer than this is reaped — worker killed via the same teardown session_stop uses — on its next access. Unset, a session worker lives until session_stop or server exit, same as before this existed. A subsequent call on an expired session gets ok: false with the stable worker_failure code, never a silent respawn. |
CODECALC_SESSION_DISK_QUOTA_MB | 512 | Per-session ceiling on total workspace disk. session_write_file and oversized-output spilling refuse BEFORE writing (resource_exhausted, no partial file); code run via execute_code(session_id=...)/session_run is checked before it starts and, since its own writes cannot be pre-checked, again after — an over-quota run still returns its result, now with disk_quota_exceeded plus usage/limit, and the session's next write/run is refused until usage (re-measured fresh each time) drops back under the line. Also the cap a SESSIONLESS run's per-run dependency workdir is held to (codecalc/dependencies.py, checked after each successful install) — reused rather than a second, independently-tunable constant, since it is the same kind of workspace in every way that matters here. |
CODECALC_TOTAL_DISK_QUOTA_MB | 8192 | Global ceiling on disk summed across every session workspace on this host — closes the gap where staying under the per-session quota by opening many sessions would otherwise be unbounded. Same enforcement points and resource_exhausted contract as CODECALC_SESSION_DISK_QUOTA_MB. |
CODECALC_MAX_ARTIFACT_BYTES | 16777216 (16 MiB) | Per-write size ceiling for anything a session write path creates — independent of the total quotas above, so one runaway file cannot hide under a generous session/global total. A WRITE-time cap; distinct from RESOURCE_MAX_BYTES (4 MiB), which caps what a read may serve back. |
CODECALC_MAX_ARTIFACT_COUNT | 500 | Per-session ceiling on the number of artifact files — catches a session writing one byte at a time into thousands of tiny files, a shape no byte-sized cap alone bounds. Only a write that creates a NEW file is checked; overwriting an existing one always succeeds regardless of the count. |
CODECALC_MIN_HOST_FREE_MB | 256 | Refuse a session write when the HOST's free disk space drops below this — protects the host even when every quota above is generous, since a shared host can be driven low by something that is not a codecalc session at all. Measured with shutil.disk_usage, which works identically on Windows, unlike statvfs. |
CODECALC_CAPABILITY_POLICY | (unset) | Capability broker. Unset, no brokering — a job's capabilities run as requested (today's behaviour); the execution receipt still discloses them under provider.capabilities with brokered: false. Set, comma-separated directives narrow them: deny-network forces no_net on a job that did not request network (enforced where the provider can, disclosed as effective where it cannot); allow-network explicitly grants network to a job that requested it; strict rejects a job whose denial the provider cannot enforce. The broker never approves a capability the request did not ask for — an escalation is refused with permission_denied / capability_not_requested, before any side effect. |
CODECALC_AUDIT_LOG | ~/.codecalc/audit/audit.log | Append-only JSON-lines audit stream for broker decisions and security-relevant side effects (denied capability, refused install, cleanup). Each event carries a source-safe timestamp, the run/session id, the decision and reason, and never the executed source or a credential. Set to a path to relocate it; set empty to disable. Best effort — a write failure never fails a run. |
CODECALC_PROCESS_HEADROOM | 512 | Fork-bomb guard. RLIMIT_NPROC is a uid-wide task budget, not a per-sandbox one — the kernel compares it against every thread your user owns, machine-wide. So codecalc measures the ambient count per execution and sets the limit to ambient + headroom: a bomb can add at most this many tasks, while a runtime wanting a few threads always has room however busy the box is. |
CODECALC_MAX_PROCESSES | (unset) | Escape hatch: pin RLIMIT_NPROC to an absolute value and skip the measurement. |
The strict service runs on Linux x86_64 or ARM64 with Docker Engine, cgroup v2,
and an explicitly registered gVisor runsc runtime. Its executor image must be
pinned by @sha256: digest on the execution path. That image is published to
GHCR (ghcr.io/the-40-thieves/codecalc-exec, multi-arch amd64+arm64) by the
publish-executor-image workflow, which an operator dispatches
(workflow_dispatch); the workflow commits the immutable digest into
docker/executor-image.lock, and published_strict_image() resolves it as the
production default. Until that first dispatch no digest is pinned and the
execution path fails closed — it never falls back to the mutable local
diagnostic tag (codecalc-exec:strict), which doctor and the conformance
suite keep using. The default systrap platform works without KVM, so the same
authenticated service can be used from Linux, macOS, and Windows; strict clients
never fall back to native local execution.
Provisioning and running any of the three strict backends in production —
the gVisor+Docker host, Windows AppContainer hardening, and the macOS/Windows
remote-client configuration — is covered in
docs/deployment/README.md, separate from the
provider interface itself in
docs/contract/provider-v1.md.
Both backends resolve CODECALC_RUNTIME_PATH identically, and
scripts/check_parity.py fails CI if the Rust and Python copies of that
contract ever drift — including if a machine-specific home directory finds its
way back into the default.
Tool-definition token cost
codecalc's tools/list returns 52 definitions. Measured with o200k_base as a
proxy, that is roughly 9,200 tokens of descriptions and input schemas, and every
client pays it before the first user message.
codecalc does not hide its tools behind a discovery facade, and that is
deliberate: the tool surface is where per-operation approval prompts, audit
names and typed schemas live, and collapsing 52 tools into one dispatcher makes
install_package and percentage look like the same permission to a client
that approves by tool name. The cost is real, but the client is the better place
to solve it, because the client can defer definitions without giving up the
schemas or the per-tool boundary.
If you are paying too much for codecalc's definitions:
- Claude Code defers every MCP tool by default — tool search is on by
default, with no token floor codecalc needs to clear.
autoloads a server's tools upfront only while their definitions total under 10% of the context window and defers all of them once that 10% is reached;falseloads everything upfront regardless of size (Claude Code MCP docs, https://code.claude.com/docs/en/mcp, retrieved 2026-09-07).calc_exact,execute_code,verify_translation,verify_optimization, andlist_languagescarry_meta["anthropic/alwaysLoad"](per that same doc, "your 3-5 most frequently used tools") so they stay loaded even when a client defers everything else;install_packageandupdate_runtimescarry_meta["anthropic/requiresUserInteraction"], which forces a permission prompt on every call regardless of the session's permission mode — both change the host and both fetch from a registry. - Claude API, via the MCP connector, takes
defer_loadingonce on the toolset'sdefault_config, or per tool inconfigs. Deferred definitions stay out of the system-prompt prefix, prompt caching is preserved, and a matching tool is expanded into its full definition when the model searches for it. - OpenAI's Responses API has the same knob under a different name:
defer_loading: trueon an MCP server tool definition (OpenAI Responses MCP tool guide, https://developers.openai.com/api/docs/guides/tools-connectors-mcp, retrieved 2026-09-07). - VS Code caps a single chat request at 128 enabled tools and groups excess tools behind "virtual tools" above a configurable threshold (VS Code agent tools docs, dated 2026-09-02, https://code.visualstudio.com/docs/copilot/agents/agent-tools). Windsurf / Cascade caps at 100 total tools (Cascade MCP docs, https://docs.devin.ai/desktop/cascade/mcp, retrieved 2026-09-07).
- The MCP specification itself has no deferral mechanism — no tool search,
grouping, tags, or toolsets; a server can only publish
ttlMs/cacheScopehints and paginatetools/list(MCP spec 2026-07-28, https://modelcontextprotocol.io/specification/2026-07-28/server/tools, retrieved 2026-09-07). A client without one of the mechanisms above pays the full cost regardless of what codecalc does. - Any client can filter which of the 52 tools it exposes to the model. Nothing here requires codecalc to change.
A server-side facade remains under consideration for clients with no such
mechanism (docs/design/2026-08-10-tool-facade.md), and is not implemented.
Trimming a description to cut this cost is exactly the change
scripts/tool_select_eval.py exists to gate: an offline, labeled eval of
whether a deterministic lexical (BM25) selector still picks the right tool
for a plain-language ask, scored against the live tools/list text.
Measured v1 baseline (196 hand-labeled prompts, none containing their own
target tool's name — see the script's own docstring): 60.71% top-1 /
75.51% top-3 accuracy on the full surface (62.75% / 63.0% top-1 on dev /
core respectively). It is a lexical proxy, not a model — see the script's
module docstring for exactly what a green run does and does not prove.
The checked-in baseline PINS the exact labeled corpus by content hash
(prompt_set_sha256); a --baseline compare against a corpus that no
longer hashes to it fails with a distinct "corpus changed" error rather than
silently scoring a smaller, easier prompt set against the old numbers. And
because a tool can be top-1-wrong against full's 51 distractors (zero
headroom to lose) while still having real headroom against core's much
smaller distractor set, both the regression compare and the ablation
self-check (replacing real descriptions with a generic stub, one tool at a
time, across every candidate tool — no sampling) run separately against all
three of full/dev/core, wired into CI via
tests/test_tool_select_eval.py so the gate is proven live, on every
surface, on every run — not just at the PR that added it.
Reducing the tool surface
For an operator who would rather not configure every client, codecalc also has
a first-party knob: CODECALC_TOOLS registers only a chosen slice of the
52-tool surface, so a client that never enables tool search still pays for a
smaller tools/list.
On a client with no deferral mechanism of its own, the client's own allow-list
does the same job from the other end — OpenAI's allowed_tools, Gemini CLI's
includeTools/excludeTools, or Codex CLI's enabled_tools/disabled_tools
all narrow what a given session sees without touching the server.
Every tool also now carries a ToolAnnotations hint (readOnlyHint,
destructiveHint, idempotentHint, openWorldHint — see
codecalc/server.py's GROUP_ANNOTATIONS/TOOL_ANNOTATION_OVERRIDES tables
for the value on each of the 52). Codex CLI's writes approval mode
(v0.144.0+) reads readOnlyHint directly: a tool marked readOnlyHint: true
skips the approval prompt, everything else still asks. That covers the whole
calculator group (25/25 pure) plus the read-only members of the mixed
groups — list_languages/list_execution_providers/runtimes_status in
execution, z3_check/algebraic_equiv in verification,
session_list/session_files/session_read_file/session_artifacts/
run_inspect in sessions, and analyze_complexity in analysis — without
codecalc doing anything client-specific; the annotation is the same hint
every MCP client reads, writes just happens to be the mode that consumes it.
This is not the facade the section above declines to build. Every tool a
group activates keeps its own name, its own typed input schema and its own
per-tool approval prompt — a group that is not active simply never registers
its tools with the MCP SDK at all, so they are absent from tools/list and
rejected by tools/call, not merely hidden behind a dispatcher a client could
still invoke by guessing the name.
Every tool belongs to exactly one group:
| Group | Tools |
|---|---|
calculator (25) | calc_exact, compare_threshold, percentage, calc_stats, percentiles, collision_probability, data_sizes, human_duration, epoch_time, base_repr, radix_convert, float_repr, int_widths, bit_analysis, bitop, solve_expression, limit_expression, simplify_expression, convert_units, physical_constants, list_units, evaluate_expression, truth_table, solve_linear, matrix |
verification (5) | verify_translation, verify_optimization, algebraic_equiv, compare_edge_cases, z3_check |
execution (6) | list_languages, list_execution_providers, execute_code, execute_code_stream, compare_execution, runtimes_status |
sessions (11) | session_start, session_stop, session_list, session_files, session_write_file, session_read_file, session_run, session_artifacts, run_submit, run_inspect, run_cancel |
analysis (3) | analyze_complexity, benchmark, extract_function |
admin (2) | install_package, update_runtimes |
CODECALC_TOOLS takes a comma-separated list of group names, preset names, or
both:
| Preset | Expands to |
|---|---|
core | calculator |
dev | calculator, execution, verification, analysis |
full | every group (the default) |
CODECALC_TOOLS=calculator # just the calculator (25 tools)
CODECALC_TOOLS=core # same thing, by preset name
CODECALC_TOOLS=calculator,execution # two groups, unioned
CODECALC_TOOLS=dev # a coding-assistant slice (39 tools)
Unset or empty registers every group — 52 tools, same as today —
so nothing changes for an operator who does not set this. An unknown group or
preset name is a loud startup failure naming the bad value and every known
group/preset, never a silent fallback to "everything" or "nothing": either
direction would turn a typo into a footgun nobody notices until it matters.
codecalc doctor prints the active groups, the full group→tools mapping, and
how many tools this process actually registered, whatever CODECALC_TOOLS is
set to.
Client-side deferred loading (the section above) and this env var compose
cleanly: point a client with no deferred-loading mechanism at a
CODECALC_TOOLS-restricted process, or use both — a smaller declared surface
still benefits from being deferred.
Test
Each file is a standalone script that prints one PASS/FAIL line per
assertion and exits non-zero if any failed — no test runner, no plugins.
cd /path/to/codecalc
# everything. `|| break` used to be `|| break` alone, which stopped at the
# first failure AND left the loop exiting 0 — a red suite reported success to
# anything wrapping this command. This form runs them all and carries the
# failure out.
fail=0
for f in tests/test_*.py; do PYTHONPATH=. .venv/bin/python "$f" || { echo "FAILED: $f"; fail=1; }; done
for f in scripts/*.py; do PYTHONPATH=. .venv/bin/python "$f" || { echo "FAILED: $f"; fail=1; }; done
[ "$fail" -eq 0 ] # the exit status of the whole run
# or individually
PYTHONPATH=. .venv/bin/python tests/test_smoke.py # every language, via the Rust executor
PYTHONPATH=. .venv/bin/python tests/test_mcp_all.py # every tool over MCP stdio, answers checked
PYTHONPATH=. .venv/bin/python tests/test_executor_sweep.py # sandbox regressions
62 test files and 16 CI-invoked scripts, 2184 assertions. "CI-invoked"
means referenced by path (scripts/<name>.py) from a job in
.github/workflows/*.yml — scripts/check_claims.py derives the count that
way and gates it, so a script wired into a workflow without this sentence
changing, or this sentence bumped without a workflow change, fails the build.
Nothing in the suite
needs the internet, so none of it is ever skipped for lack of a network.
It can skip for lack of a capability, and that is correct rather than a regression: a machine without a symlink privilege, without a given language runtime, or without a built native executor cannot exercise the cases that need them. The suite reports three distinct outcomes — the property holds, the property is broken, and this machine cannot exercise it — and every skip names its real cause. A nonzero skip count on Windows or in fallback mode is the healthy result; what would be wrong is a skip reading as a pass.
This paragraph previously claimed zero skips unconditionally. That became
false the moment the suite learned to distinguish the third outcome, and
nothing gated it: check_claims.py gates the counts below, not the prose
around them. The counts are gated by
scripts/check_claims.py: they were written by hand once and were stale within
three pull requests, which is exactly the failure the rest of that script
exists to prevent. Four of the files are regression suites named after the
sweep that produced them — test_bug_sweep, test_executor_sweep,
test_python_sweep, test_network_modules — and each one's docstring states
the defect it locks out and how it was reproduced, because a regression test
whose reason has been forgotten is the first one deleted.
Two rules the suite holds itself to, learned from breaking both:
- Assert the value, not the shape. Three of these files once had no
assertions at all: they called tools, printed the output and exited 0. They
caught a crash and never a wrong answer — a
runtimes_statustotal replaced with-999passed, printingtotal = -999. - Don't pin what varies.
benchmarkandcompare_executionrank by measured time, so their winner moves under load; their structure is asserted and their timing is not.runtimes_statusis checked against itself — the summary must agree with the data it summarises — so it holds on any machine rather than describing this one.
Platform support
Linux, macOS and Windows. The three do not offer the same primitives, and the
executor reports which ones it could not apply in an unenforced array on
every result rather than letting a caller assume they all held.
The native table below describes the local provider and is not a hostile-code
security boundary. On macOS, <host>-strict instead uses the explicitly