1326 words Slides

17.7 Custom Tools in the SDK

Course: Claude Code - Enterprise Development

Section: Claude Agent SDK

Video Length: 3-4 minutes

Presenter: Daniel Treasure


Opening Hook

"Built-in tools (Read, Bash, Grep) are powerful, but every company has unique needs. Custom tools let you teach agents to use domain-specific functions. Want Claude to call your internal APIs, calculate metrics, or integrate with your SaaS tools? That's what custom tools do."


Key Talking Points

What to say:

  • "Custom tools are user-defined functions that Claude can call based on context."
  • "You define: name, description, input schema, implementation logic."
  • "Claude chooses whether to call based on your description—you don't force it."
  • "Input is validated against schema; output is structured and controlled."
  • "This is safer and more controlled than exposing raw APIs."

What to show on screen:

  • Tool definition with schema
  • Claude deciding to use tool
  • Tool execution with input validation
  • Structured output returned
  • Multiple custom tools in one agent

Demo Plan

[00:00 - 00:45] Custom Tool Anatomy 1. Show three parts: definition (name, description, schema), execution logic, output 2. Example: calculate_fibonacci(n) tool 3. Show schema: validates input type and range 4. Show: Claude calling tool when context matches

[00:45 - 01:45] Define & Register Custom Tools 1. Create custom tool function 2. Define input schema (type, properties, required, ranges) 3. Register with agent 4. Show: agent has access to this tool alongside built-ins

[01:45 - 02:45] Real Example: API Integration 1. Define tool: get_customer_data(customer_id: str) -> dict 2. Schema: validates customer_id is 6-digit number 3. Implementation: calls internal API, handles errors 4. Show Claude using tool when appropriate 5. Show output integrated into Claude's response

[02:45 - 03:45] Multiple Tools & Tool Selection 1. Define several tools: get_customer, create_invoice, send_notification 2. Show: Claude selecting appropriate tool for task 3. Demonstrate: Claude chains multiple tool calls 4. Show: error handling and fallback


Code Examples & Commands

Python Custom Tools:

from claude_code import Agent, Tool
import json

# Define a custom tool
def calculate_fibonacci(n: int) -> str:
    """Calculate the nth Fibonacci number."""
    if n < 0:
        return json.dumps({"error": "n must be non-negative"})

    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b

    return json.dumps({"n": n, "result": a})

# Register custom tool with agent
agent = Agent(
    model="claude-sonnet-4-5-20250929",
    custom_tools=[
        Tool(
            name="calculate_fibonacci",
            description="Calculate the nth Fibonacci number. Useful for sequence analysis.",
            input_schema={
                "type": "object",
                "properties": {
                    "n": {
                        "type": "integer",
                        "description": "Position in Fibonacci sequence (0-indexed)",
                        "minimum": 0,
                        "maximum": 1000
                    }
                },
                "required": ["n"]
            },
            execute=calculate_fibonacci
        )
    ]
)

# Claude will use the tool when appropriate
result = agent.run("What is the 15th Fibonacci number?")
print(result.output)

Real API Integration Tool:

from claude_code import Agent, Tool
import httpx
import json

# Define tool for customer API
def get_customer_data(customer_id: str) -> str:
    """
    Fetch customer data from internal API.
    Returns: name, email, account_status, lifetime_value
    """
    try:
        # Validate input
        if not customer_id.isdigit() or len(customer_id) != 6:
            return json.dumps({"error": "Invalid customer ID format"})

        # Call internal API
        response = httpx.get(
            f"https://api.internal/customers/{customer_id}",
            headers={"Authorization": "Bearer SERVICE_TOKEN"}
        )

        if response.status_code == 404:
            return json.dumps({"error": "Customer not found"})

        response.raise_for_status()
        data = response.json()

        # Return only relevant fields
        return json.dumps({
            "id": data["id"],
            "name": data["name"],
            "email": data["email"],
            "status": data["account_status"],
            "lifetime_value": data["ltv"]
        })

    except Exception as e:
        return json.dumps({"error": f"Failed to fetch data: {str(e)}"})

# Tool for creating invoices
def create_invoice(customer_id: str, amount: float, description: str) -> str:
    """Create a new invoice for a customer."""
    try:
        if not customer_id.isdigit() or len(customer_id) != 6:
            return json.dumps({"error": "Invalid customer ID"})

        if amount <= 0:
            return json.dumps({"error": "Amount must be positive"})

        # Create invoice via API
        response = httpx.post(
            "https://api.internal/invoices",
            json={
                "customer_id": customer_id,
                "amount": amount,
                "description": description
            },
            headers={"Authorization": "Bearer SERVICE_TOKEN"}
        )

        response.raise_for_status()
        data = response.json()

        return json.dumps({
            "invoice_id": data["id"],
            "customer_id": customer_id,
            "amount": amount,
            "status": "created"
        })

    except Exception as e:
        return json.dumps({"error": str(e)})

