A dense cluster of tiny pink glass seedpods narrows toward three larger coral pods on a rippled sage-green surface.
All posts

Too many MCP tools: measure and cut tool-definition tokens

Every MCP tool a client loads puts its name, description, and schema into each request. How to measure that overhead and cut it without losing the right tool.

Connect GitHub, Slack, Sentry, Grafana, and Splunk to one agent and, in Anthropic's own example, it carries 58 tools and about 55K tokens of definitions before the conversation starts (Advanced tool use, November 2025). A client that loads every MCP tool sends each name, description, and JSON Schema with every model call, so that overhead repeats on each turn whether the task touches GitHub or not.

The order I would work in is to measure first, trim the definitions you control, and only then change how tools are loaded. Deferred tool search works where the provider runs it, but it adds a search step and can miss the tool. On an AIVAX gateway, which has no tool search, the levers are the shell and per-skill tool visibility; both are covered below. One broader alternative to many narrow tools is a single command-line client: the AIVAX CLI covers the platform's whole API surface with one binary, and its responses are shaped for a model to read.

Rank your MCP tools by definition size before changing anything

Start with what the server publishes. The script below connects to a Streamable HTTP MCP server, follows nextCursor through tools/list, and ranks tools by the size of their serialized definition. Install the SDK with npm install @modelcontextprotocol/sdk@1, save the script as tool-audit.mjs, and run node tool-audit.mjs https://your-server.example/mcp. For a server behind authentication, set MCP_AUTHORIZATION to the full Authorization header value, scheme included.

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const headers = process.env.MCP_AUTHORIZATION
  ? { Authorization: process.env.MCP_AUTHORIZATION }
  : {};

const client = new Client({ name: "tool-audit", version: "1.0.0" });
await client.connect(
  new StreamableHTTPClientTransport(new URL(process.argv[2]), {
    requestInit: { headers },
  }),
);

const tools = [];
let cursor;
do {
  const page = await client.listTools({ cursor });
  tools.push(...page.tools);
  cursor = page.nextCursor;
} while (cursor);

const rows = tools
  .map((t) => ({
    name: t.name,
    characters: JSON.stringify({
      name: t.name,
      description: t.description,
      inputSchema: t.inputSchema,
    }).length,
  }))
  .sort((a, b) => b.characters - a.characters);

console.table(rows);
console.log(`${rows.length} tools, ${rows.reduce((n, r) => n + r.characters, 0)} characters`);
await client.close();

I ran it with SDK 1.32.2 against OpenAI's public documentation server, https://developers.openai.com/mcp. It listed 5 tools totalling 2,589 characters, and fetch_openai_doc alone accounted for 679 of them. That server is small; on a large one, the same ranking shows which definitions to rewrite first.

Characters approximate size; they are not tokens. For the real overhead, send the same first request twice, with and without the tool definitions, keeping the model, messages, and system instructions identical, and compare the input tokens the provider reports, with cached tokens counted separately when the provider breaks them out. Cost per task and multi-turn latency need their own comparison. When conversation logging is enabled, AIVAX conversation records store the tools, their input schemas, and usage, which gives you the before-and-after numbers without extra instrumentation.

Trimming the definitions you control comes first

Long descriptions and deeply nested schemas usually top the ranking. Shortening them needs no platform feature and helps every client that connects to the server. Keep the sentence that says when not to call a tool: dropping it saves a few tokens and can cost selection accuracy, and in my view one wrong tool call costs more than those tokens.

Clients also cut long text on their own. Claude Code truncates server instructions and the description of a tool loaded upfront at 4,096 characters, and a description loaded through tool search at 16,384; the model sees only what comes before the cut (Claude Code MCP docs). Put the words that identify the tool, and the terms a search would match, near the start.

The protocol is unlikely to help soon: SEP-1576, which proposed schema deduplication, adjustable response detail, and embedding-based tool selection, was closed on 26 June 2026 after being classified as dormant.

How many MCP tools is too many?

MCP sets no limit, so the thresholds come from vendors. Anthropic recommends tool search once an agent has 10 or more tools or more than 10k tokens of definitions, and says Claude's tool selection degrades past 30–50 available tools (tool search docs). Claude Code imposes no fixed per-server cap and calls the context window budget the practical limit. Those figures describe Anthropic's models. To find the threshold for your agent, replay tasks while you grow the catalog and note when the wrong tool starts being chosen.

