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, implemented as in-process MCP (Model Context Protocol) servers. SDK MCP servers run directly in your application process, providing better performance and simpler deployment compared to external MCP servers.
MCP Tool Naming: When using tools from MCP servers with allowed_tools or disallowed_tools, reference them with the format mcp__<server_name>__<tool_name>. For example, if you have a server named "calculator" with a tool "add", reference it as "mcp__calculator__add".
Define custom tools with type safety using the @tool decorator.
def tool(
name: str,
description: str,
input_schema: type | dict[str, Any]
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]Parameters:
name: Unique identifier for the tool. Claude uses this to reference the tooldescription: Human-readable description helping Claude understand when to use the toolinput_schema: Schema defining tool input parameters. Can be:
{"text": str})Returns: Decorator function that wraps the tool implementation and returns SdkMcpTool instance
Usage:
Basic tool with simple schema:
from claude_agent_sdk import tool
@tool("greet", "Greet a user", {"name": str})
async def greet(args):
return {
"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]
}Tool with multiple parameters:
@tool("add", "Add two numbers", {"a": float, "b": float})
async def add_numbers(args):
result = args["a"] + args["b"]
return {
"content": [{"type": "text", "text": f"Result: {result}"}]
}Tool with error handling:
@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"Result: {args['a'] / args['b']}"}]
}Complex schema with JSON Schema:
@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 logic
results = f"Found items for '{query}' (limit: {limit}, filters: {filters})"
return {
"content": [{"type": "text", "text": results}]
}The SdkMcpTool class represents a tool definition returned by the @tool decorator.
@dataclass
class SdkMcpTool(Generic[T]):
name: str
description: str
input_schema: type[T] | dict[str, Any]
handler: Callable[[T], Awaitable[dict[str, Any]]]Fields:
name: Tool identifierdescription: Tool descriptioninput_schema: Input parameter schemahandler: Async function implementing the toolCreate an in-process MCP server that runs within your Python application.
def create_sdk_mcp_server(
name: str,
version: str = "1.0.0",
tools: list[SdkMcpTool[Any]] | None = None
) -> McpSdkServerConfigParameters:
name: Unique identifier for the serverversion: Server version string (default "1.0.0")tools: List of SdkMcpTool instances created with @tool decoratorReturns: McpSdkServerConfig configuration object for use with ClaudeAgentOptions.mcp_servers
Usage:
Simple calculator server:
from claude_agent_sdk import tool, create_sdk_mcp_server
@tool("add", "Add numbers", {"a": float, "b": float})
async def add(args):
return {"content": [{"type": "text", "text": f"Sum: {args['a'] + args['b']}"}]}
@tool("multiply", "Multiply numbers", {"a": float, "b": float})
async def multiply(args):
return {"content": [{"type": "text", "text": f"Product: {args['a'] * args['b']}"}]}
calculator = create_sdk_mcp_server(
name="calculator",
version="2.0.0",
tools=[add, multiply]
)Server with application state access:
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",
version="1.0.0",
tools=[add_item, list_items, clear_items]
)Use SDK MCP servers with the client:
import anyio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
tool,
create_sdk_mcp_server
)
@tool("weather", "Get weather for a city", {"city": str})
async def get_weather(args):
city = args["city"]
# In real implementation, fetch actual weather data
return {
"content": [{"type": "text", "text": f"Weather in {city}: Sunny, 72°F"}]
}
@tool("time", "Get current time for a city", {"city": str})
async def get_time(args):
city = args["city"]
# In real implementation, fetch actual time
return {
"content": [{"type": "text", "text": f"Time in {city}: 3:45 PM"}]
}
async def main():
# Create MCP server
server = create_sdk_mcp_server(
name="info",
version="1.0.0",
tools=[get_weather, get_time]
)
# Configure client with server
options = ClaudeAgentOptions(
mcp_servers={"info": server},
allowed_tools=["mcp__info__weather", "mcp__info__time"]
)
async with ClaudeSDKClient(options=options) as client:
await client.query("What's the weather in Paris?")
async for msg in client.receive_response():
print(msg)
await client.query("What time is it in Tokyo?")
async for msg in client.receive_response():
print(msg)
anyio.run(main)Configuration for in-process SDK MCP servers.
class McpSdkServerConfig(TypedDict):
type: Literal["sdk"]
name: str
instance: McpServerFields:
type: Always "sdk" for SDK serversname: Server nameinstance: MCP server instance (created by create_sdk_mcp_server)The SDK also supports external MCP servers running as separate processes:
Stdio server (subprocess):
class McpStdioServerConfig(TypedDict):
type: NotRequired[Literal["stdio"]]
command: str
args: NotRequired[list[str]]
env: NotRequired[dict[str, str]]SSE server (server-sent events):
class McpSSEServerConfig(TypedDict):
type: Literal["sse"]
url: str
headers: NotRequired[dict[str, str]]HTTP server:
class McpHttpServerConfig(TypedDict):
type: Literal["http"]
url: str
headers: NotRequired[dict[str, str]]Union type for all server configs:
McpServerConfig = (
McpStdioServerConfig |
McpSSEServerConfig |
McpHttpServerConfig |
McpSdkServerConfig
)Use both SDK and external MCP servers together:
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
tool,
create_sdk_mcp_server
)
# In-process SDK server
@tool("local_tool", "A local tool", {"input": str})
async def local_tool(args):
return {"content": [{"type": "text", "text": f"Processed: {args['input']}"}]}
sdk_server = create_sdk_mcp_server("local", tools=[local_tool])
# Configure with both SDK and external servers
options = ClaudeAgentOptions(
mcp_servers={
"local": sdk_server, # In-process SDK server
"external": { # External subprocess server
"type": "stdio",
"command": "external-mcp-server",
"args": ["--config", "config.json"]
},
"remote": { # Remote SSE server
"type": "sse",
"url": "https://example.com/mcp",
"headers": {"Authorization": "Bearer token"}
}
},
allowed_tools=[
"mcp__local__local_tool",
"mcp__external__some_tool",
"mcp__remote__remote_tool"
]
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Use the tools")
async for msg in client.receive_response():
print(msg)No subprocess management: Runs in the same process as your application, eliminating subprocess overhead and complexity
Better performance: No IPC (inter-process communication) overhead for tool calls, resulting in faster execution
Simpler deployment: Single Python process instead of managing multiple server processes
Easier debugging: All code runs in the same process, making debugging straightforward with standard Python debuggers
Type safety: Direct Python function calls with type hints and IDE support
State access: Tools have direct access to your application's variables and state without serialization
Before (external MCP server):
options = ClaudeAgentOptions(
mcp_servers={
"calculator": {
"type": "stdio",
"command": "python",
"args": ["-m", "calculator_server"]
}
}
)After (SDK MCP server):
from my_tools import add, subtract # Your tool functions
calculator = create_sdk_mcp_server(
name="calculator",
tools=[add, subtract]
)
options = ClaudeAgentOptions(
mcp_servers={"calculator": calculator}
)import anyio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
tool,
create_sdk_mcp_server,
AssistantMessage,
TextBlock
)
# Define tools
@tool("calculate_total", "Calculate total with tax", {
"amount": float,
"tax_rate": float
})
async def calculate_total(args):
amount = args["amount"]
tax_rate = args["tax_rate"]
tax = amount * tax_rate
total = amount + tax
return {
"content": [{
"type": "text",
"text": f"Amount: ${amount:.2f}, Tax: ${tax:.2f}, Total: ${total:.2f}"
}]
}
@tool("format_currency", "Format number as currency", {"amount": float})
async def format_currency(args):
amount = args["amount"]
return {
"content": [{"type": "text", "text": f"${amount:,.2f}"}]
}
async def main():
# Create server
server = create_sdk_mcp_server(
name="finance",
version="1.0.0",
tools=[calculate_total, format_currency]
)
# Configure client
options = ClaudeAgentOptions(
mcp_servers={"finance": server},
allowed_tools=["mcp__finance__calculate_total", "mcp__finance__format_currency"]
)
async with ClaudeSDKClient(options=options) as client:
await client.query(
"Calculate the total for a $100 purchase with 8.5% tax, "
"then format the result as currency"
)
async for msg in client.receive_response():
if isinstance(msg, AssistantMessage):
for block in msg.content:
if isinstance(block, TextBlock):
print(block.text)
anyio.run(main)Install with Tessl CLI
npx tessl i tessl/pypi-claude-agent-sdk