Python SDK for Claude Code enabling developers to build AI-powered applications and agents with support for custom tools, hooks, and bidirectional interactive conversations
Comprehensive configuration through the ClaudeAgentOptions dataclass, controlling tools, permissions, models, execution environment, MCP servers, hooks, and advanced features.
Main configuration dataclass with 32 fields for fine-grained control.
@dataclass
class ClaudeAgentOptions:
"""Configuration options for Claude agent interactions.
All fields are optional with sensible defaults. Configure only what you need.
"""
# Tool Configuration
tools: list[str] | ToolsPreset | None = None
allowed_tools: list[str] = field(default_factory=list)
disallowed_tools: list[str] = field(default_factory=list)
# Permission Configuration
permission_mode: PermissionMode | None = None
can_use_tool: CanUseTool | None = None
permission_prompt_tool_name: str | None = None
# Model Configuration
model: str | None = None
fallback_model: str | None = None
betas: list[SdkBeta] = field(default_factory=list)
max_thinking_tokens: int | None = None
output_format: dict[str, Any] | None = None
# Session Configuration
system_prompt: str | SystemPromptPreset | None = None
max_turns: int | None = None
max_budget_usd: float | None = None
continue_conversation: bool = False
resume: str | None = None
fork_session: bool = False
user: str | None = None
# Execution Environment
cwd: str | Path | None = None
cli_path: str | Path | None = None
env: dict[str, str] = field(default_factory=dict)
add_dirs: list[str | Path] = field(default_factory=list)
settings: str | None = None
setting_sources: list[SettingSource] | None = None
# Integration
mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
hooks: dict[HookEvent, list[HookMatcher]] | None = None
agents: dict[str, AgentDefinition] | None = None
plugins: list[SdkPluginConfig] = field(default_factory=list)
# Advanced
sandbox: SandboxSettings | None = None
enable_file_checkpointing: bool = False
include_partial_messages: bool = False
max_buffer_size: int | None = None
stderr: Callable[[str], None] | None = None
extra_args: dict[str, str | None] = field(default_factory=dict)Type: list[str] | ToolsPreset | None
Default: None
Tool preset configuration. Can be a list of tool names or a preset object.
class ToolsPreset(TypedDict):
"""Tool preset configuration."""
type: Literal["preset"]
preset: Literal["claude_code"]Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Using preset
options = ClaudeAgentOptions(
tools={"type": "preset", "preset": "claude_code"}
)
# Using explicit list
options = ClaudeAgentOptions(
tools=["Read", "Write", "Bash", "Edit"]
)Type: list[str]
Default: [] (empty list, which means no tools allowed by default when permission_mode is not bypassPermissions)
List of tools explicitly allowed for Claude to use.
Common Built-in Tools:
"Read" - Read files from the filesystem"Write" - Write new files to the filesystem"Edit" - Edit existing files"Bash" - Execute bash commands"Grep" - Search for patterns in files"Glob" - Find files matching patterns"Task" - Launch specialized sub-agents"WebFetch" - Fetch content from URLs"WebSearch" - Search the web"AskUserQuestion" - Ask the user for input"TodoWrite" - Manage task lists"MultiEdit" - Edit multiple files at onceMCP Tools: Tools from MCP servers are accessed with the format mcp__<server_name>__<tool_name>. See Custom Tools and MCP Servers for details.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Allow specific built-in tools
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash", "Edit", "Grep", "Glob"]
)
# Include MCP tools (use mcp__<server>__<tool> format)
options = ClaudeAgentOptions(
allowed_tools=[
"Read",
"Write",
"mcp__calculator__add", # Tool "add" from server "calculator"
"mcp__calculator__multiply", # Tool "multiply" from server "calculator"
"mcp__database__query" # Tool "query" from server "database"
]
)Type: list[str]
Default: [] (empty list)
List of tools explicitly disallowed. Takes precedence over allowed_tools.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Allow many tools but explicitly block Bash
options = ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Bash", "Edit"],
disallowed_tools=["Bash"] # Bash is blocked despite being in allowed_tools
)Type: PermissionMode | None
Default: None (uses CLI default behavior)
Permission behavior mode controlling how tools are authorized.
PermissionMode = Literal["default", "acceptEdits", "plan", "bypassPermissions"]Options:
"default": CLI prompts for dangerous tools (interactive, not suitable for automation)"acceptEdits": Auto-accept file edits (recommended for automation with file operations)"plan": Plan mode for design/planning workflows"bypassPermissions": Allow all tools without prompting (use with extreme caution!)Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Auto-accept file edits (recommended for automation)
options = ClaudeAgentOptions(
permission_mode="acceptEdits",
allowed_tools=["Read", "Write", "Edit"]
)
# Bypass all permissions (dangerous! only in controlled environments)
options = ClaudeAgentOptions(
permission_mode="bypassPermissions"
)Type: CanUseTool | None
Default: None
Custom permission callback for fine-grained tool authorization. Requires streaming mode (ClaudeSDKClient or AsyncIterable prompt).
CanUseTool = Callable[
[str, dict[str, Any], ToolPermissionContext],
Awaitable[PermissionResult]
]Callback Signature:
PermissionResult (either PermissionResultAllow or PermissionResultDeny)Usage:
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
ToolPermissionContext,
PermissionResult,
PermissionResultAllow,
PermissionResultDeny
)
async def permission_callback(
tool_name: str,
tool_input: dict[str, Any],
context: ToolPermissionContext
) -> PermissionResult:
"""Custom permission logic."""
# Allow Read operations
if tool_name == "Read":
return PermissionResultAllow(behavior="allow")
# Check Bash commands for dangerous operations
if tool_name == "Bash":
command = tool_input.get("command", "")
# Block destructive commands
if any(pattern in command for pattern in ["rm -rf", "format", "dd if="]):
return PermissionResultDeny(
behavior="deny",
message=f"Dangerous command blocked: {command}",
interrupt=True # Stop the conversation
)
# Allow safe commands
return PermissionResultAllow(behavior="allow")
# Block Write to sensitive files
if tool_name in ["Write", "Edit"]:
file_path = tool_input.get("file_path", "")
if any(sensitive in file_path for sensitive in [".env", "credentials", "secrets"]):
return PermissionResultDeny(
behavior="deny",
message=f"Cannot modify sensitive file: {file_path}",
interrupt=False
)
# Default: allow
return PermissionResultAllow(behavior="allow")
options = ClaudeAgentOptions(
can_use_tool=permission_callback,
allowed_tools=["Read", "Write", "Bash"]
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Analyze and fix the codebase")
async for msg in client.receive_response():
print(msg)See Permission System for complete details.
Type: str | None
Default: None
Tool name for permission prompts. Set to "stdio" for control protocol. Cannot be used with can_use_tool.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
permission_prompt_tool_name="stdio"
)Type: str | None
Default: None (uses CLI default, typically "sonnet")
AI model to use for the conversation.
Valid Model Names:
"sonnet", "opus", "haiku""claude-sonnet-4-5", "claude-opus-4-5", "claude-haiku-4", or any valid Anthropic model IDModel Comparison:
"haiku": Fastest, cheapest, suitable for simple tasks"sonnet": Balanced performance and cost (default)"opus": Most capable, slowest, most expensiveUsage:
from claude_agent_sdk import ClaudeAgentOptions
# Using short name
options = ClaudeAgentOptions(
model="sonnet" # or "opus", "haiku"
)
# Using full model ID
options = ClaudeAgentOptions(
model="claude-sonnet-4-5"
)Type: str | None
Default: None
Fallback model if primary model is unavailable.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
model="opus",
fallback_model="sonnet" # Use sonnet if opus unavailable
)Type: list[SdkBeta]
Default: [] (empty list)
Beta feature flags to enable experimental features.
SdkBeta = Literal["context-1m-2025-08-07"]Available Beta Features:
"context-1m-2025-08-07" - Enables extended context window support (1 million tokens)Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
betas=["context-1m-2025-08-07"],
model="opus" # Extended context typically used with more capable models
)Type: int | None
Default: None
Maximum tokens for extended thinking blocks. Requires models that support extended thinking.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
max_thinking_tokens=10000,
model="opus" # Extended thinking typically available on more capable models
)Type: dict[str, Any] | None
Default: None
Structured output format matching Messages API structure. Use to request JSON outputs conforming to a specific schema.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Define JSON Schema for structured output
schema = {
"type": "json_schema",
"json_schema": {
"name": "code_analysis",
"strict": True,
"schema": {
"type": "object",
"properties": {
"files_analyzed": {"type": "integer"},
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"severity": {"type": "string", "enum": ["critical", "warning", "info"]},
"file": {"type": "string"},
"line": {"type": "integer"},
"description": {"type": "string"}
},
"required": ["severity", "file", "description"],
"additionalProperties": False
}
}
},
"required": ["files_analyzed", "issues"],
"additionalProperties": False
}
}
}
options = ClaudeAgentOptions(
output_format=schema,
allowed_tools=["Read", "Grep"]
)The structured output will be available in ResultMessage.structured_output.
Type: str | SystemPromptPreset | None
Default: None (uses CLI default system prompt)
System prompt for the conversation. Can be a string or preset configuration.
class SystemPromptPreset(TypedDict):
"""System prompt preset configuration."""
type: Literal["preset"]
preset: Literal["claude_code"]
append: NotRequired[str]Usage:
from claude_agent_sdk import ClaudeAgentOptions
# String prompt
options = ClaudeAgentOptions(
system_prompt="You are a helpful Python expert specializing in web development"
)
# Preset with append
options = ClaudeAgentOptions(
system_prompt={
"type": "preset",
"preset": "claude_code",
"append": "Additional instructions: Focus on security and performance"
}
)Type: int | None
Default: None (unlimited)
Maximum conversation turns before stopping. Useful for cost control and preventing infinite loops.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
max_turns=5 # Stop after 5 turns
)Type: float | None
Default: None (unlimited)
Cost budget limit in USD. Conversation stops if cost exceeds this limit.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
max_budget_usd=1.0 # Stop if cost exceeds $1.00
)Type: bool
Default: False
Continue existing conversation session without forking.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
continue_conversation=True
)Type: str | None
Default: None
Resume from session ID. Get session ID from ResultMessage.session_id.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Resume previous session
options = ClaudeAgentOptions(
resume="session-abc-123" # Session ID from previous ResultMessage
)Type: bool
Default: False
Fork resumed session to new ID instead of continuing. Use with resume to create a branch from a previous session.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
# Fork from existing session (creates new session ID)
options = ClaudeAgentOptions(
resume="session-abc-123",
fork_session=True # Creates new session from this point
)Type: str | None
Default: None
User identifier for the session. Useful for tracking or multi-user systems.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
user="user@example.com"
)Type: str | Path | None
Default: None (uses current working directory)
Working directory for the session. All file operations are relative to this directory.
Usage:
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions
# String path
options = ClaudeAgentOptions(
cwd="/path/to/project"
)
# Path object
options = ClaudeAgentOptions(
cwd=Path("/path/to/project")
)Type: str | Path | None
Default: None (uses bundled Claude Code CLI)
Path to Claude Code CLI executable. If None, uses bundled CLI included with SDK.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
cli_path="/usr/local/bin/claude-code" # Use custom CLI installation
)Type: dict[str, str]
Default: {} (empty dict)
Environment variables for the session. These are passed to the CLI and available in tool execution.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
env={
"DEBUG": "true",
"API_KEY": "secret",
"ENVIRONMENT": "production"
}
)Type: list[str | Path]
Default: [] (empty list)
Additional directories to include in context for file operations.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
add_dirs=["/path/to/lib", "/path/to/config", "/path/to/docs"]
)Type: str | None
Default: None
Settings profile name to load. Profiles are defined in Claude Code configuration files.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
settings="production" # Load "production" settings profile
)Type: list[SettingSource] | None
Default: None (loads all sources)
Which setting sources to load. Controls configuration hierarchy.
SettingSource = Literal["user", "project", "local"]Sources:
"user": User-level settings (e.g., ~/.config/claude)"project": Project-level settings (e.g., .claude/ in project root)"local": Local workspace settingsUsage:
from claude_agent_sdk import ClaudeAgentOptions
# Only project settings (reproducible builds for CI/CD)
options = ClaudeAgentOptions(
setting_sources=["project"] # Exclude user and local settings
)
# User and project, but not local
options = ClaudeAgentOptions(
setting_sources=["user", "project"]
)Type: dict[str, McpServerConfig] | str | Path
Default: {} (empty dict, no MCP servers)
MCP server configurations. Can be dictionary of server configs or path to config file.
Usage:
from claude_agent_sdk import ClaudeAgentOptions, tool, create_sdk_mcp_server
# In-process SDK server
@tool("add", "Add numbers", {"a": float, "b": float})
async def add(args):
return {"content": [{"type": "text", "text": f"{args['a'] + args['b']}"}]}
sdk_server = create_sdk_mcp_server("calc", tools=[add])
# Mixed configuration
options = ClaudeAgentOptions(
mcp_servers={
"calculator": sdk_server, # In-process SDK server
"external": { # External stdio server
"type": "stdio",
"command": "mcp-server",
"args": ["--config", "config.json"]
},
"remote": { # Remote SSE server
"type": "sse",
"url": "https://example.com/mcp",
"headers": {"Authorization": "Bearer token"}
}
},
allowed_tools=[
"mcp__calculator__add", # SDK server tool
"mcp__external__some_tool", # External server tool
"mcp__remote__remote_tool" # Remote server tool
]
)
# Load from file
options = ClaudeAgentOptions(
mcp_servers="/path/to/mcp-config.json"
)See Custom Tools and MCP Servers for detailed information.
Type: dict[HookEvent, list[HookMatcher]] | None
Default: None (no hooks)
Hook configurations for deterministic processing at specific points in the agent loop.
Usage:
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
async def pre_tool_hook(input_data, tool_use_id, context):
"""Hook executed before tool use."""
if input_data["tool_name"] == "Bash":
print(f"About to execute: {input_data['tool_input']}")
return {"continue_": True}
options = ClaudeAgentOptions(
hooks={
"PreToolUse": [
HookMatcher(
matcher="Bash", # Only for Bash tool
hooks=[pre_tool_hook],
timeout=30.0
)
]
}
)See Hooks for complete details.
Type: dict[str, AgentDefinition] | None
Default: None (no custom agents)
Custom agent definitions for specialized sub-agents.
@dataclass
class AgentDefinition:
"""Custom agent definition."""
description: str # Human-readable description
prompt: str # System prompt for agent
tools: list[str] | None = None # Tools available to agent
model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None # Model to useUsage:
from claude_agent_sdk import ClaudeAgentOptions, AgentDefinition
options = ClaudeAgentOptions(
agents={
"reviewer": AgentDefinition(
description="Code review specialist",
prompt="You are an expert code reviewer",
tools=["Read", "Grep"],
model="sonnet"
),
"implementer": AgentDefinition(
description="Implementation specialist",
prompt="You implement code changes",
tools=["Read", "Write", "Edit"],
model="opus"
)
}
)See Advanced Features for details.
Type: list[SdkPluginConfig]
Default: [] (no plugins)
Plugin configurations to extend SDK functionality.
class SdkPluginConfig(TypedDict):
"""Plugin configuration."""
type: Literal["local"] # Plugin type
path: str # Path to plugin fileUsage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
plugins=[
{"type": "local", "path": "/path/to/custom_logger.py"},
{"type": "local", "path": "/path/to/metrics_collector.py"}
]
)Type: SandboxSettings | None
Default: None (no sandboxing)
Bash command sandboxing configuration (macOS and Linux only).
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
sandbox={
"enabled": True,
"autoAllowBashIfSandboxed": True,
"excludedCommands": ["docker", "git"],
"network": {
"allowUnixSockets": ["/var/run/docker.sock"],
"allowLocalBinding": True
}
}
)See Sandbox Configuration for complete details.
Type: bool
Default: False
Enable file change tracking for rewinding. Required for ClaudeSDKClient.rewind_files().
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
enable_file_checkpointing=True # Enable tracking
)Type: bool
Default: False
Include partial message updates during streaming. When enabled, yields StreamEvent messages with incremental updates.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
include_partial_messages=True # Get partial updates
)See Message and Content Types for StreamEvent details.
Type: int | None
Default: None (unlimited)
Maximum bytes when buffering CLI stdout.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
max_buffer_size=1048576 # 1MB limit
)Type: Callable[[str], None] | None
Default: None
Callback for stderr output from CLI. Useful for logging and debugging.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
def stderr_handler(output: str):
"""Handle stderr output."""
print(f"CLI stderr: {output}")
options = ClaudeAgentOptions(
stderr=stderr_handler
)Type: dict[str, str | None]
Default: {} (no extra args)
Pass arbitrary CLI flags. Values are flag values, None for boolean flags.
Usage:
from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
extra_args={
"replay-user-messages": None, # Boolean flag without value
"custom-flag": "value", # Flag with value
"another-option": "123"
}
)import anyio
from pathlib import Path
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AgentDefinition,
tool,
create_sdk_mcp_server,
HookMatcher
)
# Define custom tool
@tool("custom_tool", "A custom tool", {"input": str})
async def custom_tool(args):
return {"content": [{"type": "text", "text": f"Processed: {args['input']}"}]}
# Define hook
async def logging_hook(input, tool_use_id, context):
print(f"Tool {input.get('tool_name')} called")
return {"continue_": True}
async def main():
# Create MCP server
server = create_sdk_mcp_server("custom", tools=[custom_tool])
# Complete configuration
options = ClaudeAgentOptions(
# Tools and permissions
allowed_tools=["Read", "Write", "Edit", "mcp__custom__custom_tool"],
disallowed_tools=[],
permission_mode="acceptEdits",
# Model configuration
model="sonnet",
fallback_model="haiku",
max_thinking_tokens=5000,
betas=["context-1m-2025-08-07"],
# Session
system_prompt="You are an expert Python developer",
max_turns=10,
max_budget_usd=2.0,
# Environment
cwd=Path("/path/to/project"),
env={"DEBUG": "true", "ENVIRONMENT": "development"},
add_dirs=["/path/to/lib"],
# Integration
mcp_servers={"custom": server},
hooks={
"PreToolUse": [HookMatcher(hooks=[logging_hook])]
},
agents={
"test": AgentDefinition(
description="Test agent",
prompt="Run tests",
tools=["Bash"],
model="haiku"
)
},
# Advanced
enable_file_checkpointing=True,
sandbox={
"enabled": True,
"autoAllowBashIfSandboxed": True
}
)
async with ClaudeSDKClient(options=options) as client:
await client.query("Help me with this project")
async for msg in client.receive_response():
print(msg)
anyio.run(main)from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
permission_mode="acceptEdits", # No prompts
max_turns=20,
max_budget_usd=5.0,
model="haiku", # Fast and cheap
allowed_tools=["Read", "Write", "Bash"]
)from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
permission_mode="default", # Prompt for actions
allowed_tools=["Read", "Grep", "Glob"], # Read-only
max_turns=10
)from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
permission_mode="bypassPermissions",
setting_sources=["project"], # Reproducible
max_budget_usd=1.0,
env={"CI": "true"}
)from claude_agent_sdk import ClaudeAgentOptions
options = ClaudeAgentOptions(
enable_file_checkpointing=True, # Allow rewind
permission_mode="acceptEdits",
model="sonnet",
sandbox={"enabled": True}
)Cause: Options passed after client already connected
Solution: Pass options to client constructor before connecting
Cause: Not in allowed_tools or in disallowed_tools
Solution: Add to allowed_tools, remove from disallowed_tools
Cause: No max_budget_usd or high max_turns
Solution: Set budget and turn limits
Cause: permission_mode="default"
Solution: Use "acceptEdits" or "bypassPermissions"
Install with Tessl CLI
npx tessl i tessl/pypi-claude-agent-sdk