Benefits of Slimming Agent Tool Definitions to Save Tokens
Your agent carries a suitcase you never see: the full text of every tool it might call. Those agent tool definitions count as input tokens on every request, so a bloated set taxes each step of the loop and makes tool choice harder. Anthropic says a typical five-server setup (GitHub, Slack, Sentry, Grafana, Splunk) can use about 55K tokens on definitions before Claude does any work. Slimming them pays off four ways.
Four benefits of slimming agent tool definitions
1. Every model call gets cheaper. Tool definitions are billed as input tokens like any other, and one user question can trigger several requests in an agent loop. A 20K-token tool set across a 10-call loop is 200K input tokens of definitions for a single question (illustrative arithmetic, not a measurement). The cheap cuts are descriptions that restate the tool name, prose that repeats an enum, and house rules pasted into twelve tools instead of into the system prompt once.
Before, one of three near-identical tools (create_ticket and close_ticket read the same way):
{
"name": "update_ticket",
"description": "This tool updates an existing ticket in the ticketing system. Use this tool whenever the user wants to update a ticket. Always confirm with the user before updating. Do not use this tool to create tickets. Do not use this tool to close tickets.",
"input_schema": {
"type": "object",
"properties": {
"ticket_id": {"type": "string", "description": "The id of the ticket. This is the id of the ticket to update."},
"priority": {"type": "string", "enum": ["low", "medium", "high"], "description": "The priority of the ticket. Can be low, medium or high."}
},
"required": ["ticket_id"]
}
}
After, one merged tool:
{
"name": "ticket",
"description": "Create, update or close a support ticket. Use action=create for a new issue; it returns the ticket id. Use action=update to change the title, body or priority of an existing ticket, and action=close to resolve one. update and close need ticket_id; create does not.",
"input_schema": {
"type": "object",
"properties": {
"action": {"type": "string", "enum": ["create", "update", "close"]},
"ticket_id": {"type": "string"},
"title": {"type": "string"},
"body": {"type": "string"},
"priority": {"type": "string", "enum": ["low", "medium", "high"]}
},
"required": ["action"]
}
}
The merged tool keeps four sentences of description, in line with Anthropic's advice of at least 3 to 4 sentences per tool, and drops the repeated boilerplate. Move "confirm before changing a ticket" into your system prompt once. Then prove the saving with the free token counting endpoint, run before and after:
import json
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-sonnet-5-5" # count against the model you will run
PROBE = [{"role": "user", "content": "hi"}]
BASE = client.messages.count_tokens(model=MODEL, messages=PROBE).input_tokens
def tool_tokens(tools: list[dict]) -> int:
counted = client.messages.count_tokens(model=MODEL, messages=PROBE, tools=tools)
return counted.input_tokens - BASE
tools = json.load(open("tools.json")) # your client tool definitions
print("all tools:", tool_tokens(tools))
sizes = {t["name"]: tool_tokens([t]) for t in tools}
for name, n in sorted(sizes.items(), key=lambda kv: -kv[1])[:5]:
print(f"{name:<30}{n:>6}")
Each single-tool count includes the fixed tool preamble the API adds, so the numbers won't sum to the total. Use them to rank your fattest tools. Recount on the model you will actually run: models from Claude Opus 4.7 onward use a tokenizer that produces roughly 30 percent more tokens for the same text.
2. The model picks the right tool more often. Anthropic's docs say tool selection degrades once you pass 30 to 50 available tools, and they suggest collapsing create_pr, review_pr and merge_pr into one tool with an action parameter. In Anthropic's own MCP evaluations, tool search lifted Opus 4 from 49% to 74% and Opus 4.5 from 79.5% to 88.1%. Those are vendor numbers, so replay your own prompts before banking them. The catch with merging: the schema can't mark ticket_id required for only two of three actions, so validate in your handler and return an error the model can recover from.
3. Your context window and prompt cache stay intact. Past roughly 10 tools, stop sending everything up front. Add the tool search tool and mark rarely used tools with defer_loading:
import anthropic
client = anthropic.Anthropic()
HOT = {"ticket", "search_docs"} # the 3 to 5 tools most requests use
# ALL_TOOLS: your full list of tool definitions
tools = [{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"}]
tools += [t if t["name"] in HOT else {**t, "defer_loading": True} for t in ALL_TOOLS]
response = client.messages.create(
model="claude-sonnet-5-5",
max_tokens=2048,
messages=[{"role": "user", "content": "Close ticket T-1042 and page the on-call"}],
tools=tools,
)
The model sees only the search tool and your hot tools. When it needs another, the API runs the search server side and loads up to five matches by default. In Anthropic's example, definitions dropped from about 77K tokens to about 8.7K, leaving roughly 191K tokens of context free instead of 123K. Deferred tools stay out of the cached prefix, so discovering one mid-conversation does not break your prompt cache. Editing an existing definition does invalidate it.
For MCP servers, whose descriptions you don't write, defer at the toolset level and keep one tool hot:
{
"type": "mcp_toolset",
"mcp_server_name": "google-drive",
"default_config": {"defer_loading": true},
"configs": {"search_files": {"defer_loading": false}}
}
Gotchas: you still send every definition in each request, because deferral changes what reaches the model's context, not your payload. At least one tool must stay non-deferred, a deferred tool can't carry cache_control, and on Amazon Bedrock server-side tool search works only through InvokeModel, not Converse.
4. Tool size becomes a number you can test. Definitions grow quietly: someone adds a paragraph in a pull request and the bill creeps. Put the counter in CI, in the same module as the script above:
MAX_TOOL_TOKENS = 6000 # set from your current count plus headroom
def test_tool_definitions_stay_within_budget():
tools = json.load(open("tools.json"))
assert tool_tokens(tools) <= MAX_TOOL_TOKENS, "tool definitions grew past budget"
The counting endpoint accepts client tools but rejects server tools, including tool search and the MCP connector, so it measures the definitions you own.
When slimming agent tool definitions is not worth it
If you have fewer than 10 tools, use all of them on nearly every request, and keep the definitions small, Anthropic's own guidance is that plain tool calling fits better than tool search. Tool search also adds a search step before a deferred tool can be called, which costs latency.
Over-trimming is the other trap. Anthropic calls detailed descriptions the most important factor in tool performance, and a model that guesses wrong burns more tokens on retries than you saved. Measure first, cut the redundancy, and stop when accuracy dips.
For anyone running agents in production, tool definitions are overhead paid on every call, so trimming them once cuts cost and misfires for the life of the system.