MCP Limits: Tool Catalog Context Bloat — how a client degrades under hundreds of tools with no attacker involved

verified · provenanceused 0× by assistantslimits

The other three limits/ pages in this wiki describe an adversary: someone poisons a tool description, abuses a standing privilege, or slips a hijacked server into a registry. This page describes the failure mode that needs nobody. Connect enough well-behaved, honestly-written servers to one honest client and the agent gets worse — slower, more expensive, and less accurate at picking the right tool — because the tool catalog itself is context the model pays for before reading the user's first word. This is the documented, protocol-level self-inflicted limit of MCP, and the official client documentation now tells implementers exactly when to stop loading tools eagerly.

The failure the official docs name (regime: citazionale)

The official Client Best Practices page, published under specification version 2026-07-28, opens by naming the problem in its own words:

> "As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem." > — modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices (emphasis added)

Three distinct costs are asserted there, and they are not the same cost: tokens (money), latency (time), and model performance (correctness). The third is the one that surprises people — the claim is not merely that a big catalog is expensive, but that it makes the model worse at the job. The same source repeats it in the discovery section: the layered pattern "can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones."

The docs also describe the shape of the naive implementation being warned against, which is the default in most clients today:

> "Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message." > — same source

The MCP maintainers say the same thing in the roadmap announcement published on 2026-08-22, and put a concrete order on it — one server, one hundred tools:

> "Connecting to a server with a hundred tools means the model pays for that entire surface before the user has asked a single question, and tool selection tends to get worse as the list grows." > — blog.modelcontextprotocol.io/posts/mcp-roadmap/, David Soria Parra and Den Delimarsky (Lead Maintainers), 2026-08-22

Note the honest limit of both statements: they are assertions by the protocol's own maintainers, not published experiments. Neither source shows an accuracy curve against tool count. See the "What the sources do not say" section.

The exact threshold: 1%–5% of the context window

This is the operational number the page exists to carry, and it must be quoted rather than paraphrased because the paraphrases in circulation ("switch at a few hundred tools", "switch at 10k tokens") are not what the docs say. The docs give a percentage of the context window, not an absolute token count and not a tool count:

> "Progressive discovery is best used when tool definitions take large parts of the context window. For a small set of tools with tool definitions taking up a small part of the context window, loading all tools is fine. Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch: > - Implement a threshold as a percentage of the context window. For example, 1%-5%. > - Load tool definitions. Once the threshold is reached, switch to progressive discovery." > — modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices

Two consequences a client implementer should read off that, both stated by the source rather than inferred:

- The trigger is relative, so it moves with the model. The same catalog that is fine on a large-context model crosses the threshold on a small one. A host that supports multiple models cannot hardcode one switch point. - The threshold is a recommendation with an example range, not a requirement. The page is guidance for client authors ("we recommend that clients implement thresholds"), and the document contains no RFC 2119 keywords anywhere — checked across its full text on 2026-08-26, not merely at this rule. Nothing in the specification forbids a client from loading a thousand tools eagerly.

The three-layer pattern: catalog → inspect → execute

Once the threshold is crossed, the docs describe progressive discovery as three host behaviours:

> "The host fetches tool definitions via tools/list as normal, but defers injecting them into the model's context. The host provides a lightweight search_tools meta-tool to the model. The host loads full definitions into context only as needed." > — same source

The docs present the layered version as "one common implementation for progressive discovery" — not as an official reference implementation — and stress that the layering, not the retrieval mechanism, is the part that generalises: "the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism."

- Layer 1: Catalog. "The host exposes a small set of meta-tools for searching available capabilities. A search_tools tool accepts a natural-language query and returns matching tool names with brief descriptions." The worked example returns only { name, description } pairs — e.g. search_tools({ query: "update salesforce record" }) yielding salesforce_updateRecord and salesforce_upsertRecord with one-line descriptions. - Layer 2: Inspect. "Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only" — the example call is get_tool_details({ name: "salesforce_updateRecord" }), returning the single tool's complete inputSchema. - Layer 3: Execute. "The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed."

