FlowMCP · benchmark report · 2026-07-31

Two tools beat thirty-five

Give a small local model 35 raw API tools and it fails almost every task. Collapse the same 35 tools into 2 pre-built workflows and the same model succeeds nearly every time, using a tenth of the tokens. That's the whole result — everything below is us trying to break it, and mostly failing to.

For anyone building or evaluating MCP tool servers. If you just want the headline, the four numbers below and the "What the data says" section are the report — the method details and per-model tables are for people who want to check our work.

The same self-hosted models ran against four tool surfaces: FlowMCP's two-workflow façade (A), the 35-tool primitive surface the MCP ecosystem builds by default (B), the primitives behind a tool-search interface (C), and flows + primitives mixed on one surface (D) — plus textual-recipe condition R, which reuses B's surface while changing the system prompt, and code-mode condition E (subconditions E0/E1/E2), where the model writes a sandboxed program against the same primitives. Six experimental conditions. A/B/C/R/E share three core tasks; D adds partial-match and decline tasks. Within each pairwise comparison, everything not named as the manipulated variable — models, fixture data, scoring, trials — was held constant.

Harness: github.com/PeterGreenAppliedAI/FlowMCP bench/ · 610 runs executed, 440 analyzed (296 agentic + 144 code-mode; 12 superseded broken-host runs, 144 superseded pre-fix code-mode runs, and smoke cells retained in the raw data) · 10 models (4B–35B + DeepSeek v4-flash, Ollama + vLLM nodes + DGX Sparks) + a frontier probe (Claude Fable 5) · raw results and transcripts committed in-repo

79% vs 10%
task success, façade vs primitive surface — identical tasks, models, and data (48 runs each)
33 : 0
paired McNemar, all 48 matched A/B cells: 33 discordant pairs, every one favoring the façade (exact p ≈ 2.3×10⁻¹⁰)
0/16
no façade misuse observed in 16 decline runs — no model called a flow when none applied
7B ≥ 35B
a 7B through the façade (100%) outscored a 35B driving the raw surface (33%)

The claim under test

Most MCP servers wrap an entire platform: every endpoint becomes a tool, the model gets a 40-tool surface, and orchestration is outsourced to the model — then everyone blames the model. FlowMCP inverts that: workflows are the tools. Each MCP tool is one named workflow; a deterministic engine executes the steps; the model picks a flow and fills at most three parameters. The design bar is that small models drive it reliably.

That is an empirical claim, so this is an experiment, not a demo.

Method

Condition A presents the real FlowMCP server — spawned as a child process, spoken to over actual stdio JSON-RPC — exposing exactly two tools: morning_brief(city?) and hn_top().

Condition B presents a hand-built but realistic 35-tool platform surface: 17 weather endpoints, 10 Hacker News endpoints, 8 utility tools. Producing a morning brief there requires the model to orchestrate what FlowMCP's engine normally does: search_locations → get_daily_forecast → hn_get_top_story_ids → hn_get_item ×5 — about 8 correct calls in order, while ignoring 27 plausible distractors (get_current_weather and get_hourly_forecast sit right beside the correct get_daily_forecast).

Condition C isolates discovery: the same 35 primitives sit behind two meta-tools — search_tools(query) returns matching definitions on demand, call_tool(name, args) invokes them. The per-turn surface is tiny; the orchestration burden is unchanged. Condition D is the production shape: flows and all 35 primitives on one 37-tool surface, adding two task types — partial match (the right answer is a flow plus one primitive call) and decline (no flow applies; the model must use a primitive and ignore the façade).

All conditions are grounded in identical fixture data — the mocked primitives return exactly what FlowMCP's flows fetch. A failure is an orchestration or specification failure, never missing data. Scoring is outcome-based everywhere: the final answer must contain the true values (temperatures 30.1°/20.2°, ≥3 of 5 story titles, the 1.0842 exchange rate, the moon phase). 2 trials per cell, agentic loop capped at 12 rounds, temperature 0.

