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.