Define a Tool

Su Suriya Ravichandran Updated 10 Nov 2026
10 min read ·Lesson 82 of 200

What a Tool Definition Is

A tool definition tells Claude that a tool exists, what it does, when to use it, and what input it needs. It is a dictionary with these fields:

FieldRequiredPurpose
nameYesA unique identifier, such as search_courses. Must match ^[a-zA-Z0-9_-]{1,128}$: letters, digits, underscores, and hyphens, 1–128 characters.
descriptionStrongly recommendedA detailed, plain-text explanation of what the tool does, when to use it, and how it behaves
input_schemaYesA JSON Schema describing the input object (Lesson 83)
input_examplesOptionalExample inputs that show Claude how to call the tool correctly

Additional optional properties, such as strict for guaranteed schema conformance (Lesson 83) and cache_control for prompt caching, are covered in later lessons.

search_courses_tool = {
    "name": "search_courses",
    "description": "...",
    "input_schema": {
        "type": "object",
        "properties": {...},
        "required": [...],
    },
}

Claude never sees your Python code. It decides when and how to call a tool only from the definition. A good definition is therefore the single most important factor in how well tool use works.


Writing the Description

Anthropic's tool-use documentation calls detailed descriptions "by far the most important factor in tool performance", and recommends at least 3–4 sentences per tool, more for complex tools. A good description explains:

What to explainExample
What the tool does"Searches the GOCOURSE catalog for courses matching a topic."
When to use it"Use it when a student asks which courses exist for a subject, or wants a recommendation."
When not to use it"Do not use it for questions about a student's own enrollments; use get_enrollments for that."
What each input means"level filters by difficulty; omit it to search all levels."
What it returns, and its limits"Returns up to 5 courses with title, level, and duration. It does not return prices."

A Weak Definition

{
    "name": "search",
    "description": "Searches courses.",
    "input_schema": {
        "type": "object",
        "properties": {"q": {"type": "string"}},
        "required": ["q"],
    },
}

This leaves Claude guessing: search what, with what kind of query, returning what, and when?

A Strong Definition

{
    "name": "search_courses",
    "description": (
        "Searches the GOCOURSE course catalog by topic and returns matching courses. "
        "Use it when a student asks which courses are available for a subject, wants a "
        "recommendation, or asks whether a course on a topic exists. Do not use it for "
        "questions about the student's own enrollments or progress. Returns up to 5 courses, "
        "each with a title, level, and duration in weeks. It does not return prices or schedules."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "topic": {
                "type": "string",
                "description": "Subject to search for, such as 'Django', 'SQL', or 'data analysis'.",
            },
            "level": {
                "type": "string",
                "enum": ["beginner", "intermediate", "advanced"],
                "description": "Optional difficulty filter. Omit it to search all levels.",
            },
        },
        "required": ["topic"],
    },
}

Why the strong version works better:

  • The name describes the action and its object (search_courses), not just a verb.
  • The description covers what, when, when not, outputs, and limits, so Claude can choose correctly between this tool and others, and set expectations about what the result will contain.
  • Each parameter has its own description with examples, and level lists allowed values with enum.

Naming Tools

GuidelineExample
Use verb + objectget_weather, search_courses, create_ticket
Use snake_case, matching the allowed charactersget_order_status, not Get Order Status
Namespace tools from different servicesgithub_list_prs, slack_send_message
Keep names unique within a requestTwo tools named search would be ambiguous

Namespacing becomes important as the number of tools grows: prefixes make it clear which system each tool belongs to.


Designing Good Tools

The documentation recommends fewer, more capable tools over many tiny ones. Instead of create_ticket, close_ticket, and reopen_ticket, consider one manage_ticket tool with an action parameter. Fewer tools reduce ambiguity when Claude chooses between them.

Return Only What Claude Needs

A tool's result goes back into the conversation and costs input tokens. Return high-signal information: stable identifiers (IDs or slugs) and the fields Claude needs for its next step, not entire database rows or raw API responses.

Ask for an Explanation, Not for Reasoning

