
Tool Use from Scratch: Build the Client Loop That Powers Every Claude Agent
Chris Harper
4 min read
Jun 27, 2026 · 12:08 UTC
TL;DR: Tool use is the API primitive that turns Claude into an agent — define a JSON schema, handle the tool_use block, return a tool_result, repeat until done.
What you'll be able to do after this:
- Wire up a custom tool with a JSON Schema and call it from the Claude API in ~20 lines of Python
- Handle the full client-tool loop: request → parse → execute → return result
- Control when Claude calls tools using
tool_choicemodes (auto,any,tool,none)
What tool use is
Tool use lets Claude call functions you define. Claude reads your tool descriptions, decides when to call a tool, returns a structured tool_use block, and your code executes the function and sends back the result. The loop repeats until Claude has enough to answer.
There are two kinds:
- Client tools — you define the schema, your code executes the call. Claude returns
stop_reason: "tool_use". - Server tools — Anthropic executes them (web search, code execution, web fetch). No handler needed.
This tutorial covers client tools.
Step 1: Define your tool
A tool needs three fields: name, description, and input_schema (JSON Schema).
import anthropic
client = anthropic.Anthropic()
tools = [
{
"name": "get_weather",
"description": (
"Returns the current weather for a city. Use this when the user asks "
"about current conditions, temperature, or forecast in a specific location. "
"Returns temperature in the requested unit and a short condition summary. "
"Does not return multi-day forecasts or historical data."
),
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state or country, e.g. 'Austin, TX' or 'Tokyo, Japan'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
}
}
]
The description is the most important part — it tells Claude when and how to call the tool. Write at least 3–4 sentences.
Step 2: Send the request
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "What's the weather like in Austin right now?"}]
)
print(response.stop_reason) # → "tool_use"
Step 3: Parse and execute
def run_tool(name, inputs):
if name == "get_weather":
# call a real weather API in practice
return {"temperature": 32, "unit": inputs.get("unit", "celsius"), "condition": "sunny"}
tool_calls = [b for b in response.content if b.type == "tool_use"]
results = []
for call in tool_calls:
output = run_tool(call.name, call.input)
results.append({
"type": "tool_result",
"tool_use_id": call.id,
"content": str(output)
})
Step 4: Return the result and continue
messages = [
{"role": "user", "content": "What's the weather like in Austin right now?"},
{"role": "assistant", "content": response.content}, # Claude's tool_use block
{"role": "user", "content": results} # your tool_result
]
final = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages
)
print(final.content[0].text)
# → "The weather in Austin is currently 32°C and sunny."
Controlling when Claude calls tools
tool_choice value | Behavior |
|---|---|
auto (default) | Claude decides whether to call a tool or respond directly |
any | Claude must call one of your tools |
{"type": "tool", "name": "..."} | Claude must call this specific tool |
none | Claude cannot call any tools |
For production agents, auto is almost always right — Claude skips the tool when it already has the answer.
Parallel tool calls
Claude may return multiple tool_use blocks in one response. Always iterate — never assume just one:
tool_calls = [b for b in response.content if b.type == "tool_use"]
# Claude may return 1 or more — loop over all of them
Sources: Tool use overview — Claude Platform Docs | Define tools | Handle tool calls | Tutorial: Build a tool-using agent