Defining Tools¶
Level: Intermediate · Time: ~35 min · Exam relevance: CCAR-P 3.1, 3.3, 5.1
In plain English¶
A tool definition is a short contract between you and Claude. It answers three questions:
- What is this tool called? (
name) - What does it do, and when should it be used? (
description) - What inputs does it need? (
input_schema, written in JSON Schema)
Claude never sees your function's code. Everything it knows about the tool comes from this contract. A vague contract produces vague behaviour: the wrong tool, missing arguments, or no call at all. A precise contract is the cheapest reliability improvement you can make.
Think of writing a tool definition like writing a job card for a contractor who is brilliant but has never visited your building. "Fix the light" will not do. "Replace the failed LED panel in meeting room 3B; the spare is in cupboard C; do not touch the emergency lighting circuit" will.
Why it matters¶
- Tool selection accuracy depends mostly on descriptions. Anthropic's documentation calls detailed descriptions "by far the most important factor in tool performance".
- Argument quality depends on the schema: types, enums and per-field descriptions.
- Safety depends on what the schema allows. A free-text
queryfield that becomes SQL is a different risk profile from an enum of three report names. - Cost and latency depend on size: every tool definition is sent with every request and counts as input tokens. Large catalogues are where tool search (page 4) and prompt caching earn their keep.
How it works¶
The fields of a user-defined tool¶
| Field | Required | Notes (as of 2026-10-24) |
|---|---|---|
name |
Yes | Must match ^[a-zA-Z0-9_-]{1,128}$. Use verbs and namespaces: crm_search_contacts, not search |
description |
Yes in practice | Plain text: what it does, when to use it and when not, what it returns, caveats. Aim for at least three or four sentences |
input_schema |
Yes | A JSON Schema object ("type": "object") with properties and required |
input_examples |
No | Array of example input objects, each valid against input_schema |
strict |
No | true guarantees tool_use.input validates against the schema |
cache_control |
No | Prompt-caching breakpoint (see prompt caching) |
defer_loading |
No | Keep the definition out of context until tool search finds it |
eager_input_streaming |
No | Stream large tool inputs as they are generated; you then validate them yourself |
The full list of optional properties is in the tool reference.
Writing the description¶
A strong description covers:
- What the tool does, in one sentence.
- When to call it, written as trigger conditions ("Call this when the user asks…"). Being prescriptive about when helps recent models, which reach for tools more conservatively.
- When not to call it, and what it does not return.
- What each parameter means and how it changes the behaviour.
- What comes back: fields, units, limits, pagination.
Two smaller rules from Anthropic's documentation are worth remembering:
- Consolidate related operations. Prefer one
pull_requesttool with anactionparameter over separatecreate_pr,review_prandmerge_prtools when they share most of their inputs. - Ask for an explanation, not reasoning. If you add a parameter asking why Claude is making the call, request a short justification or supporting evidence. A parameter that asks for the model's step-by-step reasoning can trigger a
reasoning_extractionrefusal on current models.
Designing the schema¶
- Use
enumfor closed sets ("status": {"enum": ["open", "closed", "on_hold"]}). - Describe every property, including units and formats ("ISO 8601 date", "amount in pence").
- Put only genuinely required fields in
required; give others sensible defaults in your code. - Prefer flat objects. Deep nesting raises error rates; if you need it, add
input_examples. - Name parameters unambiguously:
customer_idrather thancustomer.
Strict tool use¶
Setting "strict": true on a tool definition makes the API constrain Claude's output so that tool_use.input always matches your schema: no missing required fields, no wrong types, no extra properties. It is a top-level field on the tool, not part of tool_choice.
Rules to remember (as of 2026-10-24):
- Every object in the schema needs
"additionalProperties": false, and the schema should listrequiredfields. - Strict mode supports a subset of JSON Schema. Basic types,
enum,const,anyOf,allOf,$ref/$defsand common string formats (date,date-time,email,uuidand others) work. Recursive schemas, numeric bounds (minimum,maximum) and string length limits (minLength,maxLength) are not enforced by the API; the Python and TypeScript SDKs strip unsupported constraints and check them client-side. - A new schema has a one-time compilation cost on first use; Anthropic caches compiled schemas for 24 hours.
- Strict mode guarantees shape, not meaning.
"order_id": "A-9999"can be perfectly valid JSON and still be an order the user does not own. You still validate business rules.
Tool use examples (input_examples)¶
For tools with nested objects, optional parameters or format-sensitive inputs, input_examples shows Claude concrete, valid calls:
- Each example must validate against
input_schema; an invalid example returns a 400. - They work on user-defined tools and Anthropic-schema client tools, but not on server tools or the computer use and browser use toolsets.
- They cost prompt tokens: roughly 20 to 50 tokens for a simple example and 100 to 200 for a complex nested one.
- Descriptions still matter most. Add examples when descriptions alone leave errors.
Controlling when Claude calls a tool¶
tool_choice takes four values:
| Value | Meaning | Status on current models (as of 2026-10-24) |
|---|---|---|
{"type": "auto"} |
Claude decides (default when tools are present) | Supported |
{"type": "none"} |
Claude may not call tools | Supported |
{"type": "any"} |
Claude must call some tool | 400 error on Claude Opus 5.5, Claude Sonnet 5.5, Claude Fable 5.1 and Claude Mythos 5.1 |
{"type": "tool", "name": "…"} |
Claude must call this tool | 400 error on the same models |
Older models (for example Claude Opus 5) still accept forced tool use, but the current default models do not. If you are migrating code that forced a call, replace it with one of these:
autoplus a clear instruction in the user or system prompt ("Use theget_order_statustool to answer"). Becauseautodoes not guarantee a call, check for atool_useblock and re-prompt if none came back.strict: trueon the tool, so that when Claude does call it the arguments are schema-valid.- Structured outputs (
output_config.formatorclient.messages.parse()) when the forced call only existed to get JSON back. That is the cleaner design for extraction; see structured output.
disable_parallel_tool_use: true still works with auto and limits Claude to at most one tool call per turn.
Example¶
A well-specified tool for a fictional logistics company, with strict mode and examples.
Cost: requires API credits (live mode).
import os
import anthropic
client = anthropic.Anthropic()
create_shipment_quote = {
"name": "shipping_create_quote",
"description": (
"Create a non-binding shipping price quote in the Acme Logistics rating engine. "
"Call this when a customer asks how much it will cost to send a parcel or pallet "
"between two UK postcodes. Returns quote_id, price in pence, service level and "
"estimated transit days. Quotes expire after 24 hours. This tool does not book a "
"shipment or take payment; never tell the customer their shipment is booked."
),
"strict": True,
"input_schema": {
"type": "object",
"properties": {
"origin_postcode": {"type": "string", "description": "UK postcode of collection, e.g. LS1 4AP."},
"destination_postcode": {"type": "string", "description": "UK postcode of delivery."},
"package_type": {"type": "string", "enum": ["parcel", "pallet"]},
"weight_kg": {"type": "number", "description": "Total weight in kilograms."},
"service_level": {
"type": "string",
"enum": ["economy", "standard", "next_day"],
"description": "Use standard if the customer has not said.",
},
},
"required": ["origin_postcode", "destination_postcode", "package_type", "weight_kg", "service_level"],
"additionalProperties": False,
},
"input_examples": [
{"origin_postcode": "LS1 4AP", "destination_postcode": "M1 1AE",
"package_type": "parcel", "weight_kg": 2.5, "service_level": "standard"},
{"origin_postcode": "B1 1BB", "destination_postcode": "G1 1XQ",
"package_type": "pallet", "weight_kg": 410, "service_level": "economy"},
],
}
response = client.messages.create(
model=os.environ.get("CLAUDE_MODEL", "claude-opus-5-5"),
max_tokens=16000,
tools=[create_shipment_quote],
# tool_choice defaults to auto; we steer with the prompt instead of forcing.
system="You are Acme Logistics' quoting assistant. Use shipping_create_quote for any price question.",
messages=[{"role": "user", "content": "How much to send a 3 kg parcel from Leeds LS1 4AP to Manchester M1 1AE?"}],
)
if response.stop_reason == "tool_use":
call = next(b for b in response.content if b.type == "tool_use")
print(call.name, call.input)
else:
print("No tool call; stop_reason =", response.stop_reason)
The same definition without strict, enum values and the "does not book" sentence is a common source of production incidents: assistants that confirm bookings that never happened.
Enterprise use case¶
Contoso Health (fictional) runs a clinic-scheduling assistant. Its first tool, query_schedule(sql: string), passed model-written SQL to a read replica. It worked in demos and failed review: the security team could not bound what data the model might read, and the SQL sometimes joined patient tables.
The redesign replaced it with three narrow tools: schedule_find_slots (clinic ID from an enum of the user's clinics, date range, appointment type from an enum), schedule_hold_slot (slot ID, 10-minute hold), and schedule_confirm_booking (hold ID, patient ID taken from the authenticated session, never from the model). All three use strict: true. Selection accuracy in the team's evaluation set rose, the data-protection officer signed off because no tool could return clinical notes, and audit logs became readable because every call had a named purpose.
Common mistakes¶
- Descriptions that only restate the name. "Gets the weather" tells Claude nothing about when to call it or what it returns.
- Forcing calls with
tool_choice: anyon Opus 5.5, Sonnet 5.5, Fable 5.1 and Mythos 5.1. It returns a 400. Useautoplus instructions,strict: trueor structured outputs. - Assuming strict mode validates business rules. It guarantees the schema, not authorisation or plausibility.
- Forgetting
additionalProperties: falseon nested objects when usingstrict. - Over-long parameter lists. Ten optional flags confuse the model; split or consolidate thoughtfully.
- Asking for "your reasoning" in a parameter. Ask for a short justification instead.
- Changing tool definitions mid-conversation casually. Tools render first in the prompt, so edits invalidate the prompt cache. Keep the tool list stable and deterministic in order.
Security considerations¶
- The schema is your first guardrail. Enums and narrow types reduce the space of possible inputs; free-text fields that reach interpreters (SQL, shell, templates) are high risk.
- Never put identity in the model's hands. User IDs, tenant IDs and roles should come from the authenticated session in your code, not from a tool parameter Claude fills.
- Descriptions are prompt content. If tool definitions come from a third party (for example an MCP server), a malicious description can steer the model. See MCP security.
- Strict mode reduces malformed input but not malicious input. Keep server-side validation.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
400 mentioning tool_choice any/tool not supported |
Forced tool use on Opus 5.5, Sonnet 5.5 or Fable 5.1 | Switch to auto and instruct; add strict: true |
400 on an input_examples entry |
Example does not validate against the schema | Fix the example or the schema |
| Strict tool rejected | Missing additionalProperties: false, or unsupported keyword |
Add it to every object; move numeric bounds into your validation |
| First request with a new strict schema is slow | Schema compilation | Expected once; compiled schemas are cached for 24 hours |
| Claude passes a name where an ID is expected | Ambiguous parameter name | Rename to customer_id, describe the format, add an example |
Check your understanding¶
- What are the three essential fields of a user-defined tool, and which one most influences tool selection?
- You migrate code that used
tool_choice: {"type": "tool", "name": "extract_invoice"}only to get JSON back. What is the cleanest replacement on Claude Opus 5.5? - Does
strict: trueguarantee that a refund amount is within policy limits? Explain. - When are
input_examplesworth their token cost?
Answers
name,descriptionandinput_schema. The description has the most influence on whether and when Claude calls the tool.- Structured outputs (
output_config.formatorclient.messages.parse()), which constrain the response to a JSON schema without needing a tool call. - No. Strict mode guarantees the input matches the schema's types and required fields, and the API does not enforce numeric bounds such as
maximum. Policy limits must be checked in your tool code. - For tools with nested objects, optional parameters or format-sensitive inputs where evaluation shows parameter errors that a clearer description has not fixed.
Further reading¶
- Define tools (Claude Docs)
- Strict tool use (Claude Docs)
- Structured outputs (Claude Docs)
- Writing effective tools for agents (Anthropic Engineering, 2025-09-11)