Skip to content

Performance benchmarks

Every number on this page is [MEASURED] on the machine below, with a reproducible harness committed under benchmarks/. We publish only figures we actually measured, and we report where a competitor edges toolnexus rather than hide it. A partial-but-real table beats a complete-but-fake one.

Measured 2 August 2026 across all seven ports and their real competitors, in one sitting on one machine. This replaces the July 2026 run wholesale — same hardware spec, a newer toolchain under everything (Go 1.23 → 1.26, Node 22 → 24, .NET and Elixir/OTP forward a generation) and every competitor library re-resolved. Compare ratios within this table, not cells against the old one. Frameworks move fast — re-run before quoting.

Every framework points at the same local mock LLM and the same stdio MCP server, running one fixed tool-calling scenario (2 LLM round-trips + 2 tool calls). We measure only the framework’s own per-request overhead — a real model call (100 ms–seconds) would swamp it. Across 39 framework configurations in 7 languages:

  • toolnexus has the lowest per-request overhead in Python, JavaScript, Go and Java, and sits within noise of the fastest in C# and Elixir — where a lighter native-tool competitor edges it by hundredths of a millisecond at the sub-millisecond floor, while toolnexus is doing real MCP.
  • Go publishes a real MCP row for the first time. The stdio-MCP bug that forced July’s native-only Go number is fixed: 0.49 ms p50 over a live MCP session, in a 10 MB binary at 17 MB RSS.
  • Clojure is new here, and it is the slowest port on the page — ~5.5 ms p50 on both hosts, five times the Python port. Published because it is measured, with the cost located (~2 ms per MCP tool call, in the port’s stdio JSON-RPC path) rather than glossed.
  • toolnexus keeps the smallest or near-smallest install footprint in every language — often dramatically (vs CrewAI 787 MB, Google ADK 230 MB, Mastra 144 MB, LangChain.js 106 MB).
  • Every competitor that ships an MCP client did real MCP here; toolnexus does real MCP in all seven languages.
  • Nothing was skipped. All 39 registered configurations produced a result.

All cells [MEASURED], N = 30 runs, 5 warmup, single machine (see Hardware). ok = the loop produced the correct final answer. Lower is better. Tables are split by tool source — an Over MCP row discovers tools over the real shared stdio MCP server (so it pays the subprocess spawn + tools/list), a Native tools row uses in-process tools with the same names/behavior. Comparing across the two is apples-to-oranges, so they’re separate. Click any column header to sort (ascending / descending).

Every Python competitor ships a first-class MCP client, so all rows are over the real MCP server.

Framework Init (ms) p50 (ms) p95 (ms) mean (ms) RSS (MB) Tools ok
toolnexus (+ skill + native) 17 0.976 1.138 0.996 58.8 5
toolnexus 14 0.998 1.100 1.004 58.8 3
OpenAI Agents SDK 19 4.077 4.316 4.068 110 3
Pydantic AI 27 4.954 5.293 4.988 139 3
LangGraph (persistent session) 115 5.508 5.931 5.504 127 3
Google ADK 17 5.896 6.730 6.005 290 3
CrewAI 37 9.266 10.097 9.429 213 3
LangGraph (reconnect-per-call)¹ 124 23.24 24.27 23.41 127 3

Install footprint: toolnexus 34 MB (2 deps) · OpenAI Agents 65 MB · LangGraph 87 MB · Pydantic AI 130 MB · Google ADK 230 MB · CrewAI 787 MB (heaviest; also needs the crewai-tools[mcp] extra or its MCP adapter aborts on a prompt). ¹ LangGraph’s documented default re-spawns the MCP subprocess on every tool call; the persistent-session row is the apples-to-apples one. LangGraph and Google ADK both need mcp<2 pinned — mcp 2.0.0 removed symbols their adapters import.

