What You Need to Know
A tool is an action Claude can ask for while it works. Some tools run in your application and others run on Anthropic's infrastructure. A tool you define has three parts: a name, a description and an input_schema. The tool set is every tool sent with a request.
Take a fish and chip shop whose owner runs an assistant. One tool, fryer_oil_status, reports whether the oil in a fryer is still fit to use. Another, fryer_oil_readings, returns the test readings that verdict came from. A third returns the till's takings by the hour. When this assistant goes wrong, it calls the wrong tool, invents a figure, or keeps calling the same tool. The cause is almost never the model. It is what a tool says, what a tool returns, or what the tool set looks like from outside.
Three Surfaces and Nothing Else
When the model decides whether to call a tool, it reads three things: the name, the description and the input_schema. It cannot see your handler, the docstring on the Python function or the OpenAPI contract behind it. Nor can it see the ticket that explains why the tool exists, or the conversation in which your team agreed which tool to use when.
If the model chose badly, the information it needed was missing from one of those three places. The fix is to put it there. A bigger model, a lower temperature or an extra layer of routing supplies no information, so none of them can fix an information problem.
In the shop, the owner knows that "is the oil all right in fryer two?" means the verdict, not the readings. The model only knows that if a description says so.
What a Description Has to Say
Anthropic ranks the description above the schema and the model. The tool-definition best practices open with "Provide extremely detailed descriptions. This is by far the most important factor in tool performance".
Learn the docs' list of what a description should carry as a checklist. It is exactly what a weak description leaves out.
- what the tool does
- when it should be used, and when it should not
- what each parameter means and how it affects the tool's behaviour
- any important caveats or limitations, explicitly including what information the tool does not return
The docs also set a floor: at least three to four sentences for each tool description, and more if the tool is complex.
The usual failure is a description that paraphrases the name. The first example below, from a weld-seam inspection system, is the version that ships when a developer is thinking about the endpoint rather than the reader.
POOR = {
"name": "weld_seam_grade",
"description": "Gets the grade for a die.",
"input_schema": {
"type": "object",
"properties": {"seam_id": {"type": "string"}},
"required": ["seam_id"],
},
}Every word of that description was already in the name. Here is the same tool, written for a reader new to the system behind it.
WELD_SEAM_GRADE = {
"name": "weld_seam_grade",
"description": (
"Returns the grade recorded against a welded pipe seam at its most "
"recent inspection pass: PASS, WATCH or WITHDRAWN, together with the "
"inspection date and the inspector's one-line note. Use this when "
"someone asks whether a seam is fit to run, or what its current status "
"is. Do not use it to obtain the measurements the grade was derived "
"from; weld_seam_readings returns those. It does not return the "
"seam's repair history, the sections it joins, or any measurement "
"value."
),
"input_schema": {
"type": "object",
"properties": {
"seam_id": {
"type": "string",
"description": (
"Seam identifier as stamped on the weld map, in the form S-4471."
),
}
},
"required": ["seam_id"],
},
}That is five components in four sentences: what the tool does, when to reach for it, the boundary against its sibling, what comes back, and what does not.
The shop's fryer_oil_status needs the same five.
Disambiguation Is a Sentence, Not an Inference
The commonest tool failure in production is two tools that could each plausibly answer the same request. Neither tool has to be badly written for it to happen. In the shop, the verdict tool and the readings tool are both about the fryer's oil. The owner asks "is fryer two still good?". A model choosing between two four-word descriptions will pick inconsistently. A new starter would too, for the same reason: nothing says which tool is meant.
The fix is textual, and it is cheap. Each description names the other tool and states the boundary, in both directions. fryer_oil_status says not to use it for the readings, because fryer_oil_readings returns those. fryer_oil_readings says the reverse for the verdict. The when-not-to-use clause is the half candidates leave out.
The routing tool is the trap that sounds like engineering
The move a real developer proposes at this point is a third tool whose job is to choose between the first two. That adds a layer and leaves the cause in place. The router must draw the same distinction from the same two uninformative descriptions. The model must now also decide whether to call the router at all. A turn has been spent, and the model is back at the original ambiguity. Indirection cannot supply information a description is missing. Only the description can.
Sometimes the boundary is hard to state in one sentence. That is evidence the two tools should be one tool with a parameter, the consolidation the docs recommend (covered below). Rewriting descriptions fixes overlap. Consolidation fixes sprawl.
The Schema Is the Other Half of the Contract
The description tells the model when to reach for a tool. The schema tells it what a well-formed call looks like. The description is a suggestion, and the schema is enforced.
Closed sets belong in enum, not in prose asking politely for one of three values. Mandatory fields belong in required. Units and formats belong in the per-property description. "Date" is not a format, and a model asked for a date will pick whichever of five plausible forms reads naturally. If the shop's fish order tool takes a delivery date, its schema should say which form. A schema that permits nonsense will receive nonsense. The handler then spends its life defending against inputs the schema could have refused.
Two current properties extend this. input_examples attaches an array of example input objects to a tool definition. Each example is validated against the tool's own input_schema. An invalid one returns a 400 rather than silently misleading the model. It costs roughly 20 to 50 tokens for a simple example and 100 to 200 for a complex nested object. So it earns its place on tools with nested objects, optional parameters or format-sensitive fields, and not on a one-field lookup such as fryer_oil_status. It is not available on server tools, nor on the computer use and browser use client toolsets.
strict: true on a tool definition guarantees that the inputs Claude sends match your schema exactly. That removes the missing-parameter class of failure at the source, instead of catching it in the handler. Neither property replaces the description. Both constrain the shape of a call the model has already decided to make.
What the Tool Returns When It Fails
What a tool returns when it fails is interface design, not exception handling.
The owner asks how Friday's lunchtime takings compared with last week. The takings tool calls the till system, and the call times out. The handler catches the timeout and returns an empty list, so the loop keeps moving. The model receives a well-formed result saying that nothing went wrong and that there is nothing there. The most probable next step is a confident answer built from the surrounding context. From outside, that is what fabrication looks like. Returning the last known good value is worse still: confidently wrong, current-looking, and with no trace that anything failed.
The pattern that works is a tool_result carrying is_error: true. Its body is written for a reader who has to decide what to do next. The handler below shows it on the weld-seam inspection system's measurement lookup.
def tool_result(tool_use_id: str, content: str, is_error: bool = False) -> dict:
block = {"type": "tool_result", "tool_use_id": tool_use_id, "content": content}
if is_error:
block["is_error"] = True
return block
def wall_thickness_series(tool_use_id: str, section_ref: str) -> dict:
try:
series = gauges.lookup(section_ref, timeout=8.0)
except gauges.Timeout:
return tool_result(
tool_use_id,
"Gauge-history lookup timed out after 8s. The service is intermittent this "
"morning; a single retry is worth attempting. Do not report thickness "
"quantities from any other source.",
is_error=True,
)
except gauges.UnknownRef:
return tool_result(
tool_use_id,
"No section matches that reference. Valid references look like "
"R-2026-0417: an R, the year, and a four-digit sequence.",
is_error=True,
)
return tool_result(tool_use_id, format_series(series))The docs state the design instruction directly: "Write instructive error messages. Instead of generic errors like failed, include what went wrong and what Claude should try next (for example, Rate limit exceeded. Retry after 60 seconds.). This gives Claude the context it needs to recover or adapt without guessing". The shop's takings tool would say that the till lookup timed out and whether one retry is worth trying.
People over-claim on two points here. First, on invalid tool calls the documented behaviour is that Claude "will retry 2-3 times with corrections before apologizing to the user". The folk story that a bad result causes an endless identical retry is not a documented mechanism. Second, server tools are handled differently. The API deals with their errors itself. In the docs' words, "Unlike client tools, you do not need to handle is_error results for server tools".
Ask which fault the option actually addresses
A longer timeout and a bounded retry are both good engineering. Neither changes what the model receives when the call finally does fail. They only change how often it fails. A shop whose takings tool times out less often still gets invented takings when it does time out.
Where the Code Runs
One tools array can hold tools from two execution models side by side.
A client tool is yours. The model emits a tool_use block. Your process runs the function, under your credentials, inside your network. You send back a tool_result. Anthropic's infrastructure never executes it. That is why a client tool can reach a database on a private subnet, such as the shop's till database.
A server tool is Anthropic's. The call appears as a server_tool_use block whose id carries the srvtoolu_ prefix. The docs spell out the consequence: "The API executes the tool internally. You see the call and its result in the response, but you don't handle execution. Unlike client tool_use blocks, you don't need to respond with a tool_result". Its result block arrives in the same assistant turn, paired by tool_use_id.
Where the code runs decides what it can reach, whose credentials it carries, who owns its failure modes, and which of them you must handle at all.
Anthropic-provided is not the same claim as Anthropic-executed. The Tool reference puts it in one line. Anthropic provides server tools that execute on Anthropic's infrastructure. It also provides client tools where Anthropic defines the schema but your application handles execution. Several familiar Anthropic-schema tools sit on the client side, and your process runs them. If the shop's assistant used one, the shop's own code would run it.
Dispatch, and Approval as a Design Decision
The harness, the code between the model and your tool functions, is where a tool set becomes a system. It receives the tool-use request and resolves the name to a handler. It validates the input again if you do not trust the model to be strict. It applies whatever policy the operation's classification demands. It executes under a timeout, logs the call and its outcome, and formats the result the loop continues from. Putting these jobs at the dispatch point, not in each handler, keeps each tool a plain function you can unit-test with no model anywhere near it.
Execution order is a dispatch decision too. As of September 2026 the API does not prescribe an order for the tool_use blocks in one turn. Independent, read-only calls are usually safe to run in parallel. Anything with side effects, shared state or ordering requirements is better run one at a time. If one turn asks for both fryers' oil status and a fish order, the two checks can run together and the order alone. If you run a batch in sequence and stop at a failure, still return a tool_result for each call you skipped, with is_error: true and a short explanation.
Approval is the policy that lives in that layer. The classification behind it is a design decision, made once, when the tool set is designed. The table below is one way to write it.
type Band = "automatic" | "confirm" | "two-party";
const APPROVAL: Record<string, Band> = {
weld_seam_grade: "automatic", // read-only
weld_seam_readings: "automatic", // read-only
stores_wall_thickness_series: "automatic",
gauges_book_out_section: "confirm", // consequential, reversible with effort
weld_seam_withdraw: "two-party" // halts an inspection run; not undone quietly
};
export function bandFor(toolName: string): Band {
return APPROVAL[toolName] ?? "two-party"; // unknown tools are never automatic
}Read-only and reversible operations run automatically. Consequential or irreversible ones warrant a confirmation step. The genuinely dangerous ones warrant a gate a second party has to pass. In the shop, checking the oil runs automatically. Placing the weekly fish order asks the owner to confirm. Paying a supplier invoice needs a second person to sign it off. MCP states the same principle from the protocol side: tools may require user consent before execution. Applications implement that through tool visibility, per-execution approval dialogues, pre-approval settings and activity logs.
As of September 2026 an MCP tool can carry annotations such as readOnlyHint and destructiveHint. The specification calls them hints that are not guaranteed to describe the tool faithfully. Clients must treat them as untrusted unless they come from trusted servers. So a server you have not vetted cannot put its own tools in the automatic band by calling them read-only. Band them as you would any unclassified tool.
Three details of that table each mark a place where a real harness leaks.
First, the default matters more than the entries. ?? "two-party" means a tool somebody adds next quarter and forgets to classify is gated, not automatic. A lookup that defaults to "automatic" turns every future omission into an unreviewed capability.
Second, the classification belongs to the operation, not to the caller. Banding by which user is asking puts an authorisation decision in a table built to describe consequences. The two drift apart the moment a second interface appears. Paying a supplier invoice has the same consequences whether the owner asks or a new starter does.
Third, the band is not the enforcement. The table decides what the harness asks for. That is a policy statement, and the model plays no part in it. What stops a two-party call from proceeding is code the harness runs before dispatch, not the band itself.
Confirmation also has a cost that is easy to under-price. Every confirm band puts a human in a loop. A set that bands too much trains its operators to approve without reading. An approval nobody reads is a worse control than none, because it manufactures a record that looks like oversight. An owner asked to approve every oil check would soon stop reading. So when banding, the useful question is not "could this do damage?". It is "would a competent operator, at the moment they are asked, have the information to answer?". If the answer is no, the gate belongs somewhere the answer is knowable, or the operation belongs behind a narrower tool.
The Shape of the Tool Set
A tool set is a menu the model reads on every turn. It fails in two opposite directions.
Sprawl is one tool per API endpoint. Eleven endpoints become eleven tools, and several of them are thin wrappers over neighbouring operations. Accuracy falls as the set grows. Every near-duplicate adds a selection decision, and every definition costs context on every request. A shop that wrapped each till endpoint as its own tool would hit this. The documented remedy is consolidation: "Rather than creating a separate tool for every action (create_pr, review_pr, merge_pr), group them into a single tool with an action parameter. Fewer, more capable tools reduce selection ambiguity". Anthropic's engineering write-up says the same from the other end: more tools do not always lead to better outcomes. A few thoughtful tools that target specific high-impact workflows beat wrapping every endpoint.
Overlap is the other direction: the disambiguation problem again, as with the shop's oil verdict and oil readings.
Two further properties keep a growing tool set readable. The first is namespacing by service or resource, so github_list_prs rather than list_prs. It makes boundaries obvious as the library grows, and it matters more once tool search is in play. The second is result shape. Return only high-signal information. Use semantic, stable identifiers such as slugs or UUIDs rather than opaque internal references. Include only the fields the next decision needs. A tool that passes the whole upstream payload through floods the context and buries the two fields that mattered.
Namespacing also settles clashes between servers. As of September 2026 the MCP specification scopes tool-name uniqueness to a single server. So a client that aggregates several servers can meet two tools both called search. That client should prefix each tool with a server identifier, not with the server's self-reported serverInfo name. The specification says that name is not guaranteed to be unique.
Result shape covers attribution as well. A client tool's tool_result can carry search_result blocks, each with a source and title you supply. Citations are off by default. With them enabled, Claude cites these blocks the way it cites web search results. The attribution then comes from your data, not from a reference Claude has to copy out correctly.
Sometimes a tool set really is large. defer_loading excludes a tool definition from the initial system prompt. The definition loads on demand when the tool search tool returns a reference to it. It is also configurable per tool on an MCP toolset, which is where large connected tool sets usually come from. Deferring everything has its own cost: each deferred tool needs a search step before Claude can call it. So the tool search docs advise keeping your three to five most frequently used tools non-deferred. The shop's oil status check would be one.
A second lever works on results rather than definitions. Programmatic tool calling, generally available as of September 2026, lets Claude write code in the code execution container that calls your tools. Adding a code execution caller such as code_execution_20260120 to a tool's allowed_callers makes it callable from that code. Each call still comes back to your application to run. The intermediate results never enter Claude's context. So fanning out over many records, or filtering a large result, takes one trip through the model instead of one per call. Totalling a month of the shop's hourly takings is that kind of job. Tools with strict: true and tools from the MCP connector cannot be called this way.
From Symptom to Surface
Work back from the symptom to the place that caused it.
The assistant called the wrong tool. It chose from names, descriptions and schemas, so the fault is in one of those three. Ask whether any sentence in either description tells the two cases apart. If none does, add the disambiguating sentence and a when-not-to-use clause to both. In the shop, the two oil tools each name the other.
The assistant invented data. A well-formed result carried no information and no error, so filling the gap from context was the most probable next step. The fix is what the tool returns when it fails. Retries, timeouts and larger models make the failure rarer, but they do not change what happens when it occurs. In the shop, the takings tool returns is_error: true and says what failed.
The assistant called the same tool again and again, or stalled. The usual cause is an unactionable result. It says the request failed, but not whether a retry would help or what a valid input looks like. The model has nothing to change between attempts.
In the exam
This is the heaviest sub-skill in Domain 8, and its items rarely announce themselves. Half arrive as a symptom, so run the routine above rather than pattern-matching under time pressure.
The wire mechanics belong to Domain 2: the tools request parameter, the tool_use and tool_result content blocks, and tool_choice with its four values. Domain 8 owns the design question on top: what the name, description and schema should contain. A stem that hands you a trace and asks you to name the failure is Domain 4. Designing the result that prevents it is this sub-skill. Deciding which operations belong in which approval band, and expressing that in the tool surface, is 8.1. Making the gate an enforceable control that fires whether or not the model cooperates is Domain 7. Both sides of that line are examined. An option that relies on an operator noticing something is not a control on either side.
For a wrong-tool stem, the keyed answer rewrites both descriptions so each names the other and says when not to use itself. The when-not-to-use clause is the half the exam keys. A candidate who can name the five components of a good description can rewrite any thin one under exam pressure. Options about bigger models, sampling or routing are distractors. The exam also builds distractors out of the overlap and sprawl pair. Offering consolidation where a description rewrite belongs, or the reverse, is the strongest wrong answer available. input_examples and strict: true are both mechanism names this cert can key on.
What a tool returns on failure is the most consequential item in this sub-skill. For a fabrication stem, the answer is what the result says on failure. A longer timeout and a bounded retry appear as fully stated, confident distractors. An option that rests on the endless identical retry story asserts something the docs do not say.
The difference between the two execution models is examinable, with what follows from it: reach, credentials, failure ownership, and which failures you must handle. The provided-versus-executed distinction catches candidates, and it recurs in 8.3. An option asserting that any built-in tool runs on Anthropic's servers is unsafe ground. A question can still turn on where a named tool runs. A fair one says so in its stem or options, as when an option states that your backend carries out the text editor's commands. Don't assume a location the question leaves out.
Sources and Dates
The poor and good description pair follows the shape of the docs' own poor and good get_stock_price exemplar; the tool and its wording are ours. Which tools sit in the server list and which in the client list changes between releases, and so do their dated version strings. That inventory belongs in the domain quick reference, with a date on it.
Exam Traps
Reading an item about the wrong tool being chosen as a sampling problem, and selecting the option that lowers the temperature to make selection deterministic.
Temperature governs how the model samples among continuations; it cannot supply a distinction two descriptions failed to carry. The exam guide kills the same posture elsewhere in one clause of its own published rationale, Temperature (A) is irrelevant, which is a good signal that it is a house distractor across the paper.
Answering inconsistent selection between two confusable tools by adding a third tool that decides which of the two to call.
It adds a layer instead of removing the ambiguity. The router faces the same distinction from the same unhelpful descriptions, the model must decide whether to call the router at all, and a round trip has been spent to arrive back at the original problem. The keyed fix is rewriting both descriptions so each names the other and says when not to use itself.
Choosing a longer timeout, or two automatic retries, when the stem asks what stops the agent reporting figures nobody recorded.
Both are genuinely good engineering and both are fully stated, which is what makes them dangerous. Neither changes what the model receives when the call finally fails, so the fabrication survives at a lower rate. The answer is the result: is_error true, plus a body saying what failed, whether a retry helps, and what a valid input looks like.
Merging two confusable tools behind an action parameter, or splitting a consolidated tool, without checking which fault the stem actually describes.
Consolidation is the documented remedy for SPRAWL, eleven near-identical endpoint wrappers. Rewriting descriptions is the remedy for OVERLAP, two tools chosen inconsistently. Both moves are recommended somewhere in the docs, so the wrong one of the pair is the strongest distractor an item can offer.
Practice Scenario
A pipeline-integrity contractor runs an operations assistant with eleven tools over its inspection programme. Two of them, one returning the grade recorded against a welded seam at its last inspection and one returning the gauge readings behind that grade, are described in four words each, and the assistant picks the wrong one about one request in fifteen. Separately, when the wall-thickness tool times out it returns a zero-length series, and the assistant then reports corrosion rates nobody measured. A developer proposes a twelfth tool that decides which of the other eleven to call. Which change addresses both faults?
Build Exercise
Two Confusable Tools, One Rewrite, One Honest Failure
What you'll learn
- How the name, description and schema are the only surfaces the model selects from
- How a disambiguating sentence in both descriptions fixes inconsistent selection
- How an informative is_error result prevents the model inventing around a silent failure
- How to tell which of two documented remedies, consolidation or disambiguation, a fault actually calls for
- Define two deliberately confusable tools over the same system, each described in a single clause that restates its name, and write down twenty representative requests together with the tool you expect for each
Why: A baseline you predicted in advance is the only way to see selection accuracy move, and predicting it exposes how much you were relying on knowledge the model never had.
You should see: Two thin tool definitions and a twenty-row table of request and expected tool.
- Run all twenty and record which tool was actually called each time
Why: The baseline will be worse than you expect, which is the point: the model is not choosing badly, it is choosing between two descriptions that do not distinguish themselves.
You should see: A hit rate noticeably below what you predicted, with the misses clustered on the ambiguous requests.
- Change nothing but the two descriptions. Each states what the tool does, when to use it, what each parameter means, what comes back, and one sentence naming the sibling and saying when NOT to use this one. Re-run the same twenty
Why: Isolating the change to the description proves the claim that detailed descriptions are by far the most important factor in tool performance, on your own numbers.
You should see: A materially higher hit rate with no code change anywhere else.
- Now break the other tool: make it time out and return an empty result, and run five requests that need it. Record verbatim what the assistant tells the user
Why: Watching a well-formed empty result turn into a confident invented answer makes the fabrication mechanism concrete rather than theoretical.
You should see: At least one answer stating something the tool never returned, delivered with no hedge.
- Replace the empty result with a tool_result carrying is_error true and a body saying what failed, whether a retry would help, and what a valid input looks like. Re-run the same five
Why: The contrast between the two transcripts is the whole teaching point of the error-design material, and it is the shape of every fabrication item in this sub-skill.
You should see: The assistant reporting the failure, or retrying, rather than filling the gap from context.
- Write down which of the two changes bought more, whether either needed a code change outside the description and the failure path, and which of your eleven-tool sprawl or two-tool overlap problems each one actually addressed
Why: Naming which remedy fits which fault is exactly the discrimination the exam builds its strongest distractors from.
You should see: A short note that separates consolidation, which fixes sprawl, from disambiguation, which fixes overlap.
Sources
- Define tools — Anthropic, the description best practices, the good and poor examples, consolidation, namespacing and input_examples
- Handle tool calls — Anthropic, the tool_result shape, is_error, instructive error messages and the documented retry behaviour
- Server tools — Anthropic, the server_tool_use block, the srvtoolu_ prefix and who executes what
- Tool reference — Anthropic, the server-versus-client split and the optional tool-definition properties
- Writing tools for agents — Anthropic, why more tools do not always mean better outcomes
- Tool search tool — Anthropic, defer_loading, on-demand tool discovery and keeping the most-used tools non-deferred
- Strict tool use — Anthropic, strict: true and guaranteed schema conformance
- Parallel tool use — Anthropic, execution order for one turn's calls and the is_error result for a call you did not run
- Programmatic tool calling — Anthropic, allowed_callers and calling tools from code execution
- Search results — Anthropic, search_result blocks returned from tool calls and their citations
- Understanding MCP servers — Model Context Protocol, user consent and approval affordances from the protocol side
- Tools (specification 2026-07-28) — Model Context Protocol, tool annotations as untrusted hints and tool names scoped to one server
- Claude Certified Developer Foundations Exam Guide — Domain 8, Tool Implementation — Anthropic