Most advice about MCP server design says keep the tool count small. I read 91 sources: 38 say design fewer tools, 20 give a number (mostly 10–20), and only 10 measured tool count against accuracy at all. Then I measured it: 1,932 runs, three clients, 5 to 100 tools. On today's top models accuracy didn't move, not with 100 tools, not with look-alikes, not even with vague names. What moves is cost, and how much depends on how your client loads tools, not on the model. Since 2026 all three clients hide most of your tool list from the model by default, which makes tool names matter in a new way.
MCP server best practices nobody measured
Before measuring anything, I read 91 sources on MCP server design, every one of them in full: 21 official docs, 28 vendor blogs, 20 independent blogs, 2 forum threads and 20 papers. 38 of them say to curate fewer tools, 22 say to keep the visible set small through search or deferred loading, and 8 recommend code mode, where one or two tools run code. None say to expose everything.
20 sources give a number: 5–8 (Workato), 5–15 (Phil Schmid), under 10 per namespace and under 20 (OpenAI), 10–20 (Gemini, Speakeasy), 30–50 (Claude docs), "40 is the knee" (Archestra). Only 3 of those 20 numbers come from the source's own measurement. Two of the most repeated claims don't hold up. "Models break at 30 tools" traces back to the RAG-MCP paper, which counted MCP servers in the prompt, not tools. "Cursor won't take more than 40 tools" stopped being true when the cap was removed in January 2026.
Of the 91 sources, 46 are opinion and 8 cite someone else. 37 measured something, but only 10 measured tool count against accuracy: 7 papers, GitHub's Copilot (40 → 13 tools for +2–5 points), LangChain and one informal test on a 1.7B model. The rest measured tokens or compared MCP with a CLI, which isn't a tool-count measurement. Even on grouping the advice splits: 15 sources recommend workflow tools, 15 grouped tools, and 3 one tool per operation, and those 3 argue from permission prompts, not accuracy.
How I measured it
I built a fake server with 100 realistic file and document tools, with honest names and schemas. Some of them are deliberately confusable, like pdf_compress, pdf_optimize and image_compress. It also has a grouped variant, with 7 tools covering 50 operations, and a vague-names variant. None of the tools touches a file: the server's call log is the judge.
The agents got 18 tasks with known answers, 10 of them ambiguous, at N = 5, 10 … 50, 75 and 100 tools, two times each. The clients were Claude Code (Opus 5.5) with tool search on and off, Codex CLI (GPT-6.1-sol) and Antigravity (Gemini 3.1 Pro), each on its own subscription. All measurements are on GitHub: the report, results.csv and the raw log of every run.
How many MCP tools is too many?
Every run with separate tools was fully correct, from 5 to 100 tools, in all three clients and in both Claude Code modes. That includes every ambiguous task and the 50 extra look-alike tools at 75–100. So on Opus 5.5, GPT-6.1-sol and Gemini 3.1 Pro (October 2026), "fewer tools = more accurate" didn't show up in this range, which matches the survey's caveat that degradation depends heavily on the model.
Claude Code's tool search never hid the right tool. Every run made exactly one ToolSearch call, as its first action, and selected tools by exact name. The model sees all tool names up front and picks between look-alikes from the names alone, before it reads any schema.
What MCP tools really cost in Claude Code, Codex and Gemini
Input tokens, averaged over 36 runs per cell (18 tasks × 2). Every one of these runs was fully correct, so the table shows cost only.
| Client | How tools reach the model | First call: 5 / 50 / 100 tools | Per run: 5 / 50 / 100 tools | One more tool (first call / per run) |
|---|---|---|---|---|
| Claude Code (Opus 5.5), tool search on (default) | Names only; schemas loaded on demand with ToolSearch | 3.9k / 4.6k / 5.4k | 13.8k / 17.4k / 19.9k | ~15 / 79 tokens |
| Claude Code (Opus 5.5), tool search off | Every full schema in the prompt | 4.1k / 11.7k / 20.2k | 9.3k / 27.9k / 48.6k | 169 / 429 tokens |
| Codex CLI (GPT-6.1-sol) | Code mode: schemas never in the prompt; the model searches them with code | 13.2k flat | 45.3k / 61.4k / 78.2k | 0 / 408 tokens |
| Antigravity (Gemini 3.1 Pro) | Names only; schema file opened on demand | 3.8k / 4.1k / 4.3k | 15.7k / 20.5k / 18.6k | ~5 tokens / flat |
Tool search is the switch that matters in Claude Code. It's on by default: only tool names go into the prompt, and the model loads the schemas it needs with one ToolSearch call. That extra call makes it dearer than loading everything below about 19 tools, and cheaper above: 59% less input per run at 100 tools. It's off behind a proxy or on Azure Foundry, with older models, with ENABLE_TOOL_SEARCH=false, or with --tools "". Fewer tokens didn't mean a cheaper run, though: at list price a run with search on cost $0.047 against $0.038 with it off at 100 tools, because cached input is cheap and the search adds a call. What search saves is context, not money.
The trap I fell into: my isolation flag switched off Claude Code's tool search, and I first reported the "search off" numbers as normal. A single flag changed the first-call cost by 4x.
Grouping MCP tools: cheaper in one client, worse in another
With every schema loaded (Claude Code, search off), 7 grouped tools cost what 14 separate ones do, at 100% accuracy. With tool search on, Claude Code's default, grouping barely matters: 16.1k input tokens per run against 17.4k for 50 separate tools. Grouping pays off only where every schema is loaded upfront.
Antigravity is the one place in the whole experiment where accuracy dropped, to 89%. Gemini skipped reading the schema of the multi-operation tool, guessed operation names such as delete, help and fake_op, and ran a wrong one 3 times.
Do names decide? Vague names under deferred loading
For this variant I kept the same 100 tools and the same descriptions, and gave the tools careless names: optimize_file, optimize_document, process_image, combine_files, handle_pages. Accuracy held: 276 of 276 runs were correct, and no wrong tool was executed. The descriptions decided the answer.
But wherever the model sees names first, the names decided the path to that answer. In Claude Code with search on, honest names let the model load exactly the schema it needed in 69 of 72 runs; with vague names, in 7 of 72. It fell back to keyword searches, the schemas loaded per run went from 1.6 to 7.6 at 100 tools (23 in one run), and input grew by 21%. Gemini opened the wrong tool's schema first in 34 of 72 runs, and in 10 runs it probed wrong tools with empty arguments. My server rejected those empty calls; on a real server, a tool with no required arguments would have run. Gemini's input grew by 38% at 100 tools.
Where every description is already in the prompt, vague names changed nothing: Claude Code with search off didn't notice them. Good descriptions rescue bad names only where the model can see them. Codex searches the definitions with code, so names barely mattered there either.
The names that misled most often: "3 JPGs into one PDF" led to combine_files, which actually merges PDFs; "shrink a PNG" led to optimize_image; and pptx and docx to PDF got mixed up between convert_file and convert_document.
MCP tool naming: dots, prefixes and the 64-character budget
Names that every client can call use letters, digits, _ and -, up to 64 characters. The Claude API rejects dots, and some clients rename dots on their side. Those 64 characters are also shared: Claude Code calls your tool mcp__<server>__<tool>, so a long server name eats into the budget. One more surprise from the runs: Gemini's API rejected tool schemas with integer enums in one eager-loading run.
So what should you do?
- Don't cut tools for accuracy's sake on frontier models; cut or defer them for cost and context, and check which of your users' clients loads them eagerly.
- Name tools by action and object, not by generic verbs: names are now the first thing a model reads, and vague ones cost extra reads and risk a wrong call.
- Write descriptions a model can tell apart: that's what kept accuracy at 100% with look-alikes and vague names.
- Don't group blindly: test in the clients your users run.
- Measure your own server. This worklore story walks through the same benchmark step by step, and your agent can run it against your server.
Limitations
Fake tools and one domain (files). Three frontier models only: smaller and older models weren't tested, and that's where the measured sources in the survey report drops. Direct tasks with no long reasoning alongside, so context crowding wasn't measured. Vague names were tested with good descriptions only, not with both vague. Token counts include each client's overhead. Some sizes weren't run in every lane, and the Codex vague-names lane has 60 of 72 runs because of a usage limit. And it's one point in time: October 2026.
Did you choose your tool count and names by measurement or by advice? What did you measure?

