Agents, lesson 3
Designing tools a model can use
Names, schemas, descriptions and errors a model can act on.
The model never sees your tool's code. It sees a name, a description and a schema for the inputs, and it chooses and fills in every call from those alone. This lesson designs that interface and the errors that come back through it; you need the agent loop from "Observe, decide, act" and a little JSON.
By the end, you can:
- Write a tool definition, and predict the call a model makes from it.
- Write a description that says what goes in, what comes out, and when to use another tool.
- Shape a tool around a task, so code does the searching and the model reads only what it needs.
- Return errors a model can fix, and read a tool call and its result over MCP.
Part 1. What the model sees
Write a tool definition, and predict the call a model makes from it.
Your search_orders function might be 200 careful lines. The model sees none of them. It gets a name, a description and a schema for the inputs, and from those alone it decides when to call the tool and what to pass.
{
"name": "search_orders",
"description": "Find a customer's orders by email address. Returns up to limit orders, newest first, each with order_id, date, total and status. For one order by its ID, use get_order.",
"input_schema": {
"type": "object",
"properties": {
"email": {"type": "string",
"description": "e.g. [email protected]"},
"status": {"type": "string",
"enum": ["open", "shipped", "refunded"]},
"limit": {"type": "integer", "minimum": 1,
"maximum": 50, "default": 10}
},
"required": ["email"]
}
}Example 1.1
From a message to a call
A customer writes "Has anything I ordered shipped? I'm [email protected]." With search_orders above and get_order(order_id) on offer, what call does the model make?
- Pick the toolsearch_orders
The message gives an email, not an order ID, and the description says this tool searches by email.
- Fill the required input"email": "[email protected]"
emailis listed underrequired, so every call must have it. - Fill what the message asks"status": "shipped"
The customer asked about shipped orders, and
shippedis one of the three values theenumallows. - Leave the rest outlimit → 10
Nothing in the message sets a limit, so the model leaves it out and your code uses the default.
Answersearch_orders with {"email": "[email protected]", "status": "shipped"}.
Each part of the definition does a job. The name is how a call refers to the tool. The description decides when it's chosen. The schema decides what goes in: names, types, allowed values, and which inputs are required. The function itself never leaves your side.
Tool definition and input schema
A tool definition is all the model knows of a tool: a name, a description and an input schema, the JSON Schema for its arguments. Anthropic's API calls the schema input_schema, OpenAI's parameters and MCP's inputSchema.
tool definition = name + description + input schema
Names have rules: in Anthropic's API, up to 128 letters, digits, underscores or hyphens (MCP also allows dots). Definitions go with every request and are billed as input tokens. With strict: true, Anthropic's and OpenAI's APIs make the model's input match the schema exactly.
Your turn
A tool book_room takes room_id (an integer, required), check_in (a string in YYYY-MM-DD form, required) and nights (an integer, default 1). A guest writes "Room 12 please, 3 nights from 5 March 2026." Which input should the model send?
nights is set because the guest asked for 3. Leaving it out books the default, 1 night. The third sends a string for an integer and the wrong date form, and the last uses a name the schema doesn't have.Part 2. Descriptions do the work
Write a description that says what goes in, what comes out, and when to use another tool.
Most wrong tool calls trace back to a description. Anthropic's docs rank a detailed description as the biggest single factor in how well a tool is used, and ask for 3 or 4 sentences or more. OpenAI's offer an intern test: could a person use the tool from what you gave the model?
Example 2.1
Fixing a wrong-tool call
An agent has search_orders, described as "Searches orders.", and get_order, described as "Gets an order." A customer writes "Where is order #5521?" and the model calls search_orders. Rewrite the first description so the next call goes right.
- Say what goes in"by the customer's email address"
An order number plainly doesn't fit this tool.
- Say what comes out"up to 10 orders, newest first"
The model knows what it gets back, and that one call may not show every order.
- Say where its job ends"For one order by ID, use get_order"
The line that fixes this case. The model picks tools by their descriptions, so name the other one.
- Show formats with an example"e.g. [email protected]"
Put it on the
emailargument, where the model fills it in.
Answer"Find a customer's orders by email address. Returns up to 10 orders, newest first. For one order by its ID, use get_order." The next call is get_order with 5521.
The model reads every description each time it picks a tool. Anthropic reported that precise edits to its tool descriptions helped Claude 3.5 Sonnet reach the top score on SWE-bench Verified, a test of fixing real GitHub issues. Treat a description like a prompt: test it on real tasks, and edit.
Tool description
The plain text that tells the model what a tool does, what goes in, what comes out, and when to use a different tool. The model reads it every time it picks a tool, so it works like a prompt.
description = what it does + inputs + outputs + when not to use it
The schema carries rules too. An enum lists the only allowed values, so the model can't invent "pending". Units go in the name, timeout_seconds rather than timeout, and Anthropic suggests user_id rather than user.
Your turn
A tool takes date as a string. The agent sometimes sends "next Friday" and sometimes "05/03/2026". What's the best change?
Part 3. Tools shaped like the job
Shape a tool around a task, so code does the searching and the model reads only what it needs.
Say an agent books meetings. The obvious tools mirror your calendar API: list_users, list_events and create_event. To book three people, the model reads everyone to find them, reads three calendars, finds a free slot itself, then books.
Example 3.1
One meeting, two designs
Example sizes: 300 users at 15 tokens each, 1,800 tokens per calendar. How many tokens of tool results does the model read to book three people, with the API-shaped tools and with one schedule_event(attendees, minutes, earliest, latest)?
- Find the people300 × 15 = 4,500
list_usersreturns everyone, to find three names. - Read their calendars3 × 1,800 = 5,400
One
list_eventscall per person. - Book it4,500 + 5,400 + 80 = 9,980
create_eventreturns 80. Five calls, and every result stays in the history. - Shape the tool to the job1 call, 40 tokens
It finds the slot in code and returns one line: "Booked Tue 10:00 to 10:30, room 4".
Answer9,980 tokens over 5 calls, against 40 tokens in 1 call.
Code searches, filters and joins exactly, at no token cost. The model doing it pays on every later turn, and can still miss a row. Anthropic's guide to writing tools uses this example, and search_logs over read_logs: send the model only what decides its next step.
Agent-computer interface
The set of tools an agent works through, and how each is named, described and shaped. It's Anthropic's term, by analogy with the human-computer interface: design tools for the jobs the agent does, not to mirror your API's endpoints.
one tool per job, not one per endpoint
Three more habits from that guide. Prefix tools by service, like asana_search and jira_search. Return readable names, not long random IDs. And let the model choose the detail: in its Slack example a detailed reply took 206 tokens and a concise one 72.
Your turn
A support agent calls list_tickets, which returns all 2,000 open tickets, and then scans them for the customer's. What's the best fix?
Part 4. Errors the model can act on
Return errors a model can fix, and read a tool call and its result over MCP.
Many agents reach their tools through MCP, the Model Context Protocol. An MCP server answers tools/list with each tool's name, description and inputSchema, the definition from part 1. To run one, the app running the agent sends tools/call with a name and arguments. Follow one that fails.
Example 4.1
A bad date, fixed in one turn
A flights server offers search_flights(origin, destination, date), with the date as YYYY-MM-DD. The model sends the date as "05/03/2026". Follow the messages until it gets flights.
- The app calls the servertools/call, id 4
With the tool's name and the model's arguments.
- The server checks the input"05/03/2026" isn't YYYY-MM-DD
Before running anything. 05/03 could be 5 March or 3 May, so it won't guess.
- It returns a tool errorisError: true
A normal result whose text says what was wrong and what to send. The app passes it to the model.
- The model retriestools/call, id 5: "2026-03-05"
The conversation says which day was meant; the error gave the format.
AnswerTwo calls and one wasted turn, with no crash and no person needed.
{"jsonrpc": "2.0", "id": 4, "method": "tools/call",
"params": {"name": "search_flights",
"arguments": {"origin": "LIS",
"destination": "AMS",
"date": "05/03/2026"}}}
{"jsonrpc": "2.0", "id": 4,
"result": {"resultType": "complete", "isError": true,
"content": [{"type": "text",
"text": "date must be YYYY-MM-DD, e.g. 2026-03-05. Got 05/03/2026."}]}}One line that says what to send lets the model fix its own call. A stack trace costs tokens and explains nothing, and a bare "failed" leaves it guessing. Anthropic's docs say Claude retries an invalid call 2 or 3 times, with corrections, before apologizing to the user.
Tool execution error
A tool that ran, or checked its input, and failed. It comes back as a normal result marked as an error, isError: true in MCP or is_error: true in Anthropic's API, saying what to fix. A call that can't be routed, like an unknown tool, gets a JSON-RPC protocol error instead.
error result = what went wrong + what to send instead
Successful results need the same care. A tool that returns 5,000 rows fills the context, and they stay there. Return what the next step needs, say the 10 newest orders and a note "412 more, narrow by status". Claude Code caps a tool's response at 25,000 tokens by default.
Your turn
A model calls convert with the currency code "YEN", which your tool doesn't know. Which result helps it most?
Where you'll meet it
A common design question: give an agent access to our order system. Don't wrap every endpoint. List the jobs the agent does, write one tool per job with a description of 3 or 4 sentences and a strict schema, and say how errors come back: as results that tell the model what to send instead.
Anthropic found that swapping long random IDs for readable names, or even a 0-indexed ID scheme, made Claude noticeably more precise at finding the right item. OpenAI suggests keeping fewer than 20 functions on offer at the start of a turn, as a soft limit: every definition costs tokens, and overlapping jobs blur the choice.
SCHEMA = {
"params": {"email": str, "status": ("open", "shipped", "refunded"), "limit": int},
"required": ["email"],
}
def check(args, schema):
for key in schema["required"]:
if key not in args:
return f"missing '{key}'. Pass the customer's email address."
for key, value in args.items():
rule = schema["params"].get(key)
if rule is None:
return f"unknown argument '{key}'. Allowed: {', '.join(schema['params'])}."
if isinstance(rule, tuple) and value not in rule:
return f"{key} must be one of {', '.join(rule)}, got '{value}'."
if isinstance(rule, type) and not isinstance(value, rule):
return f"{key} must be {rule.__name__}, got {value!r}."
return "ok"
print(check({"status": "open"}, SCHEMA))
print(check({"email": "[email protected]", "status": "pending"}, SCHEMA))
print(check({"email": "[email protected]", "limit": "20"}, SCHEMA))
print(check({"email": "[email protected]", "limit": 20}, SCHEMA))
# missing 'email'. Pass the customer's email address.
# status must be one of open, shipped, refunded, got 'pending'.
# limit must be int, got '20'.
# okEach message goes back as the tool's result, marked as an error, for the model to read on its next turn. In real code a library such as jsonschema checks the arguments against the schema itself.
Check yourself
Four questions from across the lesson. Try each one before you look back.
1
An agent keeps calling search_orders when the user gives an order ID like "#5521". What's the most likely fix?
2
A tool's argument check fails. Why return the error message to the model as the tool's result, instead of raising an exception in your code?
3
A read_file tool returns whole files. On a 30,000-line log it returns everything. What's the better design?
4
Over MCP, which message gives the app a tool's name, description and input schema?
tools/list returns every tool's definition, with the schema under inputSchema. tools/call runs one tool and returns its result, and the model only ever sees definitions the app hands it.What to remember
1What the model sees
The model sees only a tool's name, description and input schema (JSON Schema), sent and billed on every request, and fills every call from those alone.
2Descriptions do the work
A description says what goes in, what comes out and when to use another tool, in at least 3 or 4 sentences, with formats shown by example, like YYYY-MM-DD.
3Tools shaped like the job
Shape tools to the job: one
schedule_eventcall returning 40 tokens beats five API-shaped calls returning 9,980.4Errors the model can act on
Return failures as results the model can act on (
isError: trueplus what to send instead); over MCP, tools arrive bytools/listand run bytools/call.
Recap and going further
The lesson on one page
- A name, a description and a JSON Schema
- Never the function's code
- Sent and billed on every request
- What goes in and what comes out
- When to use a different tool instead
- Formats shown with one example
- schedule_event, not three list calls
- Search in code, not in the context
- Readable names, prefixed by service
- Check arguments before running
- An error result says what to send instead
- An unknown tool is a protocol error
- tools/list: name, description, inputSchema
- tools/call: a name and arguments
- The result: content, and isError
Try this
Measure what a description is worth
Give a model two overlapping tools, search_orders(email) and get_order(order_id), each with a one-line description. Write 20 user messages that should each use one or the other, run them, and count wrong-tool and bad-argument calls. Then rewrite both descriptions to say where each one's job ends, add an enum for status, return errors as results that say what to send instead, and run the same 20 again.
You need: Python and an API key for any model with tool calling.
Look for
- How many wrong-tool calls the one-line descriptions caused.
- Which messages still go wrong after the rewrite, and why.
- Whether the
enumstops invented status values. - How many turns the model needs to recover when an error says what to send instead.
The words, in plain language
- Tool definition and input schema
- A tool definition is all the model knows of a tool: a name, a description and an input schema, the JSON Schema for its arguments. Anthropic's API calls the schema
input_schema, OpenAI'sparametersand MCP'sinputSchema. - Tool description
- The plain text that tells the model what a tool does, what goes in, what comes out, and when to use a different tool. The model reads it every time it picks a tool, so it works like a prompt.
- Agent-computer interface
- The set of tools an agent works through, and how each is named, described and shaped. It's Anthropic's term, by analogy with the human-computer interface: design tools for the jobs the agent does, not to mirror your API's endpoints.
- Tool execution error
- A tool that ran, or checked its input, and failed. It comes back as a normal result marked as an error,
isError: truein MCP oris_error: truein Anthropic's API, saying what to fix. A call that can't be routed, like an unknown tool, gets a JSON-RPC protocol error instead.
Read next
- Writing effective tools for AI agentsAnthropic, 2025The schedule_event example, namespacing, readable identifiers, concise and detailed responses, and how to test tools on real tasks.
- Define toolsAnthropic docsThe fields of a tool definition, the rules for names, and a good and a poor description side by side.
- ToolsModel Context Protocol specification, 2026-07-28The exact shape of tools/list and tools/call, and the two kinds of error: protocol errors and results with isError.
- Function callingOpenAI API docsOpenAI's advice on naming and describing functions, including the intern test and strict mode.
Agents overviewSpotted a mistake? Tell us.