Python SDK for Claude Code providing simple query functions and advanced bidirectional interactive conversations with custom tool support
The ClaudeCodeOptions dataclass provides comprehensive configuration for controlling Claude Code behavior including tool permissions, working directory, system prompts, MCP server configurations, and advanced features.
Central configuration object for all Claude Code SDK options and settings.
@dataclass
class ClaudeCodeOptions:
"""Query options for Claude SDK."""
allowed_tools: list[str] = field(default_factory=list)
system_prompt: str | None = None
append_system_prompt: str | None = None
mcp_servers: dict[str, McpServerConfig] | str | Path = field(default_factory=dict)
permission_mode: PermissionMode | None = None
continue_conversation: bool = False
resume: str | None = None
max_turns: int | None = None
disallowed_tools: list[str] = field(default_factory=list)
model: str | None = None
permission_prompt_tool_name: str | None = None
cwd: str | Path | None = None
settings: str | None = None
add_dirs: list[str | Path] = field(default_factory=list)
env: dict[str, str] = field(default_factory=dict)
extra_args: dict[str, str | None] = field(default_factory=dict)
debug_stderr: Any = sys.stderr
can_use_tool: CanUseTool | None = None
hooks: dict[HookEvent, list[HookMatcher]] | None = None
user: str | None = None
include_partial_messages: bool = FalseControl how tool execution permissions are handled.
PermissionMode = Literal["default", "acceptEdits", "plan", "bypassPermissions"]Permission Modes:
"default": CLI prompts for dangerous tools (interactive)"acceptEdits": Auto-accept file edits while prompting for other dangerous operations"plan": Show execution plan without running tools"bypassPermissions": Allow all tools without prompts (use with caution)Support for multiple MCP server types including in-process SDK servers.
McpServerConfig = (
McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig
)
class McpStdioServerConfig(TypedDict):
"""MCP stdio server configuration."""
type: NotRequired[Literal["stdio"]] # Optional for backwards compatibility
command: str
args: NotRequired[list[str]]
env: NotRequired[dict[str, str]]
class McpSSEServerConfig(TypedDict):
"""MCP SSE server configuration."""
type: Literal["sse"]
url: str
headers: NotRequired[dict[str, str]]
class McpHttpServerConfig(TypedDict):
"""MCP HTTP server configuration."""
type: Literal["http"]
url: str
headers: NotRequired[dict[str, str]]
class McpSdkServerConfig(TypedDict):
"""SDK MCP server configuration."""
type: Literal["sdk"]
name: str
instance: "McpServer"from claude_code_sdk import ClaudeCodeOptions, query
# Simple configuration
options = ClaudeCodeOptions(
system_prompt="You are a helpful Python developer",
allowed_tools=["Read", "Write", "Bash"],
max_turns=5
)
async def main():
async for message in query(
prompt="Create a simple web server",
options=options
):
print(message)from pathlib import Path
from claude_code_sdk import ClaudeCodeOptions
# Using string path
options = ClaudeCodeOptions(
cwd="/home/user/my-project",
allowed_tools=["Read", "Write", "Bash"]
)
# Using Path object
project_path = Path.home() / "projects" / "my-app"
options = ClaudeCodeOptions(
cwd=project_path,
allowed_tools=["Read", "Write", "Bash"]
)from claude_code_sdk import ClaudeCodeOptions
# Auto-accept file edits but prompt for other dangerous operations
options = ClaudeCodeOptions(
permission_mode="acceptEdits",
allowed_tools=["Read", "Write", "Edit", "Bash"]
)
# Bypass all permissions (use with extreme caution)
options = ClaudeCodeOptions(
permission_mode="bypassPermissions",
allowed_tools=["Read", "Write", "Edit", "Bash", "WebSearch"]
)
# Plan mode - show what would be done without executing
options = ClaudeCodeOptions(
permission_mode="plan",
allowed_tools=["Read", "Write", "Edit", "Bash"]
)from claude_code_sdk import ClaudeCodeOptions, create_sdk_mcp_server, tool
# SDK (in-process) server
@tool("calculate", "Perform calculation", {"expression": str})
async def calculate(args):
result = eval(args["expression"]) # In production, use safe evaluation
return {"content": [{"type": "text", "text": f"Result: {result}"}]}
sdk_server = create_sdk_mcp_server("calculator", tools=[calculate])
# External stdio server
stdio_server = {
"type": "stdio",
"command": "python",
"args": ["-m", "external_server"],
"env": {"DEBUG": "1"}
}
# HTTP server
http_server = {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": {"Authorization": "Bearer token123"}
}
# SSE server
sse_server = {
"type": "sse",
"url": "http://localhost:8080/sse",
"headers": {"X-API-Key": "key123"}
}
options = ClaudeCodeOptions(
mcp_servers={
"calculator": sdk_server, # In-process server
"external": stdio_server, # External process
"remote_http": http_server, # HTTP server
"remote_sse": sse_server # SSE server
},
allowed_tools=[
"mcp__calculator__calculate",
"mcp__external__some_tool",
"mcp__remote_http__api_call",
"mcp__remote_sse__stream_data"
]
)from claude_code_sdk import ClaudeCodeOptions, HookMatcher
async def pre_tool_hook(input_data, tool_use_id, context):
# Custom validation logic
return {}
options = ClaudeCodeOptions(
# Basic settings
system_prompt="You are an expert developer",
append_system_prompt="Always explain your reasoning",
model="claude-3-5-sonnet-20241022",
max_turns=10,
user="developer_123",
# Tool configuration
allowed_tools=["Read", "Write", "Edit", "Bash", "WebSearch"],
disallowed_tools=["dangerous_tool"],
# Permission and security
permission_mode="acceptEdits",
# Working environment
cwd="/workspace/project",
add_dirs=["/workspace/shared", "/workspace/libs"],
env={
"PYTHON_PATH": "/workspace/python",
"NODE_PATH": "/workspace/node_modules",
"DEBUG": "1"
},
# Advanced features
continue_conversation=True,
resume="session_id_123",
include_partial_messages=True,
# Hooks
hooks={
"PreToolUse": [
HookMatcher(matcher="Bash|Write|Edit", hooks=[pre_tool_hook])
]
},
# CLI integration
settings="/path/to/claude/settings.json",
extra_args={
"--verbose": None,
"--output-style": "detailed",
"--custom-flag": "value"
},
debug_stderr=open("/tmp/claude_debug.log", "w")
)from claude_code_sdk import (
ClaudeCodeOptions, PermissionResultAllow, PermissionResultDeny,
ToolPermissionContext
)
async def custom_permission_checker(
tool_name: str,
tool_input: dict[str, Any],
context: ToolPermissionContext
) -> PermissionResult:
"""Custom permission checker for tool usage."""
# Allow read operations
if tool_name in ["Read", "Glob", "Grep"]:
return PermissionResultAllow()
# Require approval for write operations
if tool_name in ["Write", "Edit", "MultiEdit"]:
# In a real application, you might show a UI prompt
user_approval = await get_user_approval(f"Allow {tool_name} operation?")
if user_approval:
return PermissionResultAllow()
else:
return PermissionResultDeny(
message=f"User denied {tool_name} operation",
interrupt=False
)
# Deny dangerous operations
if tool_name == "Bash" and any(cmd in str(tool_input) for cmd in ["rm -rf", "format"]):
return PermissionResultDeny(
message="Dangerous command blocked",
interrupt=True
)
# Default allow
return PermissionResultAllow()
# Note: can_use_tool requires streaming mode (AsyncIterable prompt)
options = ClaudeCodeOptions(
can_use_tool=custom_permission_checker,
allowed_tools=["Read", "Write", "Edit", "Bash"]
)
# This requires using ClaudeSDKClient, not the query() function
async def main():
async with ClaudeSDKClient(options=options) as client:
await client.query("Create a new Python file")
async for msg in client.receive_response():
print(msg)import os
from pathlib import Path
from claude_code_sdk import ClaudeCodeOptions
# Set up project environment
project_root = Path("/workspace/my-project")
venv_path = project_root / "venv"
options = ClaudeCodeOptions(
cwd=project_root,
add_dirs=[
project_root / "src",
project_root / "tests",
project_root / "docs"
],
env={
"VIRTUAL_ENV": str(venv_path),
"PATH": f"{venv_path / 'bin'}:{os.environ['PATH']}",
"PYTHONPATH": str(project_root / "src"),
"PROJECT_ROOT": str(project_root),
"ENVIRONMENT": "development"
},
allowed_tools=["Read", "Write", "Edit", "Bash", "WebSearch"]
)Some configuration options have validation rules:
can_use_tool callback requires streaming mode (AsyncIterable prompt)can_use_tool and permission_prompt_tool_name are mutually exclusivecan_use_tool, the SDK automatically sets permission_prompt_tool_name="stdio"mcp_servers must be uniqueMcpServer instancesallowed_tools must exist or be provided by configured MCP serversdisallowed_tools takes precedence over allowed_toolsmcp__<server_name>__<tool_name>When ClaudeCodeOptions() is instantiated with no arguments:
ClaudeCodeOptions(
allowed_tools=[], # No tools allowed by default
system_prompt=None, # No custom system prompt
permission_mode=None, # Use Claude Code's default behavior
cwd=None, # Use current working directory
mcp_servers={}, # No MCP servers
max_turns=None, # No turn limit
continue_conversation=False, # Start fresh conversation
include_partial_messages=False, # No streaming message updates
# ... other fields use their default values
)query(): Accepts options parameter with ClaudeCodeOptions instanceClaudeSDKClient: Accepts options parameter in constructorFor specific tool and permission configurations, see:
Install with Tessl CLI
npx tessl i tessl/pypi-claude-code-sdk