Results — success rate

A · 2-flow façadeB · 35-tool primitive surface
Task success over 6 runs per cell (3 tasks × 2 trials). Models ordered by parameter count.

Results — average tokens per run

A · 2-flow façadeB · 35-tool primitive surface
Prompt + completion tokens, averaged over all runs in the cell. The 35-tool definitions cost ~3.5K prompt tokens per agentic round, every round.

Results — the six-condition ladder

compiled façadeprimitive-surface conditions
Aggregate success over the three shared tasks, 48 runs per condition (E2 = E1's cells with up to three repair attempts). Right-hand figures: average model tokens per run — E1's authoring cost is paid once; replay of a captured script or compiled flow costs zero.

Full results

The two charts above are the aggregate picture. The tables below back every number in this report — open them if you want to check a specific model or run.

Per-model results, condition A vs B (8 models, 6 runs each)click to expand
model A successright toolcallstokenssec B successcallstokenssec
qwen2.5:7b6/6100%1.08782.30/63.26,7682.7
mistral:7b-instruct4/633%1.07786.40/60.02,9527.8
llama3.1:8b2/6100%2.01,18413.90/61.02,31614.9
qwen3.5:9b6/6100%1.01,5045.90/67.714,75914.7
gemma4:12b6/6100%1.096219.02/66.310,41948.5
gpt-oss:20b5/683%0.878913.00/65.011,09960.4
qwen3.6:35b (DGX Spark)4/667%0.71,05685.92/613.017,41530.9
deepseek-v4-flash (vLLM)*5/683%1.21,47611.41/625.537,309116.2

* DeepSeek and all condition C/D runs used a 2,048-token generation cap per request; waves 1–2 (the other A/B rows) ran uncapped. The cap only bounds runaway generations — it explains why capped models avoid the 240s deliberation timeouts seen uncapped.

Conditions C and D — per modeltool search, and flows+primitives combined
model C successC tokens D successD tokensD first-call flow (of 8)*
qwen2.5:7b0/62,13810/104,4578/8
mistral:7b-instruct0/67880/103,0380/8
llama3.1:8b0/67266/102,8768/8
qwen3.5:9b2/69,6408/106,0768/8
gemma4:12b0/64,23710/105,5218/8
gpt-oss:20b0/65,1522/103,3626/8
qwen3.6:35b2/67,22310/106,6958/8
deepseek-v4-flash1/618,2317/109,8622/8
Condition E — per modelcode-mode: model writes a program instead of calling tools directly
modelE0 (no recipe)E1 (recipe)E1 tokensE2 (repair)
qwen2.5:7b0/66/62,0135/6
mistral:7b-instruct2/66/62,4525/6
llama3.1:8b0/64/61,8556/6
qwen3.5:9b2/66/63,0146/6
gemma4:12b2/64/62,5654/6
gpt-oss:20b2/66/62,5676/6
qwen3.6:35b2/66/62,2956/6
deepseek-v4-flash3/66/62,1676/6
Excluded modelswhy 2 of 10 model/runtime pairs don't appear above
excluded modelreason
gemma3:4bOllama build rejects the tools parameter — cannot function-call at all
phi4:14bsame — 400: does not support tools from the runtime
gpt-oss:20b (waves 1–2 only)model was broken on its host node (Ollama blob load failure → 500) and the gateway's fallback masked it as a 404; after a re-pull and gateway fix it joined in wave 3 — results above
How to read the tables: the aggregates (A 38/48 = 79% vs B 5/48 = 10% among function-calling models) are the claim. Per-model cells are n=6 — a 4/6-vs-6/6 difference between two models is noise, not ranking. Read rows as texture, not a leaderboard. Intent-to-treat (counting the two models that cannot function-call at all): A 38/60 = 63%, B 5/60 = 8%.

What the data says

The unspecified primitive surface was unreliable at every tested size

No eligible model below 12B completed the primitive chain: 0/24. Condition R later showed this is not one failure mode but three: contract/planning limits (recoverable by recipe), activation limits (mistral — never calls), and execution/assembly limits (llama — calls correctly, composes wrongly). The models did not fail exotically; they stalled in ordinary ways: fetched coordinates and stopped, answered from memory without calling anything, or looped on distractor tools. The 35B managed 33% at 13 calls and 17,415 tokens per attempt — and gpt-oss:20b, a tools-native reasoner, made 7–8 competent calls per attempt for 16K tokens and still went 0 for 6. In paired terms: across all 48 matched model-task-trial cells (A 38 successes, B 5; both succeeded in 5, neither in 10), 33 pairs were discordant and every one favored the façade (McNemar exact p ≈ 2.3×10⁻¹⁰); Wilson 95% intervals put A at 79% [66–88] and B at 10% [5–22].

The façade makes small models sufficient

qwen2.5:7b, qwen3.5:9b, and gemma4:12b were perfect through the façade — 18/18, every run one tool call, right tool, right arguments, ~1K tokens. The entire capability the primitive surface demands (planning, sequencing, distractor rejection) simply isn't needed when the workflow is the tool. This is the "small models drive it reliably" design bar, demonstrated.

The economics are lopsided

The primary economic result: a façade run averaged ~1,080 tokens and a primitive-surface run ~12,880 — 11.9× per attempt. The success-adjusted consequence: dividing each condition's total tokens (including every failed attempt) by its successes gives ~1,360 vs ~123,600 — roughly 90× observed tokens per successful task in this sample. With only five B successes that ratio is deliberately failure-sensitive; the 11.9× per-attempt figure is the stable one. Every one of those 35 tool definitions is re-sent every round, so the surface taxes each turn whether or not it helps. (DeepSeek alone averaged 37K tokens per condition-B attempt, succeeding once.)

Condition C: discovery is not sequencing

Tool search halved token consumption (12.9K → 6.0K per run) but produced no aggregate reliability gain: 5/48 in both conditions — with heterogeneous per-model effects (qwen3.5:9b improved 0/6 → 2/6 under search; gemma4:12b declined 2/6 → 0/6; the two largest models were unchanged). The prediction placed on record before the wave ran — search fixes token cost, not orchestration reliability — held in aggregate. The smaller search surface still did not improve completion.

Condition R: the textual recipe — near-A success, 14× the price

The sharpest control in the set: same 35 primitives as B, but the system prompt states the task contract and the intended call sequence (a textual recipe — it bundles contract, plan, and possibly distractor suppression; it is not a pure specification condition). Result: 36/48 (75%), close to A's 38/48 (79%) in this sample (paired A↔R: 6 A-only cells, 4 R-only, 32 both, 6 neither — McNemar exact p ≈ 0.75, no detected difference; no equivalence margin was predefined, so we do not claim equivalence). Six of eight models went perfect, including every failed reasoner: gpt-oss:20b 0/6 → 6/6, DeepSeek 1/6 → 6/6, qwen3.6 2/6 → 6/6, qwen2.5:7b 0/6 → 6/6. The two exceptions define the taxonomy's hard floor: mistral did not activate in any primitive-based condition (0 calls across B, C, D, and R) and llama follows the sequence but cannot assemble the result.

The price: the recipe condition averaged ~12,500 tokens per run (~15,600 over successful runs) against the façade's ~1,080 — the model re-enacts the workflow at inference time, every time. This also retires the strongest objection to the economics: B's low token numbers were partly an artifact of early failure; R reveals the true inference cost of completing the primitive chain, and it is ~14× what compilation pays (successful-run averages). The three-stage model the data supports: B = infer the contract + infer the plan + execute; R = receive both, still pay to execute; A = select the contract, and the compiled plan executes deterministically. A textual recipe can recover execution for capable models — but only by repeatedly paying multi-round inference; deterministic execution removes the repeated multi-step orchestration burden — sharply lowering the competence required — and the recurring cost.

Condition E: code mode — execution recovered, intent still missing, reuse free

The model writes a Node.js program against the same 35 primitives (with truthfully documented response shapes), executed in a disposable sandboxed process. Three subconditions: E0 (no recipe), E1 (one-shot + the R recipe), E2 (up to three repair attempts on execution errors). Results across 48 cells each: E0 13/48 (27%) · E1 44/48 (92%) · E2 44/48 (92%), at ~2.4K model tokens per E1 run.

E0's task breakout is the interaction that matters: hn_now (a largely self-specifying deliverable) passed 12/16, while brief_city went 0/16 and brief_default 1/16. Code can recover execution when the requested outcome is sufficiently specified; it does not manufacture missing product intent. Recipes supply that intent (E0→E1: 13 → 44), and repair feedback adds marginal reliability for weaker executors (llama3.1:8b: E1 4/6 → E2 6/6).

Two findings that reshape the taxonomy. First, mistral — zero tool calls in every agentic condition — went 6/6 in E1: its limit was the conversational tool-calling loop, not capability. Plan representation is an independent axis; some "execution limits" are interface-relative. Second, against the façade: E1's 44/48 vs A's 38/48 is similar observed success (paired: 8 E1-only vs 2 A-only discordant cells, McNemar exact p ≈ 0.11 — no detected difference, and A's cells for six models predate the generation cap). The economics ladder ends: A ~1.1K tokens per run; E1 ~2.4K to author a program, after which replay is free — 101 of 101 successful scripts re-executed correctly at zero model tokens; R ~15K every single time; B ~12.9K, mostly failing.

The execution-spectrum framing this supports: instruction skills teach the model a procedure and it executes turn by turn (R); code-mode skills let it express the procedure as a program (E); a compiled flow removes the procedure from inference altogether (A). FlowMCP evaluates those media under the same MCP tasks — and a captured, verified E script is one mechanical step away from being a flow: same contract, same plan, plus validation, computed effects, and write governance. Code mode is the exploratory author; the flow is what you keep.

Condition D: the façade wins attention, composes, and doesn't over-trigger

With flows and all 35 primitives on one 37-tool surface: 53/80 (66%) overall, and the models that were strong through the pure façade stayed strong — qwen2.5:7b, gemma4:12b, and qwen3.6:35b went 10/10 each, including partial-match (flow + one primitive call, 11/16 overall) and decline (12/16). First-call flow selection on flow-appropriate tasks: 36/48 (three flow tasks × 8 models × 2 trials); on partial-match tasks the first call was the flow in 12/16. And no façade misuse was observed in the 16 decline runs — no model called a flow when none applied (n is small: the 95% upper bound on the misuse rate is still ~20%). Decline failures were models failing to call anything, not grabbing the wrong abstraction. The honest casualty: mistral:7b-instruct, 4/6 with two tools, made zero tool calls in all ten D runs — 37 definitions pushed it past its cliff entirely. Surface size is a dose, and it has a lethal threshold per model.

The frontier probe: scale did not reliably recover specification

As an indicative test (different scaffold — a fresh-context Claude Fable 5 instance, manually looped through the identical prompts, tools, and fixture data — so these numbers deliberately stay out of the main table and charts), a frontier model ran all six cells once:

taskA · façadeB · primitive surface
brief with cityPASS — 1 callFAIL — 9 calls, flawless weather orchestration, omitted Hacker News entirely
HN nowPASS — 1 callPASS — 8 calls, fetched all 7 stories
brief with defaultPASS — 1 call, trusted the defaultFAIL — 4 calls, delivered 3 HN stories, asked which city you're in

Façade: 3/3, one call each, no hesitation on the default-city task. Primitive surface: 1/3 — and the failures were new in kind. It orchestrated flawlessly (parallel batches, correct sequencing, zero distractor calls) and still failed: on brief with city it produced a polished weather-only brief and never touched Hacker News; on brief with default it delivered three HN stories and asked what city you're in. Nothing in 35 primitive tools says what a "morning brief" is — that knowledge lives in the workflow. Local models failed condition B through a mixture of contract/planning, activation, and assembly limits (condition R separates them); the frontier failed it from underspecification — and across every size we tested, increasing model capability did not reliably recover the missing specification (DeepSeek's 1/6 shows the raw surface is not literally impossible — it is low-reliability and extremely expensive). The façade carries product intent, not just sequencing — it's what makes the task well-posed even for models that don't need help with the mechanics.

The sentence the whole experiment compresses into: search exposes capabilities; a workflow couples the deliverable to an executable task contract. (Prompts, plans, and policies can also encode a deliverable — the façade's advantage is that the specification is reusable and mechanically executed. The recipe condition above measures it: B→R is everything a textual recipe supplies — contract, plan, and distractor suppression bundled; R→A is the total value of compilation — compact surface, hidden intermediate state, deterministic execution, and no recurring inference.)

The façade is not a cure for bad tool discipline — and that's fine

Honesty rows: llama3.1:8b picked the right tool 100% of the time, then kept calling it (2.0 calls/run) and fumbled the final answer — 2/6. mistral answered HN now from its own imagination instead of calling anything. And qwen3.6:35b, a reasoning model, twice spent 240 seconds deliberating about the missing city on brief with default instead of trusting the flow default — the explicit-city version took 9 seconds. Flow descriptions should advertise their defaults in a way reasoning models will trust.

Two of ten model/runtime pairs couldn't play — a deployment-compatibility finding

gemma3:4b and phi4:14b cannot function-call in their Ollama builds at all; the façade can compress the decision to one call, but not to zero. Tool-template support, not parameter count, is the first gate for local agent work.

What this doesn't prove

Method notes & reproducibility

Harness commits, generation caps, and population definitionsfor reproducing the runs
  • Harness commits: waves 1–2 at cf56b01, wave 3 at 5ce273e, wave 4 (C/D) at 847bbf8, wave 5 (R) at 8d464e9, wave E final at 7e4e5b7 (a schema-less wave-E run at 6871c9d is superseded: undocumented response shapes unfairly failed valid scripts — retained in raw). Raw per-run results JSON, full message transcripts for waves 4–5, code-mode results, and the 101 captured successful scripts are committed under bench/results/ (waves 1–3 predate transcript capture).
  • Generation caps: waves 1–2 uncapped; waves 3–5 capped at 2,048 tokens per request (bounds runaway deliberation; noted where it affects comparability).
  • Temperature 0 everywhere. The gateway does not expose sampling seeds — that is the extent of the determinism control.
  • Populations: eligible = 8 function-calling models (aggregates above); intent-to-treat adds the two models whose runtimes reject the tools parameter.
  • Round limit: 12 agentic rounds per run; hitting it is recorded as failure with cause.

None of the architectural ideas here are new, and their convergent reinvention in 2026 is evidence for the thesis, not against it. The core design — an agent produces a declarative workflow artifact once, a deterministic engine executes it thereafter — appears in "Separating Intelligence from Execution" (arXiv:2605.00827) and in shipped systems (workflows-mcp-server, duckflux, Microsoft's declarative agent workflows, Zapier-style workflow tools). Compiling agent traces into reusable skills is an active research area (Voyager, 2023; Agent Workflow Memory — 2024 preprint, ICML 2025; Skill-DisCo, Trace2Skill, SkillGenBench, 2026). Tool-count degradation is quantified in "How Many Tools Should an LLM Agent See?" (7–85% drops at 49–741 tools; positional bias), with practitioner reports of a ~10–20-tool "safe zone" (Pan, 2026); Tool-RAG write-ups report large retrieval-driven gains in tool-selection accuracy (e.g. 13.6% → 43%). What we have not found published elsewhere is a controlled multi-condition decomposition on identical fixtures with paired statistics. On selection our claim is deliberately narrow: our search interface reduced context cost without changing end-to-end completion; determining whether retrieval improved selection here requires a separate trajectory-level selection score from the transcripts, which we have not yet computed.

Since this report

Next