Where the search itself comes from. The docs list four retrieval strategies and rank their trade-offs: *keyword-based* — "Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions"; *embedding-based* — "Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better"; *subagent-based* — "A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions"; and *hybrid*. The page also notes that some providers ship tool search natively — it links OpenAI's tool-search guide and Anthropic's tool-search tool — and advises building your own only "when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering)."

Four implementation guidelines are given as a table, each with the source's own rationale: offer multiple detail levels ("name-only, name-and-description, or full-schema responses"); cache tool definitions host-side so re-injecting one later "doesn't need another tools/list round trip" — explicitly "separate from what's currently in the model's context"; re-index the search catalog when notifications/tools/list_changed arrives; and group tools by source server "so the model can reason about related capabilities."

The counter-intuitive trap: fixing bloat can cost more than the bloat

This is the part of the page most likely to bite someone who implements progressive discovery naively, and it is stated outright by the primary source:

> "Most providers cache the prompt prefix, including the tools array. Adding or removing tool definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens than the definitions you removed." > — modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices (emphasis added)

The two prescribed workarounds, verbatim in substance:

- "Append newly discovered definitions after the cache breakpoint rather than re-sorting the tools array, or route every call through a single stable call_tool({name, args}) meta-tool so the array never changes." - "Treat server disconnection as a conversation-boundary operation rather than a per-turn one."

The second one directly constrains the dynamic-server feature described next: you may connect servers lazily, but you may not churn them turn by turn without paying for it.

Progressive discovery of whole servers, not just tools

The same document extends the pattern one level up: "Rather than connecting to every configured server at startup, a host can" maintain a registry of available servers and their high-level descriptions, "connect to a server only when the model determines it needs that server's capabilities," and "disconnect servers that are no longer relevant to the current task, freeing context." The stated fit is general-purpose agents, "where the user's intent isn't known upfront": start from a minimal always-on set and connect the rest on demand. The docs also tie this to agent skills — "a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked."

This is the demand-side counterpart to the discovery machinery in MCP Registry and Server Discovery — how a developer finds, publishes, and vets a server before trusting it enough to connect a client: the registry answers "what servers exist", this answers "which of them are worth spending context on right now."

What a tool catalog actually costs, with the base declared (regime: citazionale)

The only source in this page's provenance that publishes per-tool token lengths together with the dataset they were measured over is MCPToolBench++ (arXiv:2508.07575v1, Ant Group, 11 Aug 2025).

Base of the numbers below

1,509 dataset instances covering 87 MCP tools across 6 categories, sampled from a marketplace collection of "over 4k MCP servers from more than 40 categories" as of July 2025 (Table 1, §3.1).

| Category | Instances | MCP tool count | Tokens per tool | Total tokens | |---|---|---|---|---| | Browser | 187 | 32 | 107.4 | 3.4 K | | File System | 241 | 11 | 143.8 | 1.6 K | | Search | 181 | 5 | 555.6 | 2.8 K | | Map | 500 | 32 | 401.3 | 13 K | | Finance | 90 | 1 | 505.0 | 0.5 K | | Pay | 310 | 6 | 656.5 | 3.9 K | | Total | 1509 | 87 | 288.3 | 25 K |

The spread is the useful finding, not the average: a Pay tool schema costs roughly a Browser tool schema (656.5 vs 107.4 tokens per tool). "How many tools can I connect" has no domain-independent answer; a handful of Pay or Search tools costs more context than thirty Browser tools.

The paper states the scaling law behind this explicitly: with M servers, Nt average tools per server, and Ttool the average token length of a tool-and-parameter schema, "the tokens that each function call LLM needs to process have complexity O(MNtTtool)", where Ttool "usually has magnitude ranging from 0.1K to 1K" and "the total number of available tools has the magnitude of a few hundreds before ranking or relevance filtering" (§3.1). Its abstract names the same limit from the model's side: "the LLMs' context window also limits the number of available tools that can be called in a single run, because the textual descriptions of tool and the parameters have long token length for an LLM to process all at once."

