insideout Get Pro

03 · Designing tools a model can use

Free

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:

  1. Write a tool definition, and predict the call a model makes from it.
  2. Write a description that says what goes in, what comes out, and when to use another tool.
  3. Shape a tool around a task, so code does the searching and the model reads only what it needs.
  4. 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.

A tool definition, in Anthropic's format
{
  "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. sam@example.com"},
      "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 sam@example.com." With search_orders above and get_order(order_id) on offer, what call does the model make?

  1. Pick the toolsearch_orders

    The message gives an email, not an order ID, and the description says this tool searches by email.

  2. Fill the required input"email": "sam@example.com"

    email is listed under required, so every call must have it.

  3. Fill what the message asks"status": "shipped"

    The customer asked about shipped orders, and shipped is one of the three values the enum allows.

  4. 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": "sam@example.com", "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?

Both required inputs are there, the date is in the stated form, and 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.

  1. Say what goes in"by the customer's email address"

    An order number plainly doesn't fit this tool.

  2. 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.

  3. 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.

  4. Show formats with an example"e.g. sam@example.com"

    Put it on the email argument, 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?

Name the exact format and show one example. Guessing inside the tool turns 05/03 into either 5 March or 3 May without telling anyone. A vague warning in the system prompt is far from where the model is filling in the argument.

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)?

  1. Find the people300 × 15 = 4,500

    list_users returns everyone, to find three names.

  2. Read their calendars3 × 1,800 = 5,400

    One list_events call per person.

  3. Book it4,500 + 5,400 + 80 = 9,980

    create_event returns 80. Five calls, and every result stays in the history.

  4. 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.

API-shaped tools: 5 calls9,980 tokenslist_users 4,500list_events × 3: 5,400create_event: 80One tool shaped like the job: 1 call40 tokensschedule_event: "Booked Tue 10:00 to 10:30"Both bars drawn to the same scale.
Figure 1Tokens of tool results the model reads to book one meeting, drawn to scale. Each of them is resent on every later turn.

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?

Make the tool do the searching. Code filters 2,000 tickets exactly and for nothing; the model reading them costs tokens on every later turn and can still miss one. Paging trims each call but still leaves the scanning to the model.

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.

  1. The app calls the servertools/call, id 4

    With the tool's name and the model's arguments.

  2. 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.

  3. 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.

  4. 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.

The failed call and its result over MCP (request metadata left out)
{"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?

One line says what was wrong and what to send, so the next call uses JPY. The traceback costs tokens and explains nothing, "failed" leaves the model guessing, and an empty result invites it to make up a rate.

Where you'll meet it

In interviews

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.

On the job

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.

Check a tool call's arguments before running it, in Python
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": "sam@example.com", "status": "pending"}, SCHEMA))
print(check({"email": "sam@example.com", "limit": "20"}, SCHEMA))
print(check({"email": "sam@example.com", "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'.
# ok

Each 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?

The model picks tools by reading descriptions. If neither description says which to use for an ID, it guesses. Telling each tool where its job ends and the other's begins fixes most wrong-tool calls, and it costs nothing.

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?

The error message is an observation. "limit must be int, got '20'" is enough for the model to resend 20 without quotes. The run carries on and nobody gets paged. A raised exception would end the run or, worse, get swallowed.

3

A read_file tool returns whole files. On a 30,000-line log it returns everything. What's the better design?

A preview plus a way to ask for more lets the model decide what it needs. Dumping 30,000 lines can push the task out of the context window. A hard error blocks a reasonable request, and summarizing every file costs a model call and may drop the one line that mattered.

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

  1. 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.

  2. 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.

  3. 3Tools shaped like the job

    Shape tools to the job: one schedule_event call returning 40 tokens beats five API-shaped calls returning 9,980.

  4. 4Errors the model can act on

    Return failures as results the model can act on (isError: true plus what to send instead); over MCP, tools arrive by tools/list and run by tools/call.

Recap and going further

The lesson on one page

What the model sees
  • A name, a description and a JSON Schema
  • Never the function's code
  • Sent and billed on every request
Descriptions
  • What goes in and what comes out
  • When to use a different tool instead
  • Formats shown with one example
Shaped like the job
  • schedule_event, not three list calls
  • Search in code, not in the context
  • Readable names, prefixed by service
Tools a model can use well
Errors
  • Check arguments before running
  • An error result says what to send instead
  • An unknown tool is a protocol error
Over MCP
  • 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 enum stops 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's parameters and MCP's inputSchema.
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: 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.

Agents overviewSpotted a mistake? Tell us.