If a tool parameter asks Claude why it is calling the tool, ask for a short explanation or supporting evidence. According to the documentation, a parameter that asks for the model's thinking or step-by-step reasoning might lead to a refusal. Reasoning belongs in thinking blocks, not tool inputs.


Input Examples

For tools with complex or format-sensitive inputs, input_examples shows Claude concrete, valid inputs:

{
    "name": "schedule_session",
    "description": "...",
    "input_schema": {...},
    "input_examples": [
        {"course_id": "django-101", "date": "2026-11-12", "time": "19:00"},
        {"course_id": "sql-basics", "date": "2026-11-15", "time": "10:30", "duration_minutes": 90},
    ],
}

According to the documentation:

  • each example must be valid against the tool's input_schema, or the request fails with a 400 error;
  • examples add input tokens: roughly 20–50 tokens for simple examples, and 100–200 for complex nested ones;
  • they are supported for your own tools, but not for server tools such as web search.

Descriptions come first; add examples when inputs have tricky formats, such as dates, times, IDs, or nested objects.


Practical Example: A Tool Registry with a Definition Checker

In real applications, each tool has two parts: a definition for Claude, and an implementation in Python. A registry keeps them together. This example also includes a small checker that catches common definition problems before they reach the API.

"""tool_registry.py: keep tool definitions and implementations together, and check definitions."""

import re
from collections.abc import Callable
from dataclasses import dataclass

NAME_PATTERN = re.compile(r"^[a-zA-Z0-9_-]{1,128}$")


@dataclass
class Tool:
    definition: dict
    function: Callable[..., str]

    @property
    def name(self) -> str:
        return self.definition["name"]


def check_definition(definition: dict) -> list[str]:
    """Return a list of problems found in a tool definition."""
    problems = []
    name = definition.get("name", "")
    if not NAME_PATTERN.match(name):
        problems.append(f"invalid name {name!r}")

    description = definition.get("description", "")
    sentences = [s for s in re.split(r"[.!?]\s", description.strip()) if s]
    if len(sentences) < 3:
        problems.append(f"{name}: description has {len(sentences)} sentence(s); aim for at least 3")

    schema = definition.get("input_schema", {})
    if schema.get("type") != "object":
        problems.append(f"{name}: input_schema must have type 'object'")
    properties = schema.get("properties", {})
    for prop, spec in properties.items():
        if "description" not in spec:
            problems.append(f"{name}: property {prop!r} has no description")
    for required in schema.get("required", []):
        if required not in properties:
            problems.append(f"{name}: required property {required!r} is not defined")
    return problems


COURSES = [
    {"title": "Django for Beginners", "level": "beginner", "weeks": 6, "topic": "django"},
    {"title": "Advanced Django APIs", "level": "advanced", "weeks": 4, "topic": "django"},
    {"title": "SQL Basics", "level": "beginner", "weeks": 3, "topic": "sql"},
]


def search_courses(topic: str, level: str | None = None) -> str:
    matches = [c for c in COURSES
               if topic.lower() in c["topic"] and (level is None or c["level"] == level)]
    if not matches:
        return f"No courses found for topic {topic!r}."
    return "\n".join(f"- {c['title']} ({c['level']}, {c['weeks']} weeks)" for c in matches[:5])


SEARCH_COURSES = Tool(
    definition={
        "name": "search_courses",
        "description": (
            "Searches the GOCOURSE course catalog by topic and returns matching courses. "
            "Use it when a student asks which courses exist for a subject or wants a recommendation. "
            "Do not use it for questions about the student's own enrollments. "
            "Returns up to 5 courses with title, level, and duration in weeks; it does not return prices."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "topic": {"type": "string", "description": "Subject to search for, e.g. 'Django' or 'SQL'."},
                "level": {"type": "string", "enum": ["beginner", "intermediate", "advanced"],
                          "description": "Optional difficulty filter. Omit to search all levels."},
            },
            "required": ["topic"],
        },
    },
    function=search_courses,
)

REGISTRY = {tool.name: tool for tool in [SEARCH_COURSES]}