# Register all tools
agent = Agent(
    model="claude-sonnet-4-5-20250929",
    custom_tools=[
        Tool(
            name="get_customer_data",
            description="Fetch customer information by ID",
            input_schema={
                "type": "object",
                "properties": {
                    "customer_id": {
                        "type": "string",
                        "description": "6-digit customer ID",
                        "pattern": "^[0-9]{6}$"
                    }
                },
                "required": ["customer_id"]
            },
            execute=get_customer_data
        ),
        Tool(
            name="create_invoice",
            description="Create invoice for customer",
            input_schema={
                "type": "object",
                "properties": {
                    "customer_id": {
                        "type": "string",
                        "pattern": "^[0-9]{6}$"
                    },
                    "amount": {
                        "type": "number",
                        "minimum": 0.01
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 200
                    }
                },
                "required": ["customer_id", "amount", "description"]
            },
            execute=create_invoice
        )
    ]
)

# Claude uses tools intelligently
result = agent.run("Check customer 123456 and create a $500 invoice for consulting services")
print(result.output)

TypeScript Custom Tools:

import Anthropic from "@anthropic-ai/sdk";

interface Tool {
  name: string;
  description: string;
  input_schema: Record<string, any>;
}

const calculateFibonacci: Tool = {
  name: "calculate_fibonacci",
  description: "Calculate the nth Fibonacci number",
  input_schema: {
    type: "object",
    properties: {
      n: {
        type: "integer",
        description: "Position (0-indexed)",
        minimum: 0,
        maximum: 1000,
      },
    },
    required: ["n"],
  },
};

async function main(): Promise<void> {
  const client = new Anthropic();

  // Implement tool handler
  function executeFibonacci(n: number): number {
    let a = 0,
      b = 1;
    for (let i = 0; i < n; i++) {
      [a, b] = [b, a + b];
    }
    return a;
  }

  // Create message with tools (implementation depends on SDK)
  console.log("Tools defined and ready for Claude to use");
}

main().catch(console.error);

Gotchas & Tips

Gotcha 1: Tool Description Matters - If description is vague, Claude may not use the tool when appropriate - Example: "calc" vs. "Calculate the nth Fibonacci number for sequence analysis" - Good descriptions help Claude understand when to use the tool

Gotcha 2: Schema Validation - Schema is the contract between Claude and your function - Mismatched schemas (e.g., Claude sends string, function expects int) will fail - Solution: Validate inputs explicitly in your function

Gotcha 3: Error Messages - Return structured error messages (JSON) - Don't return stack traces; return user-friendly messages - Claude will try alternative approaches if tool fails

Gotcha 4: Tool Selection - Claude may not use your tool if task doesn't seem to need it - Solution: Make description compelling and specific - Example: "Get real-time stock price from our data service" better than "get_price"

Tip 1: Restrict Input Ranges - Use schema constraints: minimum, maximum, pattern, enum - Prevents Claude from sending unreasonable inputs - Example: n <= 1000 for Fibonacci (prevents infinite loops)

Tip 2: Return Structured Data - Always return JSON-serializable data - Include relevant fields, exclude internals - Claude can work with JSON results easily

Tip 3: Test Tool Descriptions - Iterate on descriptions: too vague or too narrow? - Test if Claude calls tool when expected - Adjust based on behavior

Tip 4: Tool Chaining - Design tools so Claude can chain them - Example: get_customer → create_invoice → send_notification - Order matters: prerequisites before dependents

Tip 5: Versioning Tools - If tool schema changes, support both old and new - Example: get_customer_v1 and get_customer_v2 - Easier than forcing Claude to adapt


Lead-out

"Custom tools are how you extend Claude's capabilities for your domain. Next: integrating MCPs in the SDK. You've built custom MCPs (16.5), now we'll show how to register them as tools in agent code."


Reference URLs

  • Claude Code SDK Tool Definition: [check official docs]
  • JSON Schema for Tool Input: https://json-schema.org/
  • Tool Design Best Practices: [would be in official docs]

Prep Reading

  • Review JSON schema specification basics (5 min)
  • Identify 2-3 tools you'd want to add to an agent (5 min)
  • Think about input validation and error cases (5 min)

Notes for Daniel

  • Description power: Emphasize that a good description is 80% of getting Claude to use the tool.
  • Real example: Use a realistic tool (customer API, billing, notifications) not toy examples.
  • Schema validation: Show how schema prevents bad inputs—this is security in action.
  • Tool selection: Mention that Claude may refuse to use a tool if it seems wrong for the task. This is conservative and safe.