Tool Use & Function Calling
How language models invoke external tools: the request -> tool_use -> execute -> tool_result cycle, JSON Schema tool definitions, parallel tool calls, structured outputs, and error handling — with spec-accurate examples from the Anthropic Messages API and OpenAI function calling, and how MCP generalizes per-app tool wiring into one standard.
A language model on its own produces only text. Tool-use (equivalently function-calling) is the mechanism that turns that text generator into something that can act: you describe a set of operations the model is allowed to invoke, and on each turn the model may emit a structured request to run one of them. Your code executes the operation and feeds the result back, and the model continues. This is the engine inside the agent loop — every "action" an agent takes in the world is, mechanically, a tool call.
The crucial property is that the model never executes anything itself. It emits a structured request; your application (or, for some providers, the provider's infrastructure) runs the code and returns the output. Anthropic's docs frame this as a contract: "You specify what operations are available and what shape their inputs and outputs take; Claude decides when and how to call them." Engineers with classical API experience can treat it like any other typed interface — define the schema, handle the callback, return a result — except the caller on the other side is a model choosing which function to invoke based on the conversation.
The tool-call cycle
Every provider implements the same four-beat loop, differing only in field names and message shapes:
- Request. You send the conversation plus a list of tool definitions.
tool_use. Instead of (or alongside) a final answer, the model returns a structured call: a tool name and a JSON arguments object. The response carries a signal that this happened — Anthropic setsstop_reason: "tool_use"; OpenAI populates atool_callsarray.- Execute. Your code reads the tool name and arguments, runs the corresponding function, and captures the output.
tool_result. You append the model's call and your result to the conversation and send it back. The model uses the result to produce a final answer — or to call another tool, repeating from step 2.
The loop is driven by your application, not the model: each tool call is a full round trip.
Anthropic's canonical shape is a while loop keyed on stop_reason — "while
stop_reason == "tool_use", execute the tools and continue the conversation" — exiting on
any terminal stop reason ("end_turn", "max_tokens", "stop_sequence", or "refusal").
One stop reason is not an exit: "pause_turn" signals that a server-side tool loop (e.g.
web search) ran long and should be resumed — re-send the conversation unchanged to continue,
rather than break out of the loop.
sequenceDiagram
participant App as Your App
participant Model as LLM
participant Tool as Tool / API
App->>Model: messages + tool definitions
Model-->>App: stop_reason "tool_use"
tool_use block (name, input)
App->>Tool: execute(name, input)
Tool-->>App: raw output
App->>Model: same messages + assistant turn
+ tool_result (tool_use_id, content)
Model-->>App: final text (stop_reason "end_turn")
Tool definitions: a name, a description, and a JSON Schema
A tool-schema is how you tell the model what a tool does and what arguments it accepts. Across providers it has three load-bearing parts: a name, a natural-language description, and a parameter schema written in JSON Schema. The description is not decoration — Anthropic calls detailed descriptions "by far the most important factor in tool performance," recommending at least 3-4 sentences covering what the tool does, when to use it (and when not to), and what each parameter means.
In the Anthropic Messages API, a client tool is defined by name, description, and
input_schema (the name must match ^[a-zA-Z0-9_-]{1,64}$):
{
"name": "get_weather",
"description": "Get the current weather in a given location",
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The unit of temperature, either 'celsius' or 'fahrenheit'"
}
},
"required": ["location"]
}
}
OpenAI function calling carries the same information under different keys. A function
tool nests the definition under a function object with name, description, and
parameters (the JSON Schema), tagged with "type": "function":
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA"
}
},
"required": ["location"],
"additionalProperties": false
},
"strict": true
}
}
Because parameters are plain JSON Schema, you get its full expressive range — types, enum
constraints, nested objects, required lists, descriptions per field. The model is steered
by both the schema and the prose descriptions: the schema constrains shape, the
descriptions teach intent.
A note on field names.
input_schema(Anthropic) andparameters(OpenAI) name the same thing — a JSON Schema object for the tool's arguments. Mixing them up is the most common porting bug. The wrapping differs too: Anthropic putsname/description/input_schemaat the top level of each tool; OpenAI's Chat Completions API (shown above) wraps them in{"type": "function", "function": {...}}. OpenAI's newer Responses API drops that nesting and flattensname/description/parametersto the top level alongside"type": "function"— the same fields, no innerfunctionobject. The examples on this page use the Chat Completions shape throughout.
Controlling whether the model calls a tool
By default the model decides per turn. Both providers expose a knob to force or forbid tool
use. Anthropic's tool_choice has four options: auto (model decides — the default when
tools are present), any (must use some tool), tool (must use a named tool, e.g.
{"type": "tool", "name": "get_weather"}), and none (no tools — the default when no
tools are supplied). OpenAI exposes an analogous tool_choice. Note that with Anthropic's
any or tool, the API prefills the assistant turn to force a call, so the model emits no
natural-language preamble before the tool call.
The tool_use block and the tool_result
When Claude calls a tool, the response has stop_reason: "tool_use" and its content
array contains a tool-use-block with type: "tool_use", a unique id, the tool
name, and an input object conforming to the tool's input_schema. A turn can mix a
text block (Claude explaining itself) with the call:
{
"id": "msg_01Aq9w938a90dw8q",
"model": "claude-opus-4-8",
"stop_reason": "tool_use",
"role": "assistant",
"content": [
{
"type": "text",
"text": "I'll check the current weather in San Francisco for you."
},
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "get_weather",
"input": { "location": "San Francisco, CA", "unit": "celsius" }
}
]
}
You run the tool, then continue the conversation with a user message whose content
holds a tool-result block. It carries type: "tool_result", a tool_use_id matching
the id of the call it answers, and a content field (a string, or a list of text /
image / document blocks):
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "15 degrees"
}
]
}
Two formatting rules from the spec are easy to trip over: the tool_result block must
immediately follow its tool_use (no messages in between), and within the user message the
tool_result blocks must come first in the content array — any text comes after.
Violating either returns a 400 error.
Anthropic folds tool messages into the ordinary user/assistant roles. OpenAI uses a
dedicated tool role instead. The model returns a tool_calls array where each entry has
an id, "type": "function", and a function object whose arguments is a JSON-encoded
string (not a parsed object) — so you must JSON.parse it before use:
{
"id": "call_12345xyz",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"location\":\"Paris, France\"}"
}
}
You return the output as a message with "role": "tool", a tool_call_id referencing that
id, and the result in content:
{
"role": "tool",
"tool_call_id": "call_12345xyz",
"content": "15 degrees"
}
Parallel tool calls
When a request needs several independent operations — weather in three cities, three files
read — the model can ask for them all at once. Parallel-tool-calls means a single
assistant turn carries multiple tool_use blocks. The API does not prescribe an execution
order: you may run the calls concurrently (asyncio.gather, Promise.all) for lower
latency, or sequentially if they share state or have ordering requirements.
The hard rule is the response shape: return one tool_result for each tool_use block,
all together in the next single user message. Splitting them across separate user
messages is the most common mistake — Anthropic notes it actually "teaches" the model to
stop batching, reducing future parallelism. If you choose not to run a call (say a prior
write failed), still return a tool_result for it with is_error: true and a short
explanation, so every tool_use_id is answered.
{
"role": "assistant",
"content": [
{ "type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": { "location": "San Francisco, CA" } },
{ "type": "tool_use", "id": "toolu_02", "name": "get_weather", "input": { "location": "New York, NY" } }
]
}
{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "San Francisco: 68F, partly cloudy" },
{ "type": "tool_result", "tool_use_id": "toolu_02", "content": "New York: 45F, clear skies" }
]
}
Parallelism can be disabled: Anthropic exposes disable_parallel_tool_use (which caps the
model at one tool per turn), and OpenAI exposes parallel_tool_calls, which when set to
false "ensures exactly zero or one tool is called."
Structured outputs
Sometimes you don't want the model to act — you want a guaranteed-shape JSON object back (extract fields from a document, return a typed classification). Structured-output is this guarantee: the model's output is constrained to conform to a JSON Schema you supply. There are two related surfaces for it.
The first reuses the tool machinery. Anthropic's tool definitions accept strict: true,
which "ensure[s] Claude's tool calls always match your schema exactly." OpenAI's function
definitions accept the same strict: true flag; setting it, OpenAI says, "will ensure
function calls reliably adhere to the function schema, instead of being best effort." For
strict mode OpenAI requires the schema to set additionalProperties: false, mark every
property as required, and have an object at the root.
The second is a response-format surface for when you want the model's final answer (not a
tool call) to be JSON. OpenAI distinguishes the two: function calling is for "connecting the
model to tools," while response format is for when you "want to structure the model's output
when it responds to the user." OpenAI's response_format takes a json_schema object:
{
"type": "json_schema",
"json_schema": {
"name": "weather_result",
"schema": { "type": "object" },
"strict": true
}
}
The practical heuristic from Anthropic's docs: if you find yourself "writing a regex to extract a decision from model output, that decision should have been a tool call." Parsing free-form text to recover structured intent is a sign the structure belongs in the schema.
Error handling
Tools fail — networks drop, APIs rate-limit, arguments come out malformed — and the loop
has a designed-in channel for it. In the Anthropic API you report a failed execution by
returning the error text in the tool_result content with is_error: true:
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "toolu_01A09q90qw90lq917835lq9",
"content": "ConnectionError: the weather service API is not available (HTTP 500)",
"is_error": true
}
]
}
The model reads the error and adapts — apologizing to the user, or retrying with corrected
inputs. The docs advise writing instructive errors: instead of "failed", say
"Rate limit exceeded. Retry after 60 seconds.", giving the model the context to recover.
For a malformed call (e.g. a missing required parameter), returning an is_error result
lets the model retry — Anthropic notes it "will retry 2-3 times with corrections before
apologizing." A complementary defense is strict: true, which prevents schema-violating
calls from being emitted in the first place.
Treat tool results as untrusted. Tool outputs often carry content from outside your control — web pages, inbound email, user uploads, third-party APIs. An attacker who can influence that content can embed instructions that try to redirect the model (indirect prompt injection). Anthropic recommends keeping untrusted content inside
tool_resultblocks rather thansystemprompts or plain usertext. See ecosystem-ops.html for hardening.
How MCP generalizes tool wiring into a standard
Everything above describes tools wired per application: you hand-write definitions, you hold the execution code, you manage the loop. That works, but it doesn't compose — every agent must re-implement every integration, and a tool built for one app can't be reused by another. The Model Context Protocol (MCP) standardizes this layer so tools become portable across any compliant agent.
MCP runs over JSON-RPC 2.0. A server exposes a tools/list method
that returns tool definitions, each with name, description, and an inputSchema (the
same JSON-Schema-shaped field you write by hand today — note MCP's spelling is inputSchema,
camelCase). A client calls a tool with tools/call, passing name and arguments:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "New York" }
}
}
The result mirrors the tool_result idea: a content array of typed blocks plus an isError
boolean (note MCP uses camelCase isError, vs. Anthropic's is_error). The spec
distinguishes the same two error classes the per-app world has — protocol errors (unknown
tool, malformed request) returned as JSON-RPC errors, and tool execution errors (API
failures, validation errors) returned in the result with isError: true so the model can
self-correct:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
The payoff is decoupling: any MCP-speaking agent can discover and call any MCP server's
tools without bespoke wiring, and the agent's own model-facing tool layer (Anthropic
tool_use/tool_result, OpenAI tool_calls) is bridged to MCP's tools/call by the
client runtime. MCP also goes beyond plain function calling — tools can declare an optional
outputSchema and return structuredContent, pushing structured outputs across the wire.
The protocol page, protocols-mcp.html, covers transports, discovery,
and the rest of the surface.
Takeaways
- Tool use is a contract: the model emits a structured request; your code executes and returns a result. The model never runs code itself.
- A tool is a name + description + JSON Schema. The description drives selection quality as much as the schema drives input shape.
- Field names differ by provider — Anthropic
input_schema/tool_use/tool_result/is_errorvs. OpenAIparameters/tool_calls(arguments as a JSON string) /role: "tool"— but the cycle is identical. - Parallel calls arrive as multiple
tool_useblocks in one turn; return all results together in one message. - Structured outputs (
strict: true, JSON-Schema response formats) reuse the same schema machinery to guarantee output shape. - MCP lifts per-app tool wiring into a standard (
tools/list,tools/call,inputSchema,isError) so tools become portable across agents.