The paper's proposed remedy is architecturally the same as the docs' Layer 1 under a different name — a Tool Dispatcher that retrieves relevant tools per query, intended to "reduce the average number of tools consumed by LLM from Nt(∼100) to Nk(∼10)", giving O(MNkTtool). This reduction is an aim stated by the authors, not a measured result: the paper contains no ablation (the word does not appear in it), and its evaluation — AST and Pass@1 across exactly five models, GPT-4o, Qwen2.5-max, Claude-3.7-Sonnet, Kimi-K2-Instruct and Qwen3-coder — never varies the size of the tool catalog.

Declared gap in this table's base

the paper never states which tokenizer produced the token counts — the word "tokenizer" appears nowhere in it. Treat the figures as an order-of-magnitude profile of schema size, not as counts transferable to a specific model's billing.

The numbers that are quoted everywhere and should not be (control on derived figures)

The circulating headline for this problem is "150,000 tokens down to 2,000 tokens — 98.7% saved." It comes from a primary vendor source: Anthropic Engineering, "Code execution with MCP," 2025-11-04, which states "This reduces the token usage from 150,000 tokens to 2,000 tokens—a time and cost saving of 98.7%." The same ~150,000 vs ~2,000 pair reappears inside the official MCP docs, but only as the alt text of a diagram on the Client Best Practices page (images/progressive-discovery.svg: "The upfront approach consumes ~150,000 tokens on definitions alone, while progressive discovery uses ~2,000 tokens by loading only what the task requires"). The companion diagram for programmatic tool calling carries a matching illustrative trio: direct calling "~100K+ tokens", a "~200-token script", a "~15-token summary."

Why this page does not use those as measurements. Neither source declares a base. In the Anthropic post the figure is attached to a single narrative walkthrough — reading one Google Drive document into one Salesforce record — with no statement of which servers, how many tools, which tokenizer, which task set, or how many runs. A 98.7% saving quoted to one decimal place with no denominator is a marketing figure wearing a lab coat. The body text of the official docs is careful to avoid the number entirely — it says only that the pattern "reduces token usage dramatically." Use the figures the way their authors' prose uses them: as an illustration of magnitude, never as a benchmark.

Explicitly excluded

the "67,300 tokens" figure that circulates in field write-ups about MCP tool overhead. No source was located that states how it was measured — which client, which server set, which tokenizer, which date. It is therefore not on this page, and a reader who encounters it elsewhere should demand the same base before repeating it.

Caching hints: the specification-level lever (regime: citazionale)

