Defining Tool Schemas with name, description, and input_schema
A tool definition is the only information Claude has about what your function does and when to use it. Get the schema right and Claude calls it correctly; get it vague and Claude either skips it or fills it with guesses.
Summary
Every tool you give Claude is a JSON object with three required fields: name, description, and input_schema. Claude never executes your code directly - it reads this definition and decides whether to emit a tool_use block, and if so, what arguments to put in it.
The description field carries most of the weight. Claude relies on it to decide both whether to call the tool and how to fill in its parameters, so a one-line description that just restates the function name is not enough.
input_schema is standard JSON Schema, scoped to the properties this tool actually accepts. Each property should carry its own description, and any parameter with a small fixed set of legal values should use enum rather than free text.
For production tools, add "strict": true at the top level of the tool definition. It forces the schema to be exact - additionalProperties: false plus an explicit required list - and guarantees that whatever Claude returns in tool_use.input validates against your schema, no post-hoc checking needed.
Recipe
Quick-reference recipe card - copy-paste ready.
from anthropic import Anthropic
client = Anthropic()
tools = [
{
"name": "get_weather",
"description": (
"Get current weather for a location. Call this when the user "
"asks about current conditions, temperature, or forecast for "
"a specific place."
),
"input_schema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City and state, e.g., San Francisco, CA",
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit",
},
},
"required": ["location"],
},
}
]When to reach for this:
- Defining any new tool Claude will be able to call.
- A tool has optional parameters and you need Claude to know which ones actually matter.
- A parameter only accepts a small, known set of values (statuses, categories, units).
- You are debugging why Claude calls a tool with the wrong arguments or skips it entirely.
- You want Claude's output to be machine-safe without writing your own validation layer (use
strict: true).
Working Example
from anthropic import Anthropic
client = Anthropic()
search_database_tool = {
"name": "search_database",
"description": (
"Search the internal product catalog by keyword and optional "
"category filter. Call this when the user asks to find, look up, "
"or compare products - do not call it for general product "
"knowledge questions that don't need a live lookup."
),
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Free-text search keywords, e.g., 'wireless mouse'",
},
"category": {
"type": "string",
"enum": ["electronics", "home", "outdoor", "apparel"],
"description": "Restrict results to one product category",
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return",
},
},
"required": ["query"],
},
}
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
tools=[search_database_tool],
messages=[
{"role": "user", "content": "Find me a wireless mouse under home electronics"}
],
)
for block in response.content:
if block.type == "tool_use":
print(f"Tool called: {block.name}")
print(f"Arguments: {block.input}")What this demonstrates:
- A
descriptionthat states both what the tool does and when to call it, not just its function. categoryrestricted to a closed set of legal values withenum, instead of free text Claude could get wrong.max_resultsleft optional (absent fromrequired) because the tool has a sane default without it.- Reading
response.contentfor atool_useblock and inspectingblock.name/block.input, the arguments Claude generated against your schema.
Deep Dive
How It Works
- Claude receives your
toolsarray alongside the conversation and treats eachdescriptionas documentation it reads before deciding to act. - When a request matches a tool's stated purpose, Claude emits a
tool_usecontent block containing the tool'snameand aninputobject it generated to satisfy yourinput_schema. - Property-level
descriptionfields shape how Claude fills in arguments - a location field described as "City and state, e.g., San Francisco, CA" produces more consistent formatting than a bare"type": "string". requiredtells Claude which fields it must populate before calling the tool; everything else it fills in only when the conversation gives it a value.- Newer models such as Claude Sonnet 5 are more conservative about reaching for tools than earlier ones, so an explicit "call this when..." clause in the description measurably improves should-call accuracy - it is not just prose decoration.
Required Fields at a Glance
| Field | Type | Purpose |
|---|---|---|
name | string | Unique, descriptive identifier Claude echoes back in tool_use.name |
description | string | What the tool does and, critically, when to call it |
input_schema | object (JSON Schema) | Defines the shape of the arguments Claude must produce |
Strict Mode
Set "strict": true as a top-level field on the tool definition (alongside name, description, input_schema) to force tool_use.input to validate exactly against your schema, with no beta header required.
strict_tool = {
"name": "create_ticket",
"description": "Create a support ticket. Call this when the user reports a bug or requests help that needs to be tracked.",
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Short ticket title"},
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "urgent"],
"description": "Ticket priority",
},
},
"required": ["title", "priority"],
"additionalProperties": False,
},
}Strict mode requires additionalProperties: false and an explicit required array on the schema. It supports basic types (object, array, string, integer, number, boolean, null), enum, const, anyOf, allOf, $ref/$def, and string formats (date-time, date, email, uri, uuid, and similar). It does not support recursive schemas, numerical constraints (minimum, maximum, multipleOf), string length constraints (minLength, maxLength), complex array constraints, or additionalProperties set to anything other than false.
Python Notes
# input_schema is a plain dict - build it with helpers if it grows large,
# but keep it inline for small tools so name/description/schema stay
# reviewable together.
def weather_tool(units: list[str]) -> dict:
return {
"name": "get_weather",
"description": (
"Get current weather for a location. Call this when the user "
"asks about current conditions."
),
"input_schema": {
"type": "object",
"properties": {
"location": {"type": "string", "description": "City and state"},
"unit": {"type": "string", "enum": units, "description": "Temperature unit"},
},
"required": ["location"],
},
}Parameters & Return Values
| Parameter | Type | Description |
|---|---|---|
name | str | Unique tool identifier; Claude returns this in tool_use.name |
description | str | Explains what the tool does and, prescriptively, when to call it |
input_schema | dict | JSON Schema object describing accepted arguments |
input_schema.properties | dict | Per-parameter definitions, each with its own description |
input_schema.required | list[str] | Names of parameters Claude must always supply |
strict | bool (optional) | When True, guarantees tool_use.input exactly matches the schema |
Gotchas
- Vague names like
helperorw. Claude has nothing to disambiguate between tools when names don't describe their purpose. Fix: use specific, action-oriented names likeget_weatherorsearch_database. - A description that only restates the name.
"description": "Gets weather"tells Claude nothing about when to use it versus answer from general knowledge. Fix: state the purpose and add an explicit "Call this when..." clause. - Missing per-property descriptions. A bare
{"type": "string"}gives Claude no hint about expected format, so it guesses at formatting (e.g.,"NYC"vs"New York, NY"). Fix: add adescriptionwith an example to every property. - Free-text strings for fixed-choice fields. Without
enum, aunitfield can come back as"F","Fahrenheit", or"fahrenheit"depending on phrasing. Fix: constrain fixed-choice parameters withenum. - Marking everything as
required. Forcing Claude to invent values for parameters the conversation never mentioned produces low-quality, made-up arguments. Fix: only list parameters inrequiredthat the tool genuinely cannot run without. - Using
strict: truewith unsupported constraints.minLength,maximum, or a recursive$refin a strict-mode schema will fail validation, not silently loosen. Fix: move length/range checks into your own tool-execution code and keep the strict schema to the supported subset. - Too many overlapping tools. A large tool set with vague, similar descriptions causes Claude to pick the wrong tool or hesitate and call none. Fix: keep the tool set focused, and give each tool a description specific enough to be unambiguous next to its neighbors.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Loose schema, no strict | Prototyping, or your own code already validates tool_use.input before use | You need a hard guarantee that Claude's input matches your schema exactly |
strict: true | Production tools feeding directly into typed function calls or external APIs | Your schema needs numeric ranges, string length limits, or recursive structures |
One large multi-purpose tool with an action enum parameter | A handful of closely related operations (e.g., create/update/delete on one resource) | The operations take fundamentally different arguments - separate tools keep schemas cleaner |
| Several small, single-purpose tools | Each operation has a distinct name, description, and argument shape Claude should never confuse | You have dozens of near-duplicate tools - consider the tool search tool instead (tool search tool) |
FAQs
What three fields does every tool definition need?
name- a unique, descriptive identifierdescription- what the tool does and when to call itinput_schema- a JSON Schema object defining the accepted arguments
Why does the description matter so much?
Claude reads description the way a developer reads documentation before deciding whether and how to call a function. A vague description leads to missed calls (Claude doesn't realize the tool applies) or wrong arguments (Claude has no format guidance).
Should I describe when to call the tool, or just what it does?
Both. Stating a condition like "Call this when the user asks about current prices or recent events" measurably improves whether Claude reaches for the tool at the right moment, especially on newer models that call tools more conservatively by default.
Do I need a description on every property inside input_schema, or just the top-level tool description?
Both levels matter. The top-level description decides whether/when to call the tool; each property's description shapes how Claude fills in that specific argument (format, units, examples).
When should I use enum instead of a plain string type?
Whenever a parameter only has a small, known set of legal values - units, statuses, categories. enum prevents Claude from returning inconsistent variants like "F" vs "fahrenheit".
How do I decide what goes in required?
Only list parameters the tool cannot execute without. Everything else should be optional so Claude doesn't have to invent a value when the conversation didn't supply one.
What does strict: true actually guarantee?
That tool_use.input validates exactly against your input_schema - no missing required fields, no extra properties. It requires additionalProperties: false and an explicit required array, and needs no beta header.
Where does strict go in the tool definition?
tool = {
"name": "create_ticket",
"description": "...",
"strict": True,
"input_schema": {"type": "object", "properties": {}, "required": [], "additionalProperties": False},
}It is a top-level field alongside name/description/input_schema, not something set on tool_choice.
What JSON Schema features are off-limits in strict mode?
Recursive schemas, numeric constraints (minimum/maximum/multipleOf), string length constraints (minLength/maxLength), complex array constraints, and any additionalProperties value other than false.
Can I still use minLength or maximum if I need them?
Not under strict: true. Either drop strict mode and validate those constraints yourself after receiving tool_use.input, or enforce them in your tool-execution code rather than the schema.
What happens if I give two tools very similar descriptions?
Claude may pick the wrong one, or hesitate and call neither, because the descriptions don't give it enough signal to disambiguate. Keep each tool's description specific enough to be unambiguous next to its neighbors.
Should input_schema ever be anything other than type: object?
No - input_schema always describes an object whose properties are the tool's named arguments. Individual properties can be any JSON Schema type (string, integer, array, etc.), but the schema's root is always object.
How many tools is too many to define at once?
There's no hard limit, but a large tool set with vague or overlapping descriptions confuses tool selection well before you hit any technical ceiling. Keep the active tool set focused on what's relevant to the conversation.
Related
- Tool Use Basics - the minimal end-to-end tool_use/tool_result round trip this schema plugs into.
- How Claude Decides When to Call a Tool - how description wording affects should-call accuracy.
- Handling Tool Use Requests and Returning Tool Result Blocks - what to do with the
tool_use.inputthis schema produces. - Best Practices - broader guidance for designing reliable tool-using agents.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the official
anthropicPython SDK (latest 0.x release). Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.