Overlapping names bring that point closer. Anthropic's engineering post lists wrong tool selection and incorrect parameters as the most common failures, especially between names like notification-send-user and notification-send-channel. I would audit a catalog with several such pairs long before it reaches 30 tools.

Six ways to cut tool-definition tokens, and what each costs

Options for reducing MCP tool-definition tokens, what the model sees, and where each is available
OptionWhat the model seesTrade-offWhere it is available
Allowlist tools at the sourceOnly the listed tools of each serverStatic: you decide in advance which tools a task can useOpenAI Responses API allowed_tools. An AIVAX MCP source has no per-tool filter
Defer loading with tool searchThe search tool plus server labels; full definitions load when the model asksOne extra search step; success depends on finding the right toolAnthropic defer_loading; OpenAI tool_search, automatic for MCP in the Agents API; Claude Code by default. Not in AIVAX gateways
Move tools behind a shellOne shell function; tools become commands with generated --helpThe model must discover commands; each returns at most 4,096 charactersAIVAX enableBash with bashOptions
Reveal tools per skillread_skill, always-visible tools, and the tools of the active skillYou map tools to skills in advance; the tool list changes when the skill changesAIVAX hideToolsWithoutSkill
Trim descriptions and schemasThe same tools, with smaller definitionsShorter text can lower selection accuracy if it removes the "when not to call" ruleAny server you control
Split by taskA different small tool set per agent or gatewayMore configurations to maintainAny platform

OpenAI and Anthropic defer MCP tools behind a search step

Both providers keep every definition available to the API and leave the deferred ones out of the model's initial context. The model still sees the search tool, any non-deferred tools, and the server name or description it uses to decide what to search for.

In the Anthropic API, you set defer_loading: true once in the default_config of an mcp_toolset entry, or per tool in its configs, and include a tool search tool. The tool search docs suggest keeping the 3–5 most frequently used tools non-deferred.

The same docs state that tool search typically cuts definition tokens by over 85 percent. Anthropic's November 2025 engineering post reports internal MCP evaluations rising from 49% to 74% accuracy for Opus 4 and from 79.5% to 88.1% for Opus 4.5 with tool search enabled. The post does not publish the dataset or sample size, and both models have since been superseded, so those are the vendor's results on older models.

On supported models, Claude Code turns tool search on by default for MCP servers and loads only tool names and server instructions at session start; ENABLE_TOOL_SEARCH=false switches it off (Claude Code MCP docs).

OpenAI configures it differently per API. In the Responses API, you add {"type": "tool_search"} and set defer_loading: true on the MCP server tool, and only gpt-5.4 and later models support tool_search (tool search guide). The MCP guide also documents allowed_tools for importing only part of a server's tools. In the Agents API, MCP tools use automatic discovery: the runtime defers them and adds tool search when the model and provider support it, and the same guide says you do not need tool_search or a defer_loading flag for MCP servers there.

An AIVAX gateway narrows the list with the shell, skills, or separate gateways

An AIVAX gateway acts as an MCP client. It lists each configured server's tools and exposes every one to the model as a function, named mcp_<source>_<tool> by default (MCP docs). A source in mcpSources takes name, url, description, headers, cacheDuration, and three switches, allowClientInstructions, allowRemoteSkills, and prefixTools. None of these filters individual tools; narrowing happens through other fields of the gateway's parameters.

Turn rarely used tools into shell commands

With enableBash, the shell takes selected tools out of the function list and exposes them as commands behind a single shell function. The model runs help to list the commands and gets a --help page for each one, generated from the tool's JSON Schema. In a gateway update, these fields sit inside parameters:

{
  "parameters": {
    "mcpSources": [
      {
        "name": "crm",
        "url": "https://mcp.example.com/mcp",
        "headers": { "Authorization": "Bearer <TOKEN>" }
      }
    ],
    "enableBash": true,
    "bashOptions": {
      "toolList": ["mcp_crm_*"],
      "toolExclusionMode": "WhiteList"
    }
  }
}

toolList accepts exact runtime names and * or ? wildcards. In WhiteList mode, the default, only the listed tools become commands and everything else stays a direct function; BlackList inverts that. Each command runs for at most 60 seconds and returns at most 4,096 characters to the model, and the shell description names only the last ten commands, so the model relies on help and on predictable tool names. Use the shell for tools you call occasionally and that take and return text. Tools that return large payloads are better left as direct functions, because the output cap would cut those payloads.