Top comments (11)
The vague-names result (exact schema loaded in 69 of 72 runs with honest names, 7 of 72 with vague ones) matches something we hit one layer down: when the name is the index, the matching code can fail before the model gets a choice. Our tool search over 306 finance tools stemmed "rates" to "rat" and ranked the wrong tools first until we fixed it, and it crashed on the literal word "constructor". After the fixes, 46 of 48 everyday requests it was never tuned on get the right tool first.
On the context side we ended up with three front tools: 1,109 tokens of definitions per request, against 34,508 for the seven servers installed separately (our own bench, our own tools, so one data point, not a general claim). I built it, it's free and runs locally:
npx -y canli-mcp, code at github.com/arhancanli/canlicapital.A question on your runs: with honest names at 100 tools, was the first ToolSearch hit ever a look-alike (pdf_optimize vs pdf_compress), or always the exact tool?
Good question, and the short answer is: there was no first hit to be a look-alike, because with honest names Claude Code never searched. In all 36 runs at 100 tools (18 tasks × 2) it read the name list and asked for schemas by exact name with select:. Zero keyword searches, so no ranking step sat between the names and the model.
It did pull a rival in once, on purpose. The task was "scan.pdf takes forever to show up on our website, make it display page by page while downloading", where pdf_optimize and pdf_compress sit next to each other. One of the two runs loaded both (pdf_optimize first), read both descriptions and called pdf_optimize. The other loaded pdf_optimize plus pdf_info. Every other extra load across the 36 runs was a read-only helper (pdf_info, image_info, zip_list), never a competing writer. Wrong first loads: 0 of 36.
The look-alike-first cases all came from the vague-name variant: 8 of 72 Claude runs (combine_files for "3 JPGs into one PDF" four times, optimize_image / convert_image for "shrink a PNG" three times), and 34 of 72 for Gemini. That is also where Claude fell back to keyword search, in 28 of 36 runs at 100 tools, so it is the only place in my setup where a matcher like yours would have been in the path. Your "rates" → "rat" bug is one layer below anything I measured: with honest names the list went straight to the model.
Your case is a different regime from mine, and an interesting one. My sweep stops at 100 tools on one server, with look-alikes inside one catalog. Seven servers in one domain adds something I never tested: the same operation under two prefixes, where the names can't settle it and the model has to read descriptions or try both. The only hint my data gives for 300 tools is that Codex's search output was already truncated at 100 in 34 of 36 runs, so at that size some ranker is in the path whether you build it or the client does. That setup, several servers with overlapping tools in one session, is on my list for a follow-up round.
The per-run queries are in the data: research/tools-count in the worklore-mcp repo, names.md and the toolsearch_queries column of results.csv.
0 of 36 wrong first loads says the name list did the work, which also explains why a ranker bug like our "rates" -> "rat" stemmer never shows up in your honest-name runs. Thank you for the exact breakdown.
The case I'd watch in your follow-up is the same operation under two prefixes: names can't settle it, so the model either reads both descriptions or picks by list order, and those fail differently. If you log which one it called first when both are loaded, that would tell the two apart.
If canli-mcp is useful to you, a star on github.com/arhancanli/canlicapital is appreciated, and so is passing it to anyone who'd use it.
"What search saves is context, not money" is a useful correction. Most of the advice I read treats those as the same thing. Your runs were short direct tasks, so I'm curious whether the gap flips in long sessions: tool schemas are a fixed cost, but tool results pile up turn after turn, and in my experience that's where most of the input goes. Did the per-run numbers show how much of the input was schemas vs. returned tool output?
good question, and honestly my runs can't answer it. the tools were fake and returned tiny results, so each extra turn added only ~130 tokens. nearly all the input was schemas + system prompt sent again each turn.
long sessions are where tool output piles up, but i don't think i can model that honestly. fake outputs would just give me whatever sizes i picked, and i'd feel bad burning that many tokens to measure my own guesses. it needs logs from real working sessions. if you (or anyone reading) have some and don't mind sharing, i'd be happy to dig into the schemas vs output split and write it up
That's a fair call, and more careful than most benchmarks I read. Fake outputs really would just measure the sizes you picked. I keep per-turn token usage from my own sessions in CodeCook, the VS Code agent I'm building, so I could pull schema vs. tool-output counts from a few longer ones, just the numbers, no code or file contents. Happy to send them if that's useful. One thing to watch for: long sessions usually get compacted at some point, so the split looks quite different before and after that.
yes please, that would be exactly the data i'm missing. a few longer sessions is plenty.
what would make it directly comparable with my runs: per turn, input tokens split into (a) system prompt + tool schemas, (b) accumulated tool output, (c) everything else, plus a marker on the turn where compaction happened. before/after compaction as two separate series is a great point, i'd keep them apart rather than average over it. if your counts are already in some other shape, send what you have and i'll adapt.
dev.to connect (DM) or X @YahhiMurr both work. i'll write it up as a follow-up with CodeCook credited as the source, and you get to read the draft before it goes out.
Perfect — I'll pull a few longer sessions and break each turn into (a) system + schemas, (b) accumulated tool output, (c) everything else, with a marker on the compaction turn, so you get before/after as separate series. I'll DM you on Connect (or X @YahhiMurr if that's easier) once the numbers are ready. Looking forward to the write-up, and happy to look over the draft before it goes out. Thanks for offering to credit CodeCook.
Quick preview from a real CodeCook session I already published numbers for on my site (codecook.makayis.com), until I can pull the finer split:
One long turn after smart compression:
I don't yet have the clean per-turn split into (a) system+schemas, (b) accumulated tool output, (c) everything else with a compaction marker from these public figures — I'll DM that once I export a few sessions in that shape. Happy for you to use these headline numbers in the meantime if they help frame the before/after.
Thanks for publishing the raw runs, that's rare in this debate. The bit I'd underline is that tool search makes names matter more than count: if the model picks from names before it ever sees a schema,
querytells it nothing. In the vague-names variant, did ToolSearch still surface the right tool first, or did it just take more searches to get there?Mostly first try, but it read a lot more to get there. With vague names Claude Code still picked the right tool in 72/72 runs. Its first search missed the needed tool in 8 of them (5 at 100 tools), and each of those searched again and found it. The real cost was reading: at 100 tools it ran keyword searches in 28 of 36 runs and loaded 7.6 schemas per run instead of 1.6, about +21% input tokens. So under tool search the descriptions still decide which tool gets picked, and the names decide how much the model reads first. Gemini in Antigravity had it worse: its first schema read was wrong in 34 of 72 runs.