Tool use, the integration primitive
Tool use is how Claude stops being a text box and starts being an integration. It is also where most of the fiddly, testable detail lives, and where two things that sound like they belong together turn out to live in completely different places.
The loop, in five moves
You send tools, each with a name, a description, and an input_schema in JSON Schema. Claude replies with stop_reason: "tool_use" and one or more tool_use blocks. You execute. You append the assistant message verbatim, then a user message carrying tool_result blocks keyed by tool_use_id. You call again.
[
{ "role": "user", "content": "Weather in Exeter and Totnes?" },
{ "role": "assistant", "content": [
{ "type": "tool_use", "id": "tu_1", "name": "get_weather", "input": { "city": "Exeter" } },
{ "type": "tool_use", "id": "tu_2", "name": "get_weather", "input": { "city": "Totnes" } }
]},
{ "role": "user", "content": [
{ "type": "tool_result", "tool_use_id": "tu_1", "content": "17C, rain" },
{ "type": "tool_result", "tool_use_id": "tu_2", "content": "18C, cloud" }
]}
]Parallel tool use is GA, so two calls in one assistant message is normal, not exotic. Both results go back in one user message.
The bit that feels wrong: results your own code produced go in a user message. You wrote the weather lookup, so surely you are not the user. But the roles here are not about authorship, they are about direction. Everything travelling into the model is the user side, whoever generated it, and once you read the roles as inbound and outbound rather than human and machine, tool_result stops looking misplaced.
description is not documentation. It is the only thing the model has to decide whether this tool is the right one, and a vague description is the most common cause of a model calling the wrong tool. Write it like a spec, not a comment.
tool_choice controls whether, not how
Four settings. auto lets the model decide. any forces a tool call but leaves the choice open. tool with a name forces one specific tool. none forbids tools this turn.
That is the whole job of tool_choice. It picks which tool runs. It has no opinion about schemas.
Strict tool use, and the flag placement question
Strict tool use is GA, no beta header required. It constrains the generated tool input to your schema rather than merely encouraging it.
"tools": [
{
"name": "create_ticket",
"description": "Open a support ticket. Use only after the user confirms.",
"input_schema": { "type": "object", "properties": { "title": { "type": "string" } }, "required": ["title"] },
"strict": true
}
]Tools you do not have to define
Anthropic ships two kinds of tool, and the split is who executes the call, which is a different axis from GA-versus-beta and a favourite thing to conflate.
Server tools. Anthropic runs them, and no handler code appears in your application: web search, web fetch, code execution, tool search. All GA. (The advisor tool and the MCP connector are also server tools, both still beta.)
Anthropic-schema client tools. Anthropic publishes the schema and trains the model on it, but your application still executes every call and returns the tool_result: memory, bash, text editor, all GA, plus computer use, which is beta.
Two cost notes that make good questions. Web search bills at 10 dollars per 1000 searches. Web fetch is free. So a design that scrapes a known URL should reach for fetch, and one that genuinely needs discovery pays for search.
Tool search deserves a specific mention: it exists because a large tool catalogue eats your context window before the conversation even starts. If your design has forty tools, the answer is not a bigger prompt.
Try it yourself
Where strict lives
One flag, one location, and the wrong location is the whole question.
Where does the strict flag for strict tool use go, and where does it emphatically not go, and is there anything it does not work with?
Reveal answer
strict: true goes on the TOOL DEFINITION, as a sibling of name, description and input_schema. It does not go on tool_choice. tool_choice selects which tool gets used (auto, any, tool, none) and has nothing to do with schema strictness. Strict tool use is GA and needs no beta header, but it is NOT available on mcp_toolset.
Two tool calls in one turn
An assistant message comes back containing two tool_use blocks. What does the next request need?
Show answer
Correct answer: B — One user message containing two tool_result blocks, one per tool_use id
Parallel tool calls arrive as multiple tool_use blocks inside one assistant message, and every one of them needs a matching tool_result keyed by its tool_use id, all inside a single following user message. tool_result blocks go in a user message, never an assistant one, which kills the third option. Splitting into two requests breaks the alternation and leaves the first request with an unanswered tool call.
Spot the defect in this loop turn
A hand-rolled loop appends this after executing the tool. It does not work.
[
{ "role": "user", "content": "Weather in Exeter?" },
{ "role": "assistant", "content": [
{ "type": "tool_use", "id": "tu_1", "name": "get_weather", "input": { "city": "Exeter" } }
]},
{ "role": "assistant", "content": [
{ "type": "tool_result", "tool_use_id": "tu_1", "content": "17C, rain" }
]}
]Show answer
Correct answer: D — The tool_result is in an assistant message, and tool_result blocks belong in a user message
Results your own code produced still go back in a user message. That feels backwards the first time, because you generated the value and you are not the user, but the protocol treats everything you send in as the user side of the exchange regardless of where it came from. The tempting answer is the consecutive assistant messages, which is a real symptom, though it is a consequence of the actual defect rather than the defect itself. A tool_result is keyed by tool_use_id alone and carries no name field.
Forcing a tool call
The requirement is that the model must call one of your tools on this turn and must not answer in prose. Which tool_choice do you set?
Show answer
Correct answer: C — { type: any }
any means it must call a tool but may choose which. That matches the requirement exactly. tool plus a name is the tempting near-miss: it also forces a call, but it removes the model's choice entirely, which over-constrains a requirement that said one of your tools rather than this specific tool. auto permits a prose answer and none forbids tools outright.
Fill the blank without getting the level wrong
You want the generated tool input constrained to the schema rather than merely encouraged toward it. One line is missing.
{
"tool_choice": { "type": "auto" },
"tools": [
{
"name": "create_ticket",
"description": "Open a support ticket. Use only after the user confirms.",
"input_schema": { "type": "object", "properties": { "title": { "type": "string" } } },
___
}
]
}Show answer
Correct answer: A — strict: true, at the marked position inside the tool definition
Strictness is a property of the schema being enforced, so it lives with the schema, on the tool definition, beside name and description and input_schema. Moving it to tool_choice is the tempting error and the reason this gets tested, because tool_choice sounds like the place all tool configuration goes and its own name suggests it is making a decision about how the tool is used. It is not: tool_choice decides which tool runs, never how its arguments are validated. Strict tool use is also GA, so there is no beta header involved at all.
Inventory what you do not have to build
List which of your planned tools Anthropic already defines, and mark each one by who executes it.
You have checked your tool list against the GA server tools (web search, web fetch, code execution, tool search), which you can delete outright because Anthropic runs them, and separately against the GA Anthropic-schema client tools (memory, bash, text editor), where you drop the schema but still write the executor. You also noted that web search bills at 10 dollars per 1000 searches while web fetch is free.