Point every framework at the same local mock LLM and the same stdio MCP server, and measure only the framework’s own cost. The mock answers one fixed question with exactly one tool-calling turn (two parallel tool calls: get_weather, add), then a final message — so every run is 2 LLM round-trips + 2 tool executions through that framework’s own agent loop. Zero network, zero cost, deterministic. It picks tool names from each framework’s advertised list, so prefixed names work.

Metric How
Cold init Wall time to build the toolkit/agent including MCP connect + tool discovery. Measured in-process on every port, so it excludes interpreter/JVM boot everywhere and stays comparable. Honest per-language timers (perf_counter, performance.now, time.Now, System.nanoTime, Stopwatch, monotonic_time, koine.time/mono-ms).
p50/p95/mean Wall time per scenario run, over N = 30 measured runs after 5 warmup. Clojure is the one exception — a sample there is a batch of 10 runs divided by 10; see the Clojure tab.
Peak RSS Max resident set of the runner process (/usr/bin/time -l).
Footprint Isolated per-framework install (du -sh of each venv/module/dll set).
Machine Apple Mac17,8 — M5 Pro (arm64), 18 cores, 48 GB RAM
OS macOS 26.4 (Darwin 25.4.0)
Date 2 August 2026 — every row from one sitting
Runtimes CPython 3.11.15 · Node 24.18 · Go 1.26.3 · Temurin JDK 21.0.11 · .NET SDK 10.0.301 · Elixir 1.20.2/OTP 29 · Clojure 1.12.5 on OpenJDK 26, and cljgo 0.9.0
Network none — mock LLM + MCP server are both local, offline, deterministic

Single machine, otherwise idle, one process at a time. Absolute numbers will differ on your hardware; the ratios within this table are the portable takeaway. Treat ±0.5 ms as the noise floor for the multi-millisecond rows and ~0.05 ms for the sub-millisecond ones.

Where a framework ships a first-class MCP client (toolnexus everywhere; LangGraph, ADK, OpenAI Agents, Pydantic AI, CrewAI, Vercel AI SDK, LangChain.js, Mastra, LangChain4j, Eino), rows use the real shared stdio MCP server. Where MCP isn’t first-class (Semantic Kernel, Microsoft.Extensions.AI, langchaingo, Elixir LangChain), the row uses in-process native tools with identical names/behavior and is tagged (native). Native skips the stdio spawn + discovery, so a (native) row has an inherent small advantage over an MCP row — kept explicit so the comparison stays honest.

Nothing. All 39 registered configurations produced a result in this run; the skipped list in benchmarks/results.json is empty. If a future run cannot stand a framework up — a dropped package, an SDK that starts demanding a real key — it lands in that list with a reason, rather than disappearing from the table.

Full instructions + every runner: benchmarks/README.md. Each competitor installs in its own isolated venv/module (toolnexus from the local repo); start mock_llm.py, then run each runner (or run_all.py) — N = 30, 5 warmup, one JSON line each.

On pure framework overhead for a fixed MCP tool-calling scenario, toolnexus is the lowest in Python, JavaScript, Go and Java, and within noise of the fastest in C# and Elixir — where a lighter native-tool competitor edges it by hundredths of a millisecond at a floor where a real model call is 100–1000× larger. What holds in every language: toolnexus does real MCP, carries the smallest or near-smallest install footprint, and never falls into the heavy tier (Spring AI, LangGraph, ADK, CrewAI). That matches its design goal — a small, right-sized library, not a runtime.

The honest asterisks, all of them: LangChain4j edges Java by 0.04 ms — a dead heat, though the gap did close from 0.78 ms in July. Semantic Kernel’s native path edges C# by 0.03 ms while toolnexus is doing live MCP. And the Clojure port is last on this page by a factor of five, with ~2 ms per MCP tool call sitting in its stdio JSON-RPC path — a specific, findable cost, published the day a runner existed to measure it rather than the day it looked good. The one unambiguous improvement: Go’s stdio-MCP bug is fixed, so the Go row finally does the same work as everyone else’s — and it is the fastest cell on the page.