Agent Mentor Learn
Agent Tool Calling: Getting Agents to Actually Do Things · Lesson 4 of 6

Lesson 4: Designing Tool Interfaces: Name, Description, Parameters, Return Value

Learning goals:

  • Judge whether a tool description gives the model enough to pick the right tool and fill in the right parameters
  • Use JSON Schema's enum and required to close off room for parameter misuse, and know when to use strict mode to turn those constraints into hard guarantees
  • Design return values and error messages the model can act on to correct itself

Prerequisites: You've finished Lesson 3 and know the difference between the five tool types — read / write / execute / search / call | Prev Lesson 3 << | Next Lesson 5 >>

One Tool, Two Descriptions, Two Outcomes

Say your toolbox has a code search tool. Here's the first version of how it's registered:

The user asks: "Which directory is utils.ts in?"

All the model has to go on is those two lines — the name and the description. It has no way to tell whether search_files looks up files by name or searches for a string inside file contents; the description doesn't say. The model picks this tool and passes utils.ts as the query:

If this tool is actually a full-text search (looking for the string utils.ts inside each file's contents), and no file's contents literally contain those characters, the result comes back empty. The model gets an empty result and can't tell whether the file doesn't exist or its search approach was wrong, so it guesses. The common guess is to try a few synonyms and search again, and keep getting empty results.

Now swap in this description:

Same question, but this time the model reads "to find files by their name, use code_search_glob" and switches directly to code_search_glob, which is registered in the same toolbox, passing the right parameter:

Between the two calls, nothing changed: same model, same prompt, no change to any implementation code. The only difference is those few lines the model can read in the tool definition — a more precise name, a description that spells out the boundary and names the alternative tool, and parameters with their own descriptions. That's what this lesson is about: every field in a tool interface is the only thing the model has to reason with when it makes a decision.

The Description Is All the Model Sees When Choosing a Tool

Developers tend to write tools the way they write API comments: give the function a meaningful name, put the logic in the body, and let whoever needs it read the source. That habit breaks down on tool definitions — the model doesn't read your implementation code. All it can see is the name, description, and input_schema fields1; which tool to pick and what parameters to pass all come down to those few lines.

The official requirement for a description is direct: it should be "A detailed plaintext description of what the tool does, when it should be used, and how it behaves."1 Miss any one of those three and the model has to guess. Miss "what it does" and the model may skip the tool entirely and take a longer route to fake the result. Miss "when to use it" and, when the toolbox holds several similar tools (say both a grep and a glob), the model can't tell where the boundary is, and its odds of choosing wrong climb with the number of tools. Miss "how it behaves" and the model doesn't know what shape of result it'll get back, so it can't write correct follow-up logic to parse that result.

A good description should "Avoid ambiguity by clearly describing (and enforcing with strict data models) expected inputs and outputs."2 rather than reaching for elegant phrasing. The code_search_grep description in the last section works because it does two things: it makes clear it searches contents, not file names, and it names code_search_glob as the tool for finding files by name. Those two sentences let the model choose between similar tools without trial and error.

Names Should Signal Ownership Too: Namespacing

The description's job is to spell out what the tool does; the name's job is different — it should keep the tool from being confused with another one in a crowded toolbox. Once you have a lot of tools, especially after wiring in several external services, names like list_prs, send_message, create_issue are names anyone might pick, and the name alone doesn't tell you which service they belong to.

The official advice is to prefix tool names with the service: "When your tools span multiple services or resources, prefix names with the service (e.g., github_list_prs, slack_send_message). This makes tool selection unambiguous as your library grows, and is especially important when using tool search."3 When the model has to pick one tool out of dozens, a prefixed name narrows the field first, so it can rule out most of the options without opening each description to compare them line by line. The five tool types from Lesson 3 (read, write, execute, search, call) benefit the same way if each one is backed by a different service: fs_read_file and db_read_row are obviously not the same thing at a glance, whereas a bare read blurs them together.

input_schema: Nail Down the Shape of the Parameters

The description decides whether the model will pick this tool; the input_schema decides whether it can fill in the parameters correctly1. Here's an easy thing to miss: in JSON Schema, not every field is "constraining" a parameter — some fields only "describe" it.

Adding a description to a parameter only states intent; it won't reject any input that doesn't match what the sentence says4:

The model might pass "typescript", might pass "ts", might pass "TypeScript files" — the description is only a suggestion, and nothing stops it from passing something arbitrary. What actually stops arbitrary values is enum:

With enum in place, the legal values are listed explicitly, the model almost always fills one in, and the odds of an arbitrary value drop sharply. But note: this is strong guidance to the model, not a hard platform guarantee. In the default mode the API does not validate parameters against the schema for you, and the model will still occasionally produce inputs with wrong types or missing required fields5, so the checks on invalid values in your tool implementation still need to stay. Same goes for required: if a "write file" tool doesn't mark path as required, the model will occasionally leave it out, and the implementation then has to either error out or guess a default path, neither of which is good. Marking path as required drops the odds of that kind of misuse very low — and having "required" actually enforced by the platform depends on the strict mode in the next section.

Remember this distinction: type, enum, and required are real constraints in validation terms, while title and description are only notes for the model — no matter how detailed, they don't constitute a validation rule4. When you design an input_schema, ask first: can the "inputs that shouldn't appear" on this parameter be blocked outright with enum or required, instead of only writing "please pass xxx" in the description? How to upgrade these constraints from "written in the schema" to "enforced by the platform" is the next section.

Turning Soft Constraints into Hard Guarantees: additionalProperties: false and strict Mode

The last section kept stressing the difference between "constraining" and "describing," but there's another layer to keep straight: writing a constraint in the schema and the model's produced parameters actually passing validation are still two different things. In the default mode, the API won't intercept a call that doesn't match the schema for you — the model will occasionally write a number as the string "2", or just leave out a required field5.

There's a subtler direction too: the model may add fields out of nowhere. Say a ticket-creation tool's schema declares only two parameters, title and priority, but one call comes back with:

That skip_review key was never in the schema's properties; the model invented it on its own. Standard JSON Schema's default behavior is precisely to allow an object to carry undeclared extra keys — and if your tool implementation happens to pass the whole input through to a downstream system, and the downstream code really does have a branch checking that field name, one model hallucination silently bypasses a review step that was supposed to happen. Adding "additionalProperties": false at the top of the input_schema writes "only declared keys are allowed" into the validation rules too.

To make the platform actually enforce all of this, add the top-level field "strict": true to the tool definition. The way strict mode works is by constraining the model's sampling itself: "Setting strict: true on a tool definition guarantees Claude's tool inputs match your JSON Schema by constraining the model's token sampling to schema-valid outputs (a technique called grammar-constrained sampling)."5 Type, enum, required, additionalProperties all get honored, and invalid parameters simply never get generated. In the official docs, the strict-mode example schemas all carry additionalProperties: false as well — the two are meant to be used together. Only at this point does "invalid values are ruled out before the request is even sent" truly hold; for a tool without strict mode on, you can't drop a single line of parameter validation on the implementation side.

Return Values: Give the Model What It Can Use Next, Not a Log for Humans

When a tool finishes, its result is wrapped in a tool_result block and passed back to the model. The core fields are tool_use_id (which call this is for), content (the result), and is_error (whether it failed)6. Of these three, the one most often written badly is the content on failure.

Say a "write file" tool fails because the directory doesn't exist. Two ways to write it:

This throws the system log straight back. The model can tell it failed, but can't tell what to do next — the common result is that the model retries the exact same call, hits the same error a second time, and falls into a loop.

Same failure, but this version tells the model three things: what the failure was, which tool it can call to fix it, and what other path is available. The MCP spec is explicit: "Clients SHOULD provide tool execution errors to language models to enable self-correction."7 — on the condition that this message itself carries the clues needed to correct, not a stack trace only the person debugging the code can read.

Tool Count and Granularity: More Isn't Better

A bigger toolbox isn't a better one. Every tool definition (name, description, and input_schema combined) has to be packed into the context before the conversation begins, and once you have a lot of tools, that overhead grows fast. The Anthropic engineering team gave a figure: "That's 58 tools consuming approximately 55K tokens before the conversation even starts." — that much context burned before the conversation truly starts. They've also seen more extreme cases internally: "At Anthropic, we've seen tool definitions consume 134K tokens before optimization."8 The more crowded the context, the less room the model has left to reason about the actual task.

The second problem that comes with a high tool count has nothing to do with tokens: choosing gets harder. Pile up several tools with similar functions and the model has to spend an extra step just on "which one do I use," with the odds of getting it wrong climbing along with the tool count — "More tools don't always lead to better outcomes."2 That's also why the earlier sections kept stressing that a description has to spell out the boundary.

The reverse — granularity too coarse — doesn't work either. A "file operations" tool that crams read, write, delete, and edit into one input_schema and distinguishes behavior with an action parameter forces the model to first guess the right action value, then guess which parameters to fill — more error-prone than splitting into single-responsibility tools like fs_read_file and fs_write_file. The practical trade-off: first split tools along Lesson 3's five types, then, once the count grows, control the odds of a wrong pick with namespacing and precise descriptions — rather than stacking one do-everything tool to keep the count down.

Recap

  • The description is the only text the model sees when it picks a tool and fills in parameters; spelling out "what it does, when to use it, when not to" matters more than writing it elegantly
  • Adding a service prefix (namespacing) to the name helps the model rule out a big batch of irrelevant options first, once there are a lot of tools
  • In input_schema, type, enum, and required are real constraints in validation terms while title and description are only notes; enum plus required drives the odds of arbitrary values very low, and turning "invalid inputs simply never get generated" into a hard guarantee takes additionalProperties: false plus strict mode5
  • Return values — especially on failure — need to spell out "why it failed" and "what to do next" so the model can self-correct instead of retrying verbatim
  • More tools isn't better: the definitions eat context tokens, and the more similar the tools the easier it is for the model to pick wrong; granularity isn't "finer is better" either — split by function first, then control the odds of a wrong pick with clear naming and descriptions

>> Lesson 5: Permissions and Safety: The Boundaries of What an Agent Can Do

Footnotes

  1. Define tools — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/define-tools 2 3

  2. Writing effective tools for AI agents—using AI agents | Anthropic Engineering — https://www.anthropic.com/engineering/writing-tools-for-agents 2

  3. How to implement tool use - Claude Platform Docs — https://platform.claude.com/docs/en/agents-and-tools/tool-use/implement-tool-use

  4. Creating your first schema - JSON Schema — https://json-schema.org/learn/getting-started-step-by-step 2

  5. Strict tool use — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/strict-tool-use 2 3 4

  6. Handle tool calls — Claude API — https://platform.claude.com/docs/en/agents-and-tools/tool-use/handle-tool-calls

  7. Tools - Model Context Protocol — https://modelcontextprotocol.io/docs/concepts/tools

  8. Introducing advanced tool use on the Claude Developer Platform | Anthropic Engineering — https://www.anthropic.com/engineering/advanced-tool-use

Exercises

01

A project has a "write file" tool, currently defined like this:

Level 1: Rewrite a Vague Tool Definition

The toolbox also has an edit_file tool that does one thing: make local replacements inside an existing file. The model often calls write_file when it should call edit_file for a small change, overwriting the entire file.

Rewrite write_file's description and input_schema so that:

  1. The description makes clear this tool overwrites the whole file's contents, and points to edit_file for local changes
  2. The input_schema adds an enum-constrained parameter that distinguishes "create when the file doesn't exist" from "overwrite when the file already exists," so the model doesn't accidentally overwrite a file it shouldn't touch
  3. Check: if the model gets "change the port number in config.json to 8080," would it still reach for write_file?
Done criteria · checked locally
02

Below is a simplified but real round-trip record. A "run tests" tool was called 3 times in a row, with identical inputs each time:

Level 2: Diagnose a Call That Failed Because of a Bad Return Value
Call 1: { "name": "run_tests", "input": { "suite": "unit" } }Returns: { "content": "Error: connect ECONNREFUSED 127.0.0.1:5432", "is_error": true }
Call 2: { "name": "run_tests", "input": { "suite": "unit" } }Returns: { "content": "Error: connect ECONNREFUSED 127.0.0.1:5432", "is_error": true }
Call 3: { "name": "run_tests", "input": { "suite": "unit" } }Returns: { "content": "Error: connect ECONNREFUSED 127.0.0.1:5432", "is_error": true }

Answer these:

  1. Why does the model repeat the call 3 times with identical parameters instead of trying something different?
  2. What does the model need to do to resolve the real cause (the database connection was refused; nothing is listening on port 5432)? Assume the toolbox also has a start_service tool.
  3. Rewrite the content field into an error message that would get the model to switch to the right approach before the second call.
Done criteria · checked locally