C Claude Cert Prep All Claude Certifications

tool_use and tool_result in Practice

The mechanics of the tool_use and tool_result message loop, including error signaling and multi-turn ID matching.

Tools & MCP·Lesson 2 of 6·8 min

Tool calling with the Claude API is a turn-based loop between your client code and the model, not a single API call. Understanding exactly which side produces which message type is a recurring exam point.

Who emits what

When the model decides to call a tool, it emits a tool_use content block inside its own assistant message. That block contains a name (the tool being called), an id (a unique identifier for this specific call), and input (the arguments, already validated against your schema's shape by the model's generation). The model does not execute anything — it only produces this block and stops generating (typically with stop_reason: "tool_use").

Execution is entirely your client's responsibility. Your code reads the tool_use block, runs the corresponding function or API call, and constructs a tool_result block. That block is sent back to the model inside a new user-role message, and its tool_use_id field must match the original id from the tool_use block it is answering. This id match is how the model correlates results with calls, which matters a lot when multiple tools are called in parallel in a single turn.

Multiple calls in one turn

A single assistant message can contain several tool_use blocks side by side, representing parallel tool calls the model wants executed. Your client should execute all of them (concurrently if safe) and then return a single user message containing one tool_result block per tool_use_id, all in that one message, before sending the next request. Sending results back one at a time across separate requests is not how the protocol is structured — the model expects all outstanding tool calls from a turn to be resolved together.

Signaling failure

When execution fails — the API times out, the input was semantically invalid even though it matched the schema, a downstream resource doesn't exist — the client sets is_error: true on the tool_result block and puts a clear, human-readable explanation in its content. This is not optional plumbing; it is the mechanism by which the model learns a call failed and can attempt a corrected retry in its next turn. Omitting is_error and just stuffing an error string into a normal-looking result can cause the model to treat a failure as a valid answer and hallucinate downstream conclusions from it.

The loop, end to end

Stepping back, a full tool-calling exchange looks like: your client sends a message with tools defined; the model responds with a tool_use block and stop_reason: "tool_use"; your client executes the tool and sends a new message containing a tool_result; the model reads that result and either produces a final text answer, or emits another tool_use block to keep working. This can chain for many turns — a research agent might call a search tool, then a fetch tool, then a summarization tool, each round trip adding one assistant tool_use message and one user tool_result message to the growing conversation. Nothing about the protocol caps how many rounds this goes on for; that's a limit your client code has to enforce itself, typically with a maximum iteration count or a timeout, since an unbounded loop is a real operational risk once tools are wired to production systems.

Conversation history matters here too: every prior tool_use and tool_result block stays in context for subsequent turns unless you deliberately prune it, which is part of why long tool-calling sessions can consume tokens quickly. Some client implementations summarize or drop older tool_result content once it's no longer relevant to the current step, precisely to keep the transcript from growing unboundedly across a long agentic loop.

Exam trap A question may ask "which component sends the tool_result block?" with "the model" as a tempting wrong answer, because tool_result blocks conceptually respond to something the model asked for. The model never sends tool_result — it only ever sends tool_use. The client (your application code) is always the one constructing and sending tool_result. Keep the direction fixed: tool_use flows model-to-client, tool_result flows client-to-model.
Scenario: the model emits two tool_use blocks in one turn — one calling a weather lookup, one calling a currency converter — because the user asked "what's the weather in Tokyo and what's 100 USD in yen." The weather call succeeds; the currency API times out. The correct client behavior is to return one user message containing two tool_result blocks: one normal result with the weather data, and one with is_error:true and a message like "currency conversion service timed out, try again" — both in the same message, matched by their respective tool_use_id values.

Try it

Using the Claude API directly (not an SDK's agent loop), write a minimal script that defines one simple tool (for example, get_time with a timezone parameter), sends a message that triggers it, manually inspects the returned tool_use block's id, constructs a tool_result with a deliberately wrong tool_use_id, and sends it back. Observe how the API responds to the mismatched id, then fix it and confirm a matched id produces a normal continued response.

← Designing Tool Schemas MCP Fundamentals: Servers, Clients, Hosts →