FlowMCP · benchmark report · 2026-07-31
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.
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.
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.
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.
| model | A success | right tool | calls | tokens | sec | B success | calls | tokens | sec |
|---|---|---|---|---|---|---|---|---|---|
| qwen2.5:7b | 6/6 | 100% | 1.0 | 878 | 2.3 | 0/6 | 3.2 | 6,768 | 2.7 |
| mistral:7b-instruct | 4/6 | 33% | 1.0 | 778 | 6.4 | 0/6 | 0.0 | 2,952 | 7.8 |
| llama3.1:8b | 2/6 | 100% | 2.0 | 1,184 | 13.9 | 0/6 | 1.0 | 2,316 | 14.9 |
| qwen3.5:9b | 6/6 | 100% | 1.0 | 1,504 | 5.9 | 0/6 | 7.7 | 14,759 | 14.7 |
| gemma4:12b | 6/6 | 100% | 1.0 | 962 | 19.0 | 2/6 | 6.3 | 10,419 | 48.5 |
| gpt-oss:20b | 5/6 | 83% | 0.8 | 789 | 13.0 | 0/6 | 5.0 | 11,099 | 60.4 |
| qwen3.6:35b (DGX Spark) | 4/6 | 67% | 0.7 | 1,056 | 85.9 | 2/6 | 13.0 | 17,415 | 30.9 |
| deepseek-v4-flash (vLLM)* | 5/6 | 83% | 1.2 | 1,476 | 11.4 | 1/6 | 25.5 | 37,309 | 116.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.
| model | C success | C tokens | D success | D tokens | D first-call flow (of 8)* |
|---|---|---|---|---|---|
| qwen2.5:7b | 0/6 | 2,138 | 10/10 | 4,457 | 8/8 |
| mistral:7b-instruct | 0/6 | 788 | 0/10 | 3,038 | 0/8 |
| llama3.1:8b | 0/6 | 726 | 6/10 | 2,876 | 8/8 |
| qwen3.5:9b | 2/6 | 9,640 | 8/10 | 6,076 | 8/8 |
| gemma4:12b | 0/6 | 4,237 | 10/10 | 5,521 | 8/8 |
| gpt-oss:20b | 0/6 | 5,152 | 2/10 | 3,362 | 6/8 |
| qwen3.6:35b | 2/6 | 7,223 | 10/10 | 6,695 | 8/8 |
| deepseek-v4-flash | 1/6 | 18,231 | 7/10 | 9,862 | 2/8 |
| model | E0 (no recipe) | E1 (recipe) | E1 tokens | E2 (repair) |
|---|---|---|---|---|
| qwen2.5:7b | 0/6 | 6/6 | 2,013 | 5/6 |
| mistral:7b-instruct | 2/6 | 6/6 | 2,452 | 5/6 |
| llama3.1:8b | 0/6 | 4/6 | 1,855 | 6/6 |
| qwen3.5:9b | 2/6 | 6/6 | 3,014 | 6/6 |
| gemma4:12b | 2/6 | 4/6 | 2,565 | 4/6 |
| gpt-oss:20b | 2/6 | 6/6 | 2,567 | 6/6 |
| qwen3.6:35b | 2/6 | 6/6 | 2,295 | 6/6 |
| deepseek-v4-flash | 3/6 | 6/6 | 2,167 | 6/6 |
| excluded model | reason |
|---|---|
| gemma3:4b | Ollama build rejects the tools parameter — cannot function-call at all |
| phi4:14b | same — 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 |
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].
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 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.)
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.
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.
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.
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.
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:
| task | A · façade | B · primitive surface |
|---|---|---|
| brief with city | PASS — 1 call | FAIL — 9 calls, flawless weather orchestration, omitted Hacker News entirely |
| HN now | PASS — 1 call | PASS — 8 calls, fetched all 7 stories |
| brief with default | PASS — 1 call, trusted the default | FAIL — 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.
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.
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.
tools parameter.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.