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.
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.
from anthropic import Anthropicclient = 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).
from anthropic import Anthropicclient = 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 description that states both what the tool does and when to call it, not just its function.
category restricted to a closed set of legal values with enum, instead of free text Claude could get wrong.
max_results left optional (absent from required) because the tool has a sane default without it.
Reading response.content for a tool_use block and inspecting block.name / block.input, the arguments Claude generated against your schema.
Claude receives your tools array alongside the conversation and treats each description as documentation it reads before deciding to act.
When a request matches a tool's stated purpose, Claude emits a tool_use content block containing the tool's name and an input object it generated to satisfy your input_schema.
Property-level description fields 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".
required tells 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.
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.
# 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"], }, }
Vague names like helper or w. Claude has nothing to disambiguate between tools when names don't describe their purpose. Fix: use specific, action-oriented names like get_weather or search_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 a description with an example to every property.
Free-text strings for fixed-choice fields. Without enum, a unit field can come back as "F", "Fahrenheit", or "fahrenheit" depending on phrasing. Fix: constrain fixed-choice parameters with enum.
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 in required that the tool genuinely cannot run without.
Using strict: true with unsupported constraints.minLength, maximum, or a recursive $ref in 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.
What three fields does every tool definition need?
name - a unique, descriptive identifier
description - what the tool does and when to call it
input_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.
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.
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 anthropic Python 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.
Reviewed by Chris St. John·Last updated Jul 16, 2026