Python SDK for Claude Code enabling developers to build AI-powered applications and agents with support for custom tools, hooks, and bidirectional interactive conversations
Create custom tools as Python functions that Claude can invoke using the MCP (Model Context Protocol) system.
Custom tools allow you to:
from claude_agent_sdk import tool
@tool(
name="greet",
description="Greet a user by name",
input_schema={"name": str}
)
async def greet(args):
return {
"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]
}from claude_agent_sdk import create_sdk_mcp_server
server = create_sdk_mcp_server(
name="greeter",
version="1.0.0",
tools=[greet]
)from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
options = ClaudeAgentOptions(
mcp_servers={"greeter": server},
allowed_tools=["mcp__greeter__greet"] # Note the format!
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Greet Alice")
async for msg in client.receive_response():
print(msg)Important: MCP tools use the format mcp__<server_name>__<tool_name>
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add(args):
result = args["a"] + args["b"]
return {"content": [{"type": "text", "text": f"Result: {result}"}]}@tool("divide", "Divide two numbers", {"a": float, "b": float})
async def divide(args):
if args["b"] == 0:
return {
"content": [{"type": "text", "text": "Error: Division by zero"}],
"is_error": True
}
result = args["a"] / args["b"]
return {"content": [{"type": "text", "text": f"Result: {result}"}]}@tool(
"search",
"Search for items",
{
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer", "minimum": 1, "maximum": 100},
"filters": {
"type": "object",
"properties": {
"category": {"type": "string"},
"min_price": {"type": "number"}
}
}
},
"required": ["query"]
}
)
async def search(args):
query = args["query"]
limit = args.get("limit", 10)
filters = args.get("filters", {})
# Perform search...
results = f"Found items for '{query}' (limit: {limit})"
return {"content": [{"type": "text", "text": results}]}import anyio
from claude_agent_sdk import (
tool,
create_sdk_mcp_server,
ClaudeSDKClient,
ClaudeAgentOptions
)
# Define tools
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add(args):
return {"content": [{"type": "text", "text": f"{args['a'] + args['b']}"}]}
@tool("subtract", "Subtract two numbers", {"a": float, "b": float})
async def subtract(args):
return {"content": [{"type": "text", "text": f"{args['a'] - args['b']}"}]}
@tool("multiply", "Multiply two numbers", {"a": float, "b": float})
async def multiply(args):
return {"content": [{"type": "text", "text": f"{args['a'] * args['b']}"}]}
@tool("divide", "Divide two numbers", {"a": float, "b": float})
async def divide(args):
if args["b"] == 0:
return {
"content": [{"type": "text", "text": "Error: Division by zero"}],
"is_error": True
}
return {"content": [{"type": "text", "text": f"{args['a'] / args['b']}"}]}
async def main():
# Create server with all tools
calculator = create_sdk_mcp_server(
name="calculator",
version="1.0.0",
tools=[add, subtract, multiply, divide]
)
# Configure client
options = ClaudeAgentOptions(
mcp_servers={"calculator": calculator},
allowed_tools=[
"mcp__calculator__add",
"mcp__calculator__subtract",
"mcp__calculator__multiply",
"mcp__calculator__divide"
]
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Calculate (10 + 5) * 3 - 8")
async for msg in client.receive_response():
print(msg)
anyio.run(main)Create tools that access application state:
class DataStore:
def __init__(self):
self.items = []
store = DataStore()
@tool("add_item", "Add item to store", {"item": str})
async def add_item(args):
store.items.append(args["item"])
return {"content": [{"type": "text", "text": f"Added: {args['item']}"}]}
@tool("list_items", "List all items", {})
async def list_items(args):
items_text = ", ".join(store.items) if store.items else "No items"
return {"content": [{"type": "text", "text": f"Items: {items_text}"}]}
@tool("clear_items", "Clear all items", {})
async def clear_items(args):
count = len(store.items)
store.items.clear()
return {"content": [{"type": "text", "text": f"Cleared {count} items"}]}
server = create_sdk_mcp_server(
name="store",
tools=[add_item, list_items, clear_items]
)import httpx
@tool("get_weather", "Get weather for a city", {"city": str})
async def get_weather(args):
city = args["city"]
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.weather.example.com/current",
params={"city": city, "api_key": "YOUR_KEY"}
)
if response.status_code == 200:
data = response.json()
return {
"content": [{
"type": "text",
"text": f"Weather in {city}: {data['description']}, {data['temp']}°F"
}]
}
else:
return {
"content": [{"type": "text", "text": f"Error: Could not fetch weather for {city}"}],
"is_error": True
}import asyncpg
async def get_db_connection():
return await asyncpg.connect("postgresql://user:pass@localhost/db")
@tool("query_users", "Query users from database", {"name_filter": str})
async def query_users(args):
conn = await get_db_connection()
try:
rows = await conn.fetch(
"SELECT name, email FROM users WHERE name LIKE $1",
f"%{args['name_filter']}%"
)
users = [f"{row['name']} ({row['email']})" for row in rows]
result = "\n".join(users) if users else "No users found"
return {"content": [{"type": "text", "text": result}]}
finally:
await conn.close()Combine multiple servers:
# Calculator server
calc_server = create_sdk_mcp_server("calc", tools=[add, subtract])
# Weather server
weather_server = create_sdk_mcp_server("weather", tools=[get_weather])
# Database server
db_server = create_sdk_mcp_server("database", tools=[query_users])
options = ClaudeAgentOptions(
mcp_servers={
"calc": calc_server,
"weather": weather_server,
"database": db_server
},
allowed_tools=[
"mcp__calc__add",
"mcp__calc__subtract",
"mcp__weather__get_weather",
"mcp__database__query_users"
]
)from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeAgentOptions
# In-process SDK server
@tool("custom_tool", "My custom tool", {"input": str})
async def custom_tool(args):
return {"content": [{"type": "text", "text": f"Processed: {args['input']}"}]}
sdk_server = create_sdk_mcp_server("custom", tools=[custom_tool])
# External stdio server
external_config = {
"type": "stdio",
"command": "external-mcp-server",
"args": ["--config", "config.json"]
}
# Remote SSE server
remote_config = {
"type": "sse",
"url": "https://example.com/mcp",
"headers": {"Authorization": "Bearer token"}
}
options = ClaudeAgentOptions(
mcp_servers={
"custom": sdk_server,
"external": external_config,
"remote": remote_config
},
allowed_tools=[
"mcp__custom__custom_tool",
"mcp__external__some_tool",
"mcp__remote__remote_tool"
]
)# Good
@tool(
"send_email",
"Send an email to a recipient with subject and body",
{"to": str, "subject": str, "body": str}
)
# Bad
@tool("email", "Sends email", {"to": str, "s": str, "b": str})@tool("process_age", "Process user age", {"age": int})
async def process_age(args):
age = args["age"]
if age < 0 or age > 150:
return {
"content": [{"type": "text", "text": "Invalid age"}],
"is_error": True
}
# Process valid age...@tool("api_call", "Make API call", {"endpoint": str})
async def api_call(args):
try:
# Make API call...
return {"content": [{"type": "text", "text": "Success"}]}
except Exception as e:
return {
"content": [{"type": "text", "text": f"Error: {str(e)}"}],
"is_error": True
}# Good (async for I/O operations)
@tool("fetch_data", "Fetch data", {"url": str})
async def fetch_data(args):
async with httpx.AsyncClient() as client:
response = await client.get(args["url"])
return {"content": [{"type": "text", "text": response.text}]}
# Avoid (blocking I/O)
@tool("fetch_data_bad", "Fetch data", {"url": str})
async def fetch_data_bad(args):
import requests
response = requests.get(args["url"]) # Blocking!
return {"content": [{"type": "text", "text": response.text}]}Tools must return a dictionary with:
content: List of content blocks (required)is_error: Boolean indicating error (optional)# Success response
{
"content": [
{"type": "text", "text": "Result text"}
]
}
# Error response
{
"content": [
{"type": "text", "text": "Error message"}
],
"is_error": True
}
# Multiple content blocks
{
"content": [
{"type": "text", "text": "First part"},
{"type": "text", "text": "Second part"}
]
}Install with Tessl CLI
npx tessl i tessl/pypi-claude-agent-sdk