We run 0sql, a semantic layer protocol and service that turns JSON query specs into analytical SQL queries. Our goal for the upcoming week is to launch MCP integration. But first we wondered how different would this be to our custom chat dashboard app demo. In that app we simply implemented localized function calling capabilities that hit our REST api directly. Of course, the main difference is purposeful system prompts to bootstrap the conversation. We figured there would be differences but was surprised by the magnitude.
The puzzle
Our own demo app, a chat dashboard that calls the 0sql API directly, answers a question in two model turns: one tool call to plan the query, one to explain the numbers. Claude Code talking to the same server over MCP needed six round trips and three times as long for the same questions. MCP is just function calling with a different envelope, so where did the extra turns come from?
How we measured
We built an eval: 61 natural-language questions over a customer-service model with 26 tables and 532 fields, covering filters, rankings, date buckets, window functions, period-over-period, shares, calculations, segments and cross-fact measures. Claude Code runs headless as a real MCP client with only our server’s tools. The spec it settles on is planned, run against the warehouse, and compared row for row with a reference. A pass means the same rows. Each variant ran the full set, most of them twice, on Claude Opus 5.
Thirteen rounds later, here is the trajectory:
Setup 1: First MCP server, Claude Code as installed:
6.4 model round trips, 17.4 seconds per question, 95% accuracy.
Setup 2: Tuned server, Claude Code as installed:
4.8 round trips, 14.9 seconds, 95% accuracy.
Setup 3: Tuned server, tool search off:
3.4 round trips, 11.5 seconds, 95% accuracy.
Setup 4: Tuned server, field catalogue visible before the first call:
2.0 round trips, 7.5 seconds, 100% accuracy.
The last one is the demo app’s setup exactly: query, then answer.
What the gap actually is
The demo’s model starts with the catalogue. The MCP model starts blank. The demo pastes every measure and dimension into its system prompt, so its first action is the query. Through MCP the model has to discover the fields first, which is at least one round trip, and choosing from a search’s ranked results is also where its mistakes came from. MCP has a slot for this: a server sends instructions on connect, and ours now puts the catalogue of every branch the key may read in there, about 39 KB. Claude Code keeps roughly 2,000 characters of it and marks the rest [truncated]. We checked by asking a headless Claude Code to quote what it saw. The same cap applies to tool descriptions. That one client decision is the entire difference between row three and row four.
Claude Code adds a round trip of its own. It loads MCP tool definitions lazily behind a ToolSearch call. Setting ENABLE_TOOL_SEARCH=false removed it, and with the tool schemas visible from the start the model also ran its field searches in parallel. That setting alone took us from row two to row three.
Each round trip carries the harness. Claude Code sends 40,000 to 96,000 tokens of context per model call, nearly all cached. The demo sends about 10,000. A round trip is still a few seconds.
We also tried going the other way: tell the model to write the spec from the question’s own words and skip discovery, with refusals carrying the nearest fields. Given a search tool, the model searched anyway. Without one, it wrote, got refused, and retried: the same three round trips, with worse accuracy. Nothing beats having the catalogue in front of the model before it starts.
What this means if you use 0sql from an agent
Seed the catalogue where your client reads it whole. On Claude Code that is a skill or your project’s CLAUDE.md, not the MCP server. Generate it rather than write it: a fields call with no arguments returns the catalogue of a branch as text, every field with its uid grouped by table, measures with their descriptions and synonyms. Put that text in the skill, with the instruction to write the spec from it and call sql directly. That is row four.
Turn off tool search for Claude Code. ENABLE_TOOL_SEARCH=false in the environment Claude Code runs in. One fewer round trip on every question, and better accuracy, because the model sees the full spec schema up front.
Let the server do the rest. The endpoint now takes query specs only, with the spec’s JSON schema in the tool definitions; sends the catalogue on connect for clients that pass instructions through; returns it whole from fields; and answers a refused field name with the nearest fields, their descriptions and their tables, so a wrong first guess costs one retry.
Expect two turns, not six. With the catalogue seeded, a question is one sql call and the answer, at the same accuracy as a hand-tuned direct integration.
The general lesson
Two things have to be true for an agent to answer in one call: the server has to offer what the model needs up front, and the client has to show it. MCP covers the first. A server can send its catalogue the moment a client connects, and ours does. The second is out of the server’s hands. Claude Code shows the model about 2,000 characters of what a server sends and drops the rest, so on that client the catalogue has to come from the agent’s own instructions, a skill or a project file. The server’s part is to make producing that text a single call, which is what the no-argument fields call is for.