Reveal tools per skill with hideToolsWithoutSkill

With hideToolsWithoutSkill on, the gateway offers the model only read_skill and the names in alwaysVisibleTools. When the model activates a skill with read_skill, the tools in that skill's options.allowedToolsNames join the list on the next model call. A support skill with "allowedToolsNames": ["mcp_crm_get_ticket", "mcp_crm_update_ticket"] keeps both ticket tools out of every request until the conversation needs them. The gateway side looks like this, again inside parameters; both fields are listed in the AI Gateway payload of the API reference:

{
  "parameters": {
    "skills": ["<support-skill-id>"],
    "hideToolsWithoutSkill": true,
    "alwaysVisibleTools": ["mcp_crm_search_contacts"]
  }
}

Only the most recent successful read_skill call counts. A new call replaces the active skills instead of adding to them, one call with several slugs reveals the union of their tools, and a call with an empty list switches every skill off. A call blocked by a gateway worker or rejected as invalid leaves the previous skill active, a behavior that shipped on 16 October 2026 (changelog). Tool names are matched exactly, ignoring case, with no wildcards, so list mcp_crm_get_ticket rather than mcp_crm_*. Skills imported from an MCP server through allowRemoteSkills carry no allowedToolsNames, so activating one never reveals a tool. If you combine the flag with the shell, put shell in alwaysVisibleTools; otherwise the filter hides it as well.

The flag has costs of its own. Each skill on the gateway adds its slug and description to the system instructions, so a long skill list brings back some of the tokens you removed. toolContextCount rewrites tool results older than the most recent N to [tool response truncated - call this tool again], and in the gateway code that happens before the visibility filter runs. Once the read_skill result falls outside that window it stops counting, and the skill's tools disappear until the model reads the skill again, so set toolContextCount above the number of tool calls a skill usually needs.

The flag changes only which tools the model receives. A call to a hidden name is still resolved among all of the gateway's functions, MCP tools included. To block a tool, have a gateway worker answer the tool.called event with a non-2xx status; the gateway then returns a blocked-execution message instead of running the tool.

Split the catalog across gateways

A support gateway that connects only the CRM server carries only CRM tools. Unrelated tools never reach the request, and nothing depends on the model activating the right skill. The price is several gateway configurations to keep in sync.

Whichever route you take, tool descriptions remain untrusted input, however they reach the model. MCP is a trust boundary, not just a tool catalog covers reviewing a third-party server before connecting it, and MCP skills explains what read_skill loads from a server and when.

A smaller tool list changes cost and caching

Fewer definitions lower the input tokens added to each request, and caching follows the request prefix. OpenAI's tool search guide notes that changing a definition can invalidate a cached prefix, and its tool search injects discovered tools at the end of the context to preserve the cache. Anthropic returns a 400 for a tool that has both defer_loading: true and cache_control, and asks for the breakpoint on a non-deferred tool (tool search docs). In an AIVAX gateway with hideToolsWithoutSkill, the tools array sent to the provider changes whenever the active skill changes, and toolContextCount rewrites older messages, which also changes the prefix (pipelines docs). Why a prompt cache hit rate is low shows how to compute the hit rate and find the change that broke it.

Replay the same tasks before and after the cut

A cut that hides a needed tool produces the failures Anthropic reports for large catalogs: the wrong tool, or the right tool with incorrect parameters. Before and after each change, replay a fixed set of tasks and record whether each finished, its input tokens, and its latency. For multi-step flows, an AIVAX Agentic Test runs the same simulated conversation against the gateway before and after the change and scores the outcome.

Read on 26 October 2026: the provider pages, SEP-1576, and the AIVAX gateway code and docs were checked that day. I ran the audit script against one public, single-page server without authentication, so the MCP_AUTHORIZATION header and multi-page listing were not exercised. On November 10, 2026 I created a gateway on the live AIVAX API with the mcpSources, enableBash, bashOptions, hideToolsWithoutSkill and alwaysVisibleTools settings from the JSON above, read it back with every field as written, and deleted it. The skill visibility rules, the toolContextCount interaction, the execution lookup, and the tool.called block for MCP tools come from the gateway code and its unit tests. Token savings and the effect on prompt caching were not measured, and whether an activated skill survives across stateless chat completion requests depends on the client resending the tool messages. Provider figures are quoted, not reproduced.