Lesson 8.1
Defining Tools
The anatomy of a tool definition, how to write descriptions Claude will act on, what tool_choice controls, and the SDK helpers that generate schemas from code.
Tools are how Claude reaches outside the conversation: looking things up, calling your APIs, writing files. Claude never executes anything itself; it emits a structured request and your code runs it. That makes the tool definition the contract between model and application, and the exam tests every field of it.
Anatomy of a definition
A tool has a name, a description and an input_schema in JSON Schema. You pass a list of them in the tools field of a Messages request.
{
"name": "get_order_status",
"description": "Look up the current status of a customer order by its order ID. Use this whenever the user asks where an order is, whether it has shipped, or when it will arrive. Returns status, carrier and estimated delivery date.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order identifier, e.g. ORD-10422"},
"include_history": {"type": "boolean", "description": "Also return past status events"}
},
"required": ["order_id"]
}
}
namemust match^[a-zA-Z0-9_-]{1,64}$. Use verbs:search_products,create_ticket.descriptionis the most important field. Claude decides whether and when to call a tool almost entirely from it.input_schemamust be an object schema. Describe every property, useenumfor fixed choices, and mark only genuinely required fields inrequired.
Tool definitions are sent on every request and count as input tokens, and they render before system, so they sit at the front of the cacheable prefix.
Writing descriptions that work
Anthropic's guidance is to write descriptions as if for a new colleague: what the tool does, when to use it, when not to, what it returns, and any caveats. Three to four sentences is normal. Say explicitly which user intents should trigger it; a tool described only as "gets weather" is called less reliably than one that says "call this whenever the user asks about current or forecast weather for a place". Describe return values so Claude knows how to interpret results. Avoid overlapping tools with similar descriptions; if two tools could both apply, say in each one which to prefer.
Controlling when tools are used
tool_choice governs the decision:
tool_choice | Behaviour |
|---|---|
{"type": "auto"} | Default. Claude decides whether to call any tool |
{"type": "any"} | Claude must call at least one tool |
{"type": "tool", "name": "x"} | Claude must call tool x |
{"type": "none"} | Claude may not call tools, even though they are defined |
Add "disable_parallel_tool_use": true to any of these to limit Claude to one tool call per response. Note that the newest model generation rejects the forced forms (any and tool) with a 400; on those models use auto with an explicit instruction in the prompt, and use structured output when the forced call only existed to extract JSON. Check the tool use page for your target model.
Strict schemas
Adding "strict": true at the top level of a tool definition guarantees that the input Claude produces validates against the schema. The schema must then set additionalProperties: false and list all properties in required. Use it whenever you would otherwise write validation code for enums and types.
Defining tools in the SDKs
Hand-writing JSON Schema is error-prone. The SDKs derive it from code:
from anthropic import beta_tool
@beta_tool
def get_order_status(order_id: str, include_history: bool = False) -> str:
"""Look up the current status of a customer order by its order ID.
Use whenever the user asks where an order is or when it will arrive."""
return lookup(order_id, include_history)
# tools=[get_order_status] with client.beta.messages.tool_runner(...)
import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
const getOrderStatus = betaZodTool({
name: "get_order_status",
description: "Look up the current status of a customer order by its order ID...",
inputSchema: z.object({
order_id: z.string().describe("The order identifier, e.g. ORD-10422"),
include_history: z.boolean().optional(),
}),
run: async ({ order_id, include_history }) => lookup(order_id, include_history),
});
The docstring or description becomes the tool description and the parameter types become the schema. You can also call .to_dict() / spread the tool to get a plain definition for a manual loop.
Anthropic-defined and server tools
Some tools have fixed, versioned types instead of your own schema. Client-side ones such as the bash tool ({"type": "bash_20250124", "name": "bash"}) and text editor tool still run on your machine; you implement the handler but not the schema. Server-side tools such as web search, web fetch and code execution run on Anthropic's infrastructure: you declare them in tools and results come back as content blocks in the same response, with no loop on your side. Each has a type string with a date suffix, documented on its own page.
Design checklist
- One clear purpose per tool; split rather than add modes.
- Narrow inputs and outputs; return only what the model needs.
- Deterministic ordering of the tool list so caching works.
- Describe errors the tool can return so Claude can react sensibly.
- Keep the total tool count manageable; dozens of tools dilute selection accuracy, and tool search features exist for very large catalogues.
Exam focus
- A tool definition is
name,descriptionand a JSON Schemainput_schema; Claude emits requests and your code executes them. - The description drives whether and when Claude calls the tool; state triggers, non-triggers and return values.
tool_choicevalues areauto,any,toolandnone;disable_parallel_tool_uselimits to one call; forced forms are rejected by some newer models.strict: trueon the tool guarantees schema-validinputand requiresadditionalProperties: falseplus fullrequired.- SDK helpers (
@beta_tool,betaZodTool) generate schemas from typed code. - Anthropic-defined tools use versioned
typestrings; server tools run on Anthropic's side without a client loop.
Check yourself
5 questions
1.Which fields make up a custom tool definition in the Messages API tools array?
2.This tool definition is rejected with a 400: {"name": "get weather", "description": "weather", "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}}} What is the cause, and what else should be improved?
3.Claude rarely calls a lookup_order tool even when users clearly ask about their orders. The definition reads: {"name": "lookup_order", "description": "Looks up an order", ...}. What is the most effective fix?
4.A get_forecast tool has this schema and Claude frequently omits the unit: "input_schema": {"type": "object", "properties": {"location": {"type": "string"}, "unit": {"type": "string"}}} Which change fixes it?
5.How are tool definitions billed?