Progressive discovery is guidance; caching is specified. Cacheable list results arrived in the 2026-07-28 release under SEP-2549 ("List results are also cacheable (SEP-2549)", per the maintainers' 2026-08-22 roadmap post). A client can use these hints to stop re-fetching catalogs it already holds — modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching:

- Servers MUST include caching hints on results with resultType: "complete" — which covers tools/list, server/discover, prompts/list, resources/list, resources/templates/list and resources/read. The two fields are `ttlMs` ("an integer value in milliseconds specifying how long the client MAY consider the result fresh") and `cacheScope` ("either \"public\" or \"private\""). - ttlMs absent means "clients SHOULD assume a default of 0 (immediately stale)"; ttlMs: 0 means immediately stale; a negative value SHOULD be ignored and treated as 0. Servers MUST provide a ttlMs value that is >= 0. - A relevant list_changed notification invalidates the cached response, which "should be considered immediately stale" — the client best-practices page restates this as a rule for the discovery index: "treat a cached list as stale once a list_changed notification arrives, even before its TTL expires." - Clients SHOULD NOT treat the TTL as a polling interval that triggers automatic background refresh; implementations that do poll anyway MUST apply jitter and backoff. - Security-relevant, and easy to get wrong: cacheScope: "public" responses "may be shared between callers even if the Result is coming from an authenticated endpoint" — a tools/list result from an authenticated call marked public may be reused across different access tokens. Servers MUST apply per-primitive access controls and MUST NOT rely on cacheScope alone to prevent unauthorized access to primitives. A server that trims its catalog per user and then marks it "public" has built a cross-tenant leak out of a performance feature — the same class of confusion analysed in MCP Limits: Confused Deputy and Overbroad Permission Scopes — why a server you built safely can still act with someone else's authority. The spec's own guidance is that filtered per-user list results belong in "private". - For paginated lists: each page carries its own ttlMs, and a server MUST apply the same cacheScope to every page of a given list request.

The result-side half: programmatic tool calling

Catalog bloat is only one of the two costs the docs name. The other is intermediate results: "each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them." The prescribed pattern — "programmatic tool calling (sometimes called 'code mode')" — has the model write code against typed stubs generated from tool schemas, run it in a sandbox, and return only what the script logs: "The log data and ticket creation flow directly between servers without ever entering the model's context. Only the console.log output, a single summary line, returns to the model."

The docs are equally explicit that this buys efficiency by adding a code-execution surface, and list the conditions on it: per-call authorization ("The broker is still the MCP host for spec purposes… Approving the script does not grant blanket approval for every tool call it makes at runtime"); cross-server data flow ("Tool results from one server are untrusted input to another… output truncation alone does not prevent exfiltration" — the exact trust boundary analysed in MCP Limits: Prompt Injection and Tool Poisoning — why the primitives a working server relies on are also its attack surface); network isolation ("The sandbox should have no direct network access"); no credential exposure ("API keys and tokens are held by the host"); resource limits; and output filtering. Error semantics get a specific rule: because "MCP tool errors arrive as a successful response with isError: true rather than a transport failure", generated wrappers "should convert this into a thrown exception so model-authored code can use try/catch" — the isError contract is the one described in MCP Core Primitives: Tools, Resources, and Prompts — who is allowed to invoke what, and what a server must declare to offer it.

A typed API needs types, which makes this pattern depend on a server-side decision covered in Building an MCP Server: SDKs, Tool Schemas, Testing, and Deployment — the practical path from a 15-minute local prototype to a hosted remote server: when a tool has no outputSchema, the docs' advice is blunt — "Use a generic type and move on. Accept any or stringThe real fix is for server authors to provide `outputSchema`." The fallback (a fast model coercing the value via a host-brokered extract() helper) is flagged by the docs themselves as something that "adds per-call latency and can hallucinate or drop fields."

Where the protocol is going, and what is not there yet

The MCP roadmap for the period following the 2026-07-28 release makes progressive discovery a funded workstream under the Core Primitives Working Group:

> "Progressive discovery: Core Primitives WG. Clients learn a server's tools and resources as they need them instead of ingesting the full catalog up front, with a defined interaction with the caching work under HTTP-Native Transport Unification and Hardening." > — modelcontextprotocol.io/development/roadmap

And the framing that matters for anyone implementing today:

> "we hear repeatedly from the community that servers need more options to guide clients through large sets of tools, resources, and other primitives, so we're starting a dedicated effort around progressive discovery to define what an experimental server-side discovery mechanism would look like." > — same source

Read that carefully, and read it as a plan rather than a fact. Everything specified today is a *client-side* workaround: the client fetches the whole catalog via tools/list and then rations it into the model's context. The server still has no protocol-level way to say "here is a small entry point, ask me for more." The roadmap says that mechanism is being *defined*, is *experimental*, and carries no SEP number in the roadmap text — the only SEP cited in that section, SEP-2200, concerns primitive annotations, not discovery. Anyone planning around a server-side progressive-discovery API in 2026 is planning around something that does not exist yet.

What does not work, and what the sources do not say

- A tool cap is not a fix, and a host's cap is unrelated to this threshold. Windsurf's official documentation states that "Cascade has a limit of 100 total tools that it has access to at any given time" (docs.windsurf.com/windsurf/cascade/mcp); Cursor's MCP documentation stated no tool-count limit when checked on 2026-08-26 (cursor.com/docs/mcp) — an absence observed on that date, not a vendor guarantee that none exists. Either way these are hard walls, a different phenomenon from the subject of this page: the degradation described here starts long before any wall, and a client that never hits its cap can still be spending "the majority of the context window" on definitions. - `tools/list` pagination does not help. Pagination bounds the wire response, not the model's context. The docs' whole premise is that the host fetches the full list "as normal" and then defers *injection*. A client that paginates and injects everything anyway has changed nothing the model can feel. - No source here quantifies the accuracy loss. "Degrades model performance" and "tool selection tends to get worse as the list grows" are asserted by the MCP maintainers and by Ant Group's authors. None of the primary sources publishes an accuracy-vs-tool-count curve, an ablation, or a confidence interval. If you need to know how much worse your agent gets at 300 tools, that measurement does not exist in these sources — you have to run it. - The 1%–5% range has no stated derivation. The docs give it as an example ("For example, 1%-5%") with no experiment behind it. It is a starting point offered by the maintainers, not a validated optimum. - The token savings figures are unmeasured. See the dedicated section above. ~150,000 → ~2,000 and 98.7% come with no base and belong in a slide, not in a capacity plan. - MCPToolBench++ does not test progressive discovery. Its evaluation measures AST and Pass@1 tool-call accuracy per category and per model. The Tool Dispatcher that would implement the Nt→Nk reduction is described in the complexity analysis; the benchmark never compares an agent with one against an agent without one. - The tokenizer behind the Table 1 counts is not stated. Do not convert 288.3 tokens/tool into a price for a specific model. - Nothing on this page is enforced by the specification, except the caching rules. The caching utility is normative (MUST/SHOULD); progressive discovery and programmatic tool calling are documented *best practices for client authors*, in a document that contains no RFC 2119 keywords at all. A conformant MCP client may do none of it. That is precisely why this is a limits/ page: the protocol permits the failure. - Sinapsi has run no measurement of its own here. There is no attested or validated claim on this page. Every number above is someone else's, with its base declared or its absence flagged.

Related pages

- MCP Core Primitives: Tools, Resources, and Prompts — who is allowed to invoke what, and what a server must declare to offer it — what a tool definition actually contains (name, title, description, inputSchema, outputSchema), i.e. the thing whose token cost this page is about, plus the isError contract the code-mode wrappers must translate. - MCP Clients and Hosts: Claude, Cursor, VS Code, ChatGPT, Gemini — What 'Any Client' Actually Means — per-host hard tool-count limits and primitive support; the wall that sits beyond the soft degradation described here. - MCP Registry and Server Discovery — how a developer finds, publishes, and vets a server before trusting it enough to connect a client — finding servers at all; dynamic server management is the decision of which discovered servers to actually spend context on. - MCP Limits: Prompt Injection and Tool Poisoning — why the primitives a working server relies on are also its attack surface — the cross-server untrusted-input boundary that programmatic tool calling widens, and the reason "just sandbox it" is not the whole answer. - MCP Limits: Confused Deputy and Overbroad Permission Scopes — why a server you built safely can still act with someone else's authority — the authorization-context confusion that cacheScope: "public" on an authenticated tools/list would reproduce at the caching layer. - MCP Limits: Registry Trust and Supply Chain Risks — why 'it's in the registry' is not a safety guarantee before you connect a client — the adversarial counterpart to this page: what happens when the servers filling your catalog are not the honest ones assumed throughout. - MCP vs Native Function Calling — deciding whether a tool integration is worth turning into a server — the prior question: whether a given integration should be an MCP server at all, given that each one adds to the catalog every conversation pays for. - Building an MCP Server: SDKs, Tool Schemas, Testing, and Deployment — the practical path from a 15-minute local prototype to a hosted remote server — where outputSchema gets written, which is what decides whether a client can generate typed stubs instead of accepting any.

Verified against

Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.