if __name__ == "__main__":
    for tool in REGISTRY.values():
        print(tool.name, "->", check_definition(tool.definition) or "OK")

    weak = {"name": "search", "description": "Searches courses.",
            "input_schema": {"type": "object", "properties": {"q": {"type": "string"}}, "required": ["q"]}}
    print("weak ->", check_definition(weak))

    print(REGISTRY["search_courses"].function(topic="Django", level="beginner"))

Output:

search_courses -> OK
weak -> ['search: description has 1 sentence(s); aim for at least 3', "search: property 'q' has no description"]
- Django for Beginners (beginner, 6 weeks)

How It Works

  1. Tool pairs a definition (what Claude sees) with a Python function (what runs). The name property reads the name from the definition, so it is defined in exactly one place.
  2. check_definition() catches common problems early:
    • a name that does not match the allowed pattern, which the API would reject;
    • a description with fewer than three sentences, counted roughly by splitting on sentence-ending punctuation followed by a space;
    • a schema that is not an object, properties without descriptions, and required entries that are not defined.
  3. search_courses() is the implementation. Its parameters match the schema's properties, and level is optional with a default of None, matching the schema, where only topic is required. It returns a short, high-signal text result.
  4. REGISTRY maps tool names to Tool objects. Later lessons use it to look up and run the tool Claude calls (Lesson 86), and to send all definitions with [tool.definition for tool in REGISTRY.values()] (Lesson 84).
  5. The weak definition fails two checks, showing why such checks are worth running in tests or at startup. (Python prints the second message in double quotes because the text itself contains single quotes.)

Common Mistakes

Short, Vague Descriptions

What happens: Claude calls the wrong tool, calls it at the wrong time, or never calls it.

Fix: explain what, when, when not, inputs, outputs, and limits, in at least 3–4 sentences.

Invalid Names

What happens: names with spaces, dots, or other characters are rejected with a 400 error.

Fix: use letters, digits, underscores, and hyphens only, up to 128 characters.

Parameters Without Descriptions

What happens: Claude guesses formats and meanings, such as date formats or units.

Fix: describe every parameter, with examples and allowed values.

Many Overlapping Tools

What happens: Claude chooses inconsistently between similar tools.

Fix: consolidate related operations, and namespace tools by service.

Returning Huge Results

What happens: large tool results consume context and make it harder for Claude to find what matters.

Fix: return only the fields Claude needs.


Best Practices

  • Invest most in descriptions: what, when, when not, inputs, outputs, limits.
  • Name tools with verb + object, in snake_case, namespaced by service when needed.
  • Describe every parameter, with examples and enum values where possible.
  • Consolidate related actions into fewer tools.
  • Keep definitions and implementations together in a registry.
  • Check definitions automatically in tests or at startup.
  • Add input_examples for format-sensitive or complex inputs.

Exercise

  1. Write a strong definition for a get_enrollments tool that returns a student's enrolled courses and progress. Make sure it explains how it differs from search_courses.
  2. Run check_definition() on your new definition and fix any problems it reports.
  3. Add an input_examples list with two valid examples to search_courses.
  4. Extend check_definition() to warn when an input_examples entry is missing a required property.
Show answers for steps 1 and 4

Step 1:

{
    "name": "get_enrollments",
    "description": (
        "Returns the courses the current student is enrolled in, with their progress. "
        "Use it when the student asks about their own courses, progress, or what to study next. "
        "Do not use it to find new courses; use search_courses for that. "
        "Returns each course title with the percentage completed and the date of the last activity."
    ),
    "input_schema": {
        "type": "object",
        "properties": {
            "include_completed": {
                "type": "boolean",
                "description": "Whether to include courses the student has already finished. Defaults to false.",
            },
        },
        "required": [],
    },
}

The student's identity is not a parameter: your application knows who the user is and should supply it when running the tool, rather than letting the model choose whose data to read (Lesson 176).

Step 4:

for number, example in enumerate(definition.get("input_examples", []), start=1):
    for required in schema.get("required", []):
        if required not in example:
            problems.append(f"{name}: input example {number} is missing {required!r}")

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Define a Tool and get answers drawn from it.

Signed-in readers only.