# Agent SDK reference - Python > Complete API reference for the Python Agent SDK, including all functions, types, and classes. ## Installation ```bash theme={null} pip install claude-agent-sdk ``` ## Choosing Between `query()` and `ClaudeSDKClient` The Python SDK provides two ways to interact with Claude Code: ### Quick Comparison | Feature | `query()` | `ClaudeSDKClient` | | :------------------ | :---------------------------- | :--------------------------------- | | **Session** | Creates new session each time | Reuses same session | | **Conversation** | Single exchange | Multiple exchanges in same context | | **Connection** | Managed automatically | Manual control | | **Streaming Input** | ✅ Supported | ✅ Supported | | **Interrupts** | ❌ Not supported | ✅ Supported | | **Hooks** | ❌ Not supported | ✅ Supported | | **Custom Tools** | ❌ Not supported | ✅ Supported | | **Continue Chat** | ❌ New session each time | ✅ Maintains conversation | | **Use Case** | One-off tasks | Continuous conversations | ### When to Use `query()` (New Session Each Time) **Best for:** * One-off questions where you don't need conversation history * Independent tasks that don't require context from previous exchanges * Simple automation scripts * When you want a fresh start each time ### When to Use `ClaudeSDKClient` (Continuous Conversation) **Best for:** * **Continuing conversations** - When you need Claude to remember context * **Follow-up questions** - Building on previous responses * **Interactive applications** - Chat interfaces, REPLs * **Response-driven logic** - When next action depends on Claude's response * **Session control** - Managing conversation lifecycle explicitly ## Functions ### `query()` Creates a new session for each interaction with Claude Code. Returns an async iterator that yields messages as they arrive. Each call to `query()` starts fresh with no memory of previous interactions. ```python theme={null} async def query( *, prompt: str | AsyncIterable[dict[str, Any]], options: ClaudeAgentOptions | None = None ) -> AsyncIterator[Message] ``` #### Parameters | Parameter | Type | Description | | :-------- | :--------------------------- | :------------------------------------------------------------------------- | | `prompt` | `str \| AsyncIterable[dict]` | The input prompt as a string or async iterable for streaming mode | | `options` | `ClaudeAgentOptions \| None` | Optional configuration object (defaults to `ClaudeAgentOptions()` if None) | #### Returns Returns an `AsyncIterator[Message]` that yields messages from the conversation. #### Example - With options ```python theme={null} import asyncio from claude_agent_sdk import query, ClaudeAgentOptions async def main(): options = ClaudeAgentOptions( system_prompt="You are an expert Python developer", permission_mode='acceptEdits', cwd="/home/user/project" ) async for message in query( prompt="Create a Python web server", options=options ): print(message) asyncio.run(main()) ``` ### `tool()` Decorator for defining MCP tools with type safety. ```python theme={null} def tool( name: str, description: str, input_schema: type | dict[str, Any] ) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]] ``` #### Parameters | Parameter | Type | Description | | :------------- | :----------------------- | :------------------------------------------------------ | | `name` | `str` | Unique identifier for the tool | | `description` | `str` | Human-readable description of what the tool does | | `input_schema` | `type \| dict[str, Any]` | Schema defining the tool's input parameters (see below) | #### Input Schema Options 1. **Simple type mapping** (recommended): ```python theme={null} {"text": str, "count": int, "enabled": bool} ``` 2. **JSON Schema format** (for complex validation): ```python theme={null} { "type": "object", "properties": { "text": {"type": "string"}, "count": {"type": "integer", "minimum": 0} }, "required": ["text"] } ``` #### Returns A decorator function that wraps the tool implementation and returns an `SdkMcpTool` instance. #### Example ```python theme={null} from claude_agent_sdk import tool from typing import Any @tool("greet", "Greet a user", {"name": str}) async def greet(args: dict[str, Any]) -> dict[str, Any]: return { "content": [{ "type": "text", "text": f"Hello, {args['name']}!" }] } ``` ### `create_sdk_mcp_server()` Create an in-process MCP server that runs within your Python application. ```python theme={null} def create_sdk_mcp_server( name: str, version: str = "1.0.0", tools: list[SdkMcpTool[Any]] | None = None ) -> McpSdkServerConfig ``` #### Parameters | Parameter | Type | Default | Description | | :-------- | :------------------------------ | :-------- | :---------------------------------------------------- | | `name` | `str` | - | Unique identifier for the server | | `version` | `str` | `"1.0.0"` | Server version string | | `tools` | `list[SdkMcpTool[Any]] \| None` | `None` | List of tool functions created with `@tool` decorator | #### Returns Returns an `McpSdkServerConfig` object that can be passed to `ClaudeAgentOptions.mcp_servers`. #### Example ```python theme={null} from claude_agent_sdk import tool, create_sdk_mcp_server @tool("add", "Add two numbers", {"a": float, "b": float}) async def add(args): return { "content": [{ "type": "text", "text": f"Sum: {args['a'] + args['b']}" }] } @tool("multiply", "Multiply two 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] # Pass decorated functions ) # Use with Claude options = ClaudeAgentOptions( mcp_servers={"calc": calculator}, allowed_tools=["mcp__calc__add", "mcp__calc__multiply"] ) ``` ## Classes ### `ClaudeSDKClient` **Maintains a conversation session across multiple exchanges.** This is the Python equivalent of how the TypeScript SDK's `query()` function works internally - it creates a client object that can continue conversations. #### Key Features * **Session Continuity**: Maintains conversation context across multiple `query()` calls * **Same Conversation**: Claude remembers previous messages in the session * **Interrupt Support**: Can stop Claude mid-execution * **Explicit Lifecycle**: You control when the session starts and ends * **Response-driven Flow**: Can react to responses and send follow-ups * **Custom Tools & Hooks**: Supports custom tools (created with `@tool` decorator) and hooks ```python theme={null} class ClaudeSDKClient: def __init__(self, options: ClaudeAgentOptions | None = None) async def connect(self, prompt: str | AsyncIterable[dict] | None = None) -> None async def query(self, prompt: str | AsyncIterable[dict], session_id: str = "default") -> None async def receive_messages(self) -> AsyncIterator[Message] async def receive_response(self) -> AsyncIterator[Message] async def interrupt(self) -> None async def disconnect(self) -> None ``` #### Methods | Method | Description | | :-------------------------- | :------------------------------------------------------------------ | | `__init__(options)` | Initialize the client with optional configuration | | `connect(prompt)` | Connect to Claude with an optional initial prompt or message stream | | `query(prompt, session_id)` | Send a new request in streaming mode | | `receive_messages()` | Receive all messages from Claude as an async iterator | | `receive_response()` | Receive messages until and including a ResultMessage | | `interrupt()` | Send interrupt signal (only works in streaming mode) | | `disconnect()` | Disconnect from Claude | #### Context Manager Support The client can be used as an async context manager for automatic connection management: ```python theme={null} async with ClaudeSDKClient() as client: await client.query("Hello Claude") async for message in client.receive_response(): print(message) ``` > **Important:** When iterating over messages, avoid using `break` to exit early as this can cause asyncio cleanup issues. Instead, let the iteration complete naturally or use flags to track when you've found what you need. #### Example - Continuing a conversation ```python theme={null} import asyncio from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock, ResultMessage async def main(): async with ClaudeSDKClient() as client: # First question await client.query("What's the capital of France?") # Process response async for message in client.receive_response(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}") # Follow-up question - Claude remembers the previous context await client.query("What's the population of that city?") async for message in client.receive_response(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}") # Another follow-up - still in the same conversation await client.query("What are some famous landmarks there?") async for message in client.receive_response(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(f"Claude: {block.text}") asyncio.run(main()) ``` #### Example - Streaming input with ClaudeSDKClient ```python theme={null} import asyncio from claude_agent_sdk import ClaudeSDKClient async def message_stream(): """Generate messages dynamically.""" yield {"type": "text", "text": "Analyze the following data:"} await asyncio.sleep(0.5) yield {"type": "text", "text": "Temperature: 25°C"} await asyncio.sleep(0.5) yield {"type": "text", "text": "Humidity: 60%"} await asyncio.sleep(0.5) yield {"type": "text", "text": "What patterns do you see?"} async def main(): async with ClaudeSDKClient() as client: # Stream input to Claude await client.query(message_stream()) # Process response async for message in client.receive_response(): print(message) # Follow-up in same session await client.query("Should we be concerned about these readings?") async for message in client.receive_response(): print(message) asyncio.run(main()) ``` #### Example - Using interrupts ```python theme={null} import asyncio from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions async def interruptible_task(): options = ClaudeAgentOptions( allowed_tools=["Bash"], permission_mode="acceptEdits" ) async with ClaudeSDKClient(options=options) as client: # Start a long-running task await client.query("Count from 1 to 100 slowly") # Let it run for a bit await asyncio.sleep(2) # Interrupt the task await client.interrupt() print("Task interrupted!") # Send a new command await client.query("Just say hello instead") async for message in client.receive_response(): # Process the new response pass asyncio.run(interruptible_task()) ``` #### Example - Advanced permission control ```python theme={null} from claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions ) async def custom_permission_handler( tool_name: str, input_data: dict, context: dict ): """Custom logic for tool permissions.""" # Block writes to system directories if tool_name == "Write" and input_data.get("file_path", "").startswith("/system/"): return { "behavior": "deny", "message": "System directory write not allowed", "interrupt": True } # Redirect sensitive file operations if tool_name in ["Write", "Edit"] and "config" in input_data.get("file_path", ""): safe_path = f"./sandbox/{input_data['file_path']}" return { "behavior": "allow", "updatedInput": {**input_data, "file_path": safe_path} } # Allow everything else return { "behavior": "allow", "updatedInput": input_data } async def main(): options = ClaudeAgentOptions( can_use_tool=custom_permission_handler, allowed_tools=["Read", "Write", "Edit"] ) async with ClaudeSDKClient(options=options) as client: await client.query("Update the system config file") async for message in client.receive_response(): # Will use sandbox path instead print(message) asyncio.run(main()) ``` ## Types ### `SdkMcpTool` Definition for an SDK MCP tool created with the `@tool` decorator. ```python theme={null} @dataclass class SdkMcpTool(Generic[T]): name: str description: str input_schema: type[T] | dict[str, Any] handler: Callable[[T], Awaitable[dict[str, Any]]] ``` | Property | Type | Description | | :------------- | :----------------------------------------- | :----------------------------------------- | | `name` | `str` | Unique identifier for the tool | | `description` | `str` | Human-readable description | | `input_schema` | `type[T] \| dict[str, Any]` | Schema for input validation | | `handler` | `Callable[[T], Awaitable[dict[str, Any]]]` | Async function that handles tool execution | ### `ClaudeAgentOptions` Configuration dataclass for Claude Code queries. ```python theme={null} @dataclass class ClaudeAgentOptions: allowed_tools: list[str] = field(default_factory=list) system_prompt: str | SystemPromptPreset | 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) max_buffer_size: int | None = None debug_stderr: Any = sys.stderr # Deprecated stderr: Callable[[str], None] | None = None can_use_tool: CanUseTool | None = None hooks: dict[HookEvent, list[HookMatcher]] | None = None user: str | None = None include_partial_messages: bool = False fork_session: bool = False agents: dict[str, AgentDefinition] | None = None setting_sources: list[SettingSource] | None = None ``` | Property | Type | Default | Description | | :---------------------------- | :------------------------------------------- | :------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `allowed_tools` | `list[str]` | `[]` | List of allowed tool names | | `system_prompt` | `str \| SystemPromptPreset \| None` | `None` | System prompt configuration. Pass a string for custom prompt, or use `{"type": "preset", "preset": "claude_code"}` for Claude Code's system prompt. Add `"append"` to extend the preset | | `mcp_servers` | `dict[str, McpServerConfig] \| str \| Path` | `{}` | MCP server configurations or path to config file | | `permission_mode` | `PermissionMode \| None` | `None` | Permission mode for tool usage | | `continue_conversation` | `bool` | `False` | Continue the most recent conversation | | `resume` | `str \| None` | `None` | Session ID to resume | | `max_turns` | `int \| None` | `None` | Maximum conversation turns | | `disallowed_tools` | `list[str]` | `[]` | List of disallowed tool names | | `model` | `str \| None` | `None` | Claude model to use | | `permission_prompt_tool_name` | `str \| None` | `None` | MCP tool name for permission prompts | | `cwd` | `str \| Path \| None` | `None` | Current working directory | | `settings` | `str \| None` | `None` | Path to settings file | | `add_dirs` | `list[str \| Path]` | `[]` | Additional directories Claude can access | | `env` | `dict[str, str]` | `{}` | Environment variables | | `extra_args` | `dict[str, str \| None]` | `{}` | Additional CLI arguments to pass directly to the CLI | | `max_buffer_size` | `int \| None` | `None` | Maximum bytes when buffering CLI stdout | | `debug_stderr` | `Any` | `sys.stderr` | *Deprecated* - File-like object for debug output. Use `stderr` callback instead | | `stderr` | `Callable[[str], None] \| None` | `None` | Callback function for stderr output from CLI | | `can_use_tool` | `CanUseTool \| None` | `None` | Tool permission callback function | | `hooks` | `dict[HookEvent, list[HookMatcher]] \| None` | `None` | Hook configurations for intercepting events | | `user` | `str \| None` | `None` | User identifier | | `include_partial_messages` | `bool` | `False` | Include partial message streaming events | | `fork_session` | `bool` | `False` | When resuming with `resume`, fork to a new session ID instead of continuing the original session | | `agents` | `dict[str, AgentDefinition] \| None` | `None` | Programmatically defined subagents | | `plugins` | `list[SdkPluginConfig]` | `[]` | Load custom plugins from local paths. See [Plugins](/en/api/agent-sdk/plugins) for details | | `setting_sources` | `list[SettingSource] \| None` | `None` (no settings) | Control which filesystem settings to load. When omitted, no settings are loaded. **Note:** Must include `"project"` to load CLAUDE.md files | ### `SystemPromptPreset` Configuration for using Claude Code's preset system prompt with optional additions. ```python theme={null} class SystemPromptPreset(TypedDict): type: Literal["preset"] preset: Literal["claude_code"] append: NotRequired[str] ``` | Field | Required | Description | | :------- | :------- | :------------------------------------------------------------ | | `type` | Yes | Must be `"preset"` to use a preset system prompt | | `preset` | Yes | Must be `"claude_code"` to use Claude Code's system prompt | | `append` | No | Additional instructions to append to the preset system prompt | ### `SettingSource` Controls which filesystem-based configuration sources the SDK loads settings from. ```python theme={null} SettingSource = Literal["user", "project", "local"] ``` | Value | Description | Location | | :---------- | :------------------------------------------- | :---------------------------- | | `"user"` | Global user settings | `~/.claude/settings.json` | | `"project"` | Shared project settings (version controlled) | `.claude/settings.json` | | `"local"` | Local project settings (gitignored) | `.claude/settings.local.json` | #### Default behavior When `setting_sources` is **omitted** or **`None`**, the SDK does **not** load any filesystem settings. This provides isolation for SDK applications. #### Why use setting\_sources? **Load all filesystem settings (legacy behavior):** ```python theme={null} # Load all settings like SDK v0.0.x did from claude_agent_sdk import query, ClaudeAgentOptions async for message in query( prompt="Analyze this code", options=ClaudeAgentOptions( setting_sources=["user", "project", "local"] # Load all settings ) ): print(message) ``` **Load only specific setting sources:** ```python theme={null} # Load only project settings, ignore user and local async for message in query( prompt="Run CI checks", options=ClaudeAgentOptions( setting_sources=["project"] # Only .claude/settings.json ) ): print(message) ``` **Testing and CI environments:** ```python theme={null} # Ensure consistent behavior in CI by excluding local settings async for message in query( prompt="Run tests", options=ClaudeAgentOptions( setting_sources=["project"], # Only team-shared settings permission_mode="bypassPermissions" ) ): print(message) ``` **SDK-only applications:** ```python theme={null} # Define everything programmatically (default behavior) # No filesystem dependencies - setting_sources defaults to None async for message in query( prompt="Review this PR", options=ClaudeAgentOptions( # setting_sources=None is the default, no need to specify agents={ /* ... */ }, mcp_servers={ /* ... */ }, allowed_tools=["Read", "Grep", "Glob"] ) ): print(message) ``` **Loading CLAUDE.md project instructions:** ```python theme={null} # Load project settings to include CLAUDE.md files async for message in query( prompt="Add a new feature following project conventions", options=ClaudeAgentOptions( system_prompt={ "type": "preset", "preset": "claude_code" # Use Claude Code's system prompt }, setting_sources=["project"], # Required to load CLAUDE.md from project allowed_tools=["Read", "Write", "Edit"] ) ): print(message) ``` #### Settings precedence When multiple sources are loaded, settings are merged with this precedence (highest to lowest): 1. Local settings (`.claude/settings.local.json`) 2. Project settings (`.claude/settings.json`) 3. User settings (`~/.claude/settings.json`) Programmatic options (like `agents`, `allowed_tools`) always override filesystem settings. ### `AgentDefinition` Configuration for a subagent defined programmatically. ```python theme={null} @dataclass class AgentDefinition: description: str prompt: str tools: list[str] | None = None model: Literal["sonnet", "opus", "haiku", "inherit"] | None = None ``` | Field | Required | Description | | :------------ | :------- | :------------------------------------------------------------- | | `description` | Yes | Natural language description of when to use this agent | | `tools` | No | Array of allowed tool names. If omitted, inherits all tools | | `prompt` | Yes | The agent's system prompt | | `model` | No | Model override for this agent. If omitted, uses the main model | ### `PermissionMode` Permission modes for controlling tool execution. ```python theme={null} PermissionMode = Literal[ "default", # Standard permission behavior "acceptEdits", # Auto-accept file edits "plan", # Planning mode - no execution "bypassPermissions" # Bypass all permission checks (use with caution) ] ``` ### `McpSdkServerConfig` Configuration for SDK MCP servers created with `create_sdk_mcp_server()`. ```python theme={null} class McpSdkServerConfig(TypedDict): type: Literal["sdk"] name: str instance: Any # MCP Server instance ``` ### `McpServerConfig` Union type for MCP server configurations. ```python theme={null} McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig ``` #### `McpStdioServerConfig` ```python theme={null} class McpStdioServerConfig(TypedDict): type: NotRequired[Literal["stdio"]] # Optional for backwards compatibility command: str args: NotRequired[list[str]] env: NotRequired[dict[str, str]] ``` #### `McpSSEServerConfig` ```python theme={null} class McpSSEServerConfig(TypedDict): type: Literal["sse"] url: str headers: NotRequired[dict[str, str]] ``` #### `McpHttpServerConfig` ```python theme={null} class McpHttpServerConfig(TypedDict): type: Literal["http"] url: str headers: NotRequired[dict[str, str]] ``` ### `SdkPluginConfig` Configuration for loading plugins in the SDK. ```python theme={null} class SdkPluginConfig(TypedDict): type: Literal["local"] path: str ``` | Field | Type | Description | | :----- | :----------------- | :--------------------------------------------------------- | | `type` | `Literal["local"]` | Must be `"local"` (only local plugins currently supported) | | `path` | `str` | Absolute or relative path to the plugin directory | **Example:** ```python theme={null} plugins=[ {"type": "local", "path": "./my-plugin"}, {"type": "local", "path": "/absolute/path/to/plugin"} ] ``` For complete information on creating and using plugins, see [Plugins](/en/api/agent-sdk/plugins). ## Message Types ### `Message` Union type of all possible messages. ```python theme={null} Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage ``` ### `UserMessage` User input message. ```python theme={null} @dataclass class UserMessage: content: str | list[ContentBlock] ``` ### `AssistantMessage` Assistant response message with content blocks. ```python theme={null} @dataclass class AssistantMessage: content: list[ContentBlock] model: str ``` ### `SystemMessage` System message with metadata. ```python theme={null} @dataclass class SystemMessage: subtype: str data: dict[str, Any] ``` ### `ResultMessage` Final result message with cost and usage information. ```python theme={null} @dataclass class ResultMessage: subtype: str duration_ms: int duration_api_ms: int is_error: bool num_turns: int session_id: str total_cost_usd: float | None = None usage: dict[str, Any] | None = None result: str | None = None ``` ## Content Block Types ### `ContentBlock` Union type of all content blocks. ```python theme={null} ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock ``` ### `TextBlock` Text content block. ```python theme={null} @dataclass class TextBlock: text: str ``` ### `ThinkingBlock` Thinking content block (for models with thinking capability). ```python theme={null} @dataclass class ThinkingBlock: thinking: str signature: str ``` ### `ToolUseBlock` Tool use request block. ```python theme={null} @dataclass class ToolUseBlock: id: str name: str input: dict[str, Any] ``` ### `ToolResultBlock` Tool execution result block. ```python theme={null} @dataclass class ToolResultBlock: tool_use_id: str content: str | list[dict[str, Any]] | None = None is_error: bool | None = None ``` ## Error Types ### `ClaudeSDKError` Base exception class for all SDK errors. ```python theme={null} class ClaudeSDKError(Exception): """Base error for Claude SDK.""" ``` ### `CLINotFoundError` Raised when Claude Code CLI is not installed or not found. ```python theme={null} class CLINotFoundError(CLIConnectionError): def __init__(self, message: str = "Claude Code not found", cli_path: str | None = None): """ Args: message: Error message (default: "Claude Code not found") cli_path: Optional path to the CLI that was not found """ ``` ### `CLIConnectionError` Raised when connection to Claude Code fails. ```python theme={null} class CLIConnectionError(ClaudeSDKError): """Failed to connect to Claude Code.""" ``` ### `ProcessError` Raised when the Claude Code process fails. ```python theme={null} class ProcessError(ClaudeSDKError): def __init__(self, message: str, exit_code: int | None = None, stderr: str | None = None): self.exit_code = exit_code self.stderr = stderr ``` ### `CLIJSONDecodeError` Raised when JSON parsing fails. ```python theme={null} class CLIJSONDecodeError(ClaudeSDKError): def __init__(self, line: str, original_error: Exception): """ Args: line: The line that failed to parse original_error: The original JSON decode exception """ self.line = line self.original_error = original_error ``` ## Hook Types ### `HookEvent` Supported hook event types. Note that due to setup limitations, the Python SDK does not support SessionStart, SessionEnd, and Notification hooks. ```python theme={null} HookEvent = Literal[ "PreToolUse", # Called before tool execution "PostToolUse", # Called after tool execution "UserPromptSubmit", # Called when user submits a prompt "Stop", # Called when stopping execution "SubagentStop", # Called when a subagent stops "PreCompact" # Called before message compaction ] ``` ### `HookCallback` Type definition for hook callback functions. ```python theme={null} HookCallback = Callable[ [dict[str, Any], str | None, HookContext], Awaitable[dict[str, Any]] ] ``` Parameters: * `input_data`: Hook-specific input data (see [hook documentation](https://docs.claude.com/en/docs/claude-code/hooks#hook-input)) * `tool_use_id`: Optional tool use identifier (for tool-related hooks) * `context`: Hook context with additional information Returns a dictionary that may contain: * `decision`: `"block"` to block the action * `systemMessage`: System message to add to the transcript * `hookSpecificOutput`: Hook-specific output data ### `HookContext` Context information passed to hook callbacks. ```python theme={null} @dataclass class HookContext: signal: Any | None = None # Future: abort signal support ``` ### `HookMatcher` Configuration for matching hooks to specific events or tools. ```python theme={null} @dataclass class HookMatcher: matcher: str | None = None # Tool name or pattern to match (e.g., "Bash", "Write|Edit") hooks: list[HookCallback] = field(default_factory=list) # List of callbacks to execute ``` ### Hook Usage Example ```python theme={null} from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, HookContext from typing import Any async def validate_bash_command( input_data: dict[str, Any], tool_use_id: str | None, context: HookContext ) -> dict[str, Any]: """Validate and potentially block dangerous bash commands.""" if input_data['tool_name'] == 'Bash': command = input_data['tool_input'].get('command', '') if 'rm -rf /' in command: return { 'hookSpecificOutput': { 'hookEventName': 'PreToolUse', 'permissionDecision': 'deny', 'permissionDecisionReason': 'Dangerous command blocked' } } return {} async def log_tool_use( input_data: dict[str, Any], tool_use_id: str | None, context: HookContext ) -> dict[str, Any]: """Log all tool usage for auditing.""" print(f"Tool used: {input_data.get('tool_name')}") return {} options = ClaudeAgentOptions( hooks={ 'PreToolUse': [ HookMatcher(matcher='Bash', hooks=[validate_bash_command]), HookMatcher(hooks=[log_tool_use]) # Applies to all tools ], 'PostToolUse': [ HookMatcher(hooks=[log_tool_use]) ] } ) async for message in query( prompt="Analyze this codebase", options=options ): print(message) ``` ## Tool Input/Output Types Documentation of input/output schemas for all built-in Claude Code tools. While the Python SDK doesn't export these as types, they represent the structure of tool inputs and outputs in messages. ### Task **Tool name:** `Task` **Input:** ```python theme={null} { "description": str, # A short (3-5 word) description of the task "prompt": str, # The task for the agent to perform "subagent_type": str # The type of specialized agent to use } ``` **Output:** ```python theme={null} { "result": str, # Final result from the subagent "usage": dict | None, # Token usage statistics "total_cost_usd": float | None, # Total cost in USD "duration_ms": int | None # Execution duration in milliseconds } ``` ### Bash **Tool name:** `Bash` **Input:** ```python theme={null} { "command": str, # The command to execute "timeout": int | None, # Optional timeout in milliseconds (max 600000) "description": str | None, # Clear, concise description (5-10 words) "run_in_background": bool | None # Set to true to run in background } ``` **Output:** ```python theme={null} { "output": str, # Combined stdout and stderr output "exitCode": int, # Exit code of the command "killed": bool | None, # Whether command was killed due to timeout "shellId": str | None # Shell ID for background processes } ``` ### Edit **Tool name:** `Edit` **Input:** ```python theme={null} { "file_path": str, # The absolute path to the file to modify "old_string": str, # The text to replace "new_string": str, # The text to replace it with "replace_all": bool | None # Replace all occurrences (default False) } ``` **Output:** ```python theme={null} { "message": str, # Confirmation message "replacements": int, # Number of replacements made "file_path": str # File path that was edited } ``` ### Read **Tool name:** `Read` **Input:** ```python theme={null} { "file_path": str, # The absolute path to the file to read "offset": int | None, # The line number to start reading from "limit": int | None # The number of lines to read } ``` **Output (Text files):** ```python theme={null} { "content": str, # File contents with line numbers "total_lines": int, # Total number of lines in file "lines_returned": int # Lines actually returned } ``` **Output (Images):** ```python theme={null} { "image": str, # Base64 encoded image data "mime_type": str, # Image MIME type "file_size": int # File size in bytes } ``` ### Write **Tool name:** `Write` **Input:** ```python theme={null} { "file_path": str, # The absolute path to the file to write "content": str # The content to write to the file } ``` **Output:** ```python theme={null} { "message": str, # Success message "bytes_written": int, # Number of bytes written "file_path": str # File path that was written } ``` ### Glob **Tool name:** `Glob` **Input:** ```python theme={null} { "pattern": str, # The glob pattern to match files against "path": str | None # The directory to search in (defaults to cwd) } ``` **Output:** ```python theme={null} { "matches": list[str], # Array of matching file paths "count": int, # Number of matches found "search_path": str # Search directory used } ``` ### Grep **Tool name:** `Grep` **Input:** ```python theme={null} { "pattern": str, # The regular expression pattern "path": str | None, # File or directory to search in "glob": str | None, # Glob pattern to filter files "type": str | None, # File type to search "output_mode": str | None, # "content", "files_with_matches", or "count" "-i": bool | None, # Case insensitive search "-n": bool | None, # Show line numbers "-B": int | None, # Lines to show before each match "-A": int | None, # Lines to show after each match "-C": int | None, # Lines to show before and after "head_limit": int | None, # Limit output to first N lines/entries "multiline": bool | None # Enable multiline mode } ``` **Output (content mode):** ```python theme={null} { "matches": [ { "file": str, "line_number": int | None, "line": str, "before_context": list[str] | None, "after_context": list[str] | None } ], "total_matches": int } ``` **Output (files\_with\_matches mode):** ```python theme={null} { "files": list[str], # Files containing matches "count": int # Number of files with matches } ``` ### NotebookEdit **Tool name:** `NotebookEdit` **Input:** ```python theme={null} { "notebook_path": str, # Absolute path to the Jupyter notebook "cell_id": str | None, # The ID of the cell to edit "new_source": str, # The new source for the cell "cell_type": "code" | "markdown" | None, # The type of the cell "edit_mode": "replace" | "insert" | "delete" | None # Edit operation type } ``` **Output:** ```python theme={null} { "message": str, # Success message "edit_type": "replaced" | "inserted" | "deleted", # Type of edit performed "cell_id": str | None, # Cell ID that was affected "total_cells": int # Total cells in notebook after edit } ``` ### WebFetch **Tool name:** `WebFetch` **Input:** ```python theme={null} { "url": str, # The URL to fetch content from "prompt": str # The prompt to run on the fetched content } ``` **Output:** ```python theme={null} { "response": str, # AI model's response to the prompt "url": str, # URL that was fetched "final_url": str | None, # Final URL after redirects "status_code": int | None # HTTP status code } ``` ### WebSearch **Tool name:** `WebSearch` **Input:** ```python theme={null} { "query": str, # The search query to use "allowed_domains": list[str] | None, # Only include results from these domains "blocked_domains": list[str] | None # Never include results from these domains } ``` **Output:** ```python theme={null} { "results": [ { "title": str, "url": str, "snippet": str, "metadata": dict | None } ], "total_results": int, "query": str } ``` ### TodoWrite **Tool name:** `TodoWrite` **Input:** ```python theme={null} { "todos": [ { "content": str, # The task description "status": "pending" | "in_progress" | "completed", # Task status "activeForm": str # Active form of the description } ] } ``` **Output:** ```python theme={null} { "message": str, # Success message "stats": { "total": int, "pending": int, "in_progress": int, "completed": int } } ``` ### BashOutput **Tool name:** `BashOutput` **Input:** ```python theme={null} { "bash_id": str, # The ID of the background shell "filter": str | None # Optional regex to filter output lines } ``` **Output:** ```python theme={null} { "output": str, # New output since last check "status": "running" | "completed" | "failed", # Current shell status "exitCode": int | None # Exit code when completed } ``` ### KillBash **Tool name:** `KillBash` **Input:** ```python theme={null} { "shell_id": str # The ID of the background shell to kill } ``` **Output:** ```python theme={null} { "message": str, # Success message "shell_id": str # ID of the killed shell } ``` ### ExitPlanMode **Tool name:** `ExitPlanMode` **Input:** ```python theme={null} { "plan": str # The plan to run by the user for approval } ``` **Output:** ```python theme={null} { "message": str, # Confirmation message "approved": bool | None # Whether user approved the plan } ``` ### ListMcpResources **Tool name:** `ListMcpResources` **Input:** ```python theme={null} { "server": str | None # Optional server name to filter resources by } ``` **Output:** ```python theme={null} { "resources": [ { "uri": str, "name": str, "description": str | None, "mimeType": str | None, "server": str } ], "total": int } ``` ### ReadMcpResource **Tool name:** `ReadMcpResource` **Input:** ```python theme={null} { "server": str, # The MCP server name "uri": str # The resource URI to read } ``` **Output:** ```python theme={null} { "contents": [ { "uri": str, "mimeType": str | None, "text": str | None, "blob": str | None } ], "server": str } ``` ## Advanced Features with ClaudeSDKClient ### Building a Continuous Conversation Interface ```python theme={null} from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock import asyncio class ConversationSession: """Maintains a single conversation session with Claude.""" def __init__(self, options: ClaudeAgentOptions = None): self.client = ClaudeSDKClient(options) self.turn_count = 0 async def start(self): await self.client.connect() print("Starting conversation session. Claude will remember context.") print("Commands: 'exit' to quit, 'interrupt' to stop current task, 'new' for new session") while True: user_input = input(f"\n[Turn {self.turn_count + 1}] You: ") if user_input.lower() == 'exit': break elif user_input.lower() == 'interrupt': await self.client.interrupt() print("Task interrupted!") continue elif user_input.lower() == 'new': # Disconnect and reconnect for a fresh session await self.client.disconnect() await self.client.connect() self.turn_count = 0 print("Started new conversation session (previous context cleared)") continue # Send message - Claude remembers all previous messages in this session await self.client.query(user_input) self.turn_count += 1 # Process response print(f"[Turn {self.turn_count}] Claude: ", end="") async for message in self.client.receive_response(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(block.text, end="") print() # New line after response await self.client.disconnect() print(f"Conversation ended after {self.turn_count} turns.") async def main(): options = ClaudeAgentOptions( allowed_tools=["Read", "Write", "Bash"], permission_mode="acceptEdits" ) session = ConversationSession(options) await session.start() # Example conversation: # Turn 1 - You: "Create a file called hello.py" # Turn 1 - Claude: "I'll create a hello.py file for you..." # Turn 2 - You: "What's in that file?" # Turn 2 - Claude: "The hello.py file I just created contains..." (remembers!) # Turn 3 - You: "Add a main function to it" # Turn 3 - Claude: "I'll add a main function to hello.py..." (knows which file!) asyncio.run(main()) ``` ### Using Hooks for Behavior Modification ```python theme={null} from claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, HookMatcher, HookContext ) import asyncio from typing import Any async def pre_tool_logger( input_data: dict[str, Any], tool_use_id: str | None, context: HookContext ) -> dict[str, Any]: """Log all tool usage before execution.""" tool_name = input_data.get('tool_name', 'unknown') print(f"[PRE-TOOL] About to use: {tool_name}") # You can modify or block the tool execution here if tool_name == "Bash" and "rm -rf" in str(input_data.get('tool_input', {})): return { 'hookSpecificOutput': { 'hookEventName': 'PreToolUse', 'permissionDecision': 'deny', 'permissionDecisionReason': 'Dangerous command blocked' } } return {} async def post_tool_logger( input_data: dict[str, Any], tool_use_id: str | None, context: HookContext ) -> dict[str, Any]: """Log results after tool execution.""" tool_name = input_data.get('tool_name', 'unknown') print(f"[POST-TOOL] Completed: {tool_name}") return {} async def user_prompt_modifier( input_data: dict[str, Any], tool_use_id: str | None, context: HookContext ) -> dict[str, Any]: """Add context to user prompts.""" original_prompt = input_data.get('prompt', '') # Add timestamp to all prompts from datetime import datetime timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return { 'hookSpecificOutput': { 'hookEventName': 'UserPromptSubmit', 'updatedPrompt': f"[{timestamp}] {original_prompt}" } } async def main(): options = ClaudeAgentOptions( hooks={ 'PreToolUse': [ HookMatcher(hooks=[pre_tool_logger]), HookMatcher(matcher='Bash', hooks=[pre_tool_logger]) ], 'PostToolUse': [ HookMatcher(hooks=[post_tool_logger]) ], 'UserPromptSubmit': [ HookMatcher(hooks=[user_prompt_modifier]) ] }, allowed_tools=["Read", "Write", "Bash"] ) async with ClaudeSDKClient(options=options) as client: await client.query("List files in current directory") async for message in client.receive_response(): # Hooks will automatically log tool usage pass asyncio.run(main()) ``` ### Real-time Progress Monitoring ```python theme={null} from claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, ToolUseBlock, ToolResultBlock, TextBlock ) import asyncio async def monitor_progress(): options = ClaudeAgentOptions( allowed_tools=["Write", "Bash"], permission_mode="acceptEdits" ) async with ClaudeSDKClient(options=options) as client: await client.query( "Create 5 Python files with different sorting algorithms" ) # Monitor progress in real-time files_created = [] async for message in client.receive_messages(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, ToolUseBlock): if block.name == "Write": file_path = block.input.get("file_path", "") print(f"🔨 Creating: {file_path}") elif isinstance(block, ToolResultBlock): print(f"✅ Completed tool execution") elif isinstance(block, TextBlock): print(f"💭 Claude says: {block.text[:100]}...") # Check if we've received the final result if hasattr(message, 'subtype') and message.subtype in ['success', 'error']: print(f"\n🎯 Task completed!") break asyncio.run(monitor_progress()) ``` ## Example Usage ### Basic file operations (using query) ```python theme={null} from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock import asyncio async def create_project(): options = ClaudeAgentOptions( allowed_tools=["Read", "Write", "Bash"], permission_mode='acceptEdits', cwd="/home/user/project" ) async for message in query( prompt="Create a Python project structure with setup.py", options=options ): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, ToolUseBlock): print(f"Using tool: {block.name}") asyncio.run(create_project()) ``` ### Error handling ```python theme={null} from claude_agent_sdk import ( query, CLINotFoundError, ProcessError, CLIJSONDecodeError ) try: async for message in query(prompt="Hello"): print(message) except CLINotFoundError: print("Please install Claude Code: npm install -g @anthropic-ai/claude-code") except ProcessError as e: print(f"Process failed with exit code: {e.exit_code}") except CLIJSONDecodeError as e: print(f"Failed to parse response: {e}") ``` ### Streaming mode with client ```python theme={null} from claude_agent_sdk import ClaudeSDKClient import asyncio async def interactive_session(): async with ClaudeSDKClient() as client: # Send initial message await client.query("What's the weather like?") # Process responses async for msg in client.receive_response(): print(msg) # Send follow-up await client.query("Tell me more about that") # Process follow-up response async for msg in client.receive_response(): print(msg) asyncio.run(interactive_session()) ``` ### Using custom tools with ClaudeSDKClient ```python theme={null} from claude_agent_sdk import ( ClaudeSDKClient, ClaudeAgentOptions, tool, create_sdk_mcp_server, AssistantMessage, TextBlock ) import asyncio from typing import Any # Define custom tools with @tool decorator @tool("calculate", "Perform mathematical calculations", {"expression": str}) async def calculate(args: dict[str, Any]) -> dict[str, Any]: try: result = eval(args["expression"], {"__builtins__": {}}) return { "content": [{ "type": "text", "text": f"Result: {result}" }] } except Exception as e: return { "content": [{ "type": "text", "text": f"Error: {str(e)}" }], "is_error": True } @tool("get_time", "Get current time", {}) async def get_time(args: dict[str, Any]) -> dict[str, Any]: from datetime import datetime current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") return { "content": [{ "type": "text", "text": f"Current time: {current_time}" }] } async def main(): # Create SDK MCP server with custom tools my_server = create_sdk_mcp_server( name="utilities", version="1.0.0", tools=[calculate, get_time] ) # Configure options with the server options = ClaudeAgentOptions( mcp_servers={"utils": my_server}, allowed_tools=[ "mcp__utils__calculate", "mcp__utils__get_time" ] ) # Use ClaudeSDKClient for interactive tool usage async with ClaudeSDKClient(options=options) as client: await client.query("What's 123 * 456?") # Process calculation response async for message in client.receive_response(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(f"Calculation: {block.text}") # Follow up with time query await client.query("What time is it now?") async for message in client.receive_response(): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, TextBlock): print(f"Time: {block.text}") asyncio.run(main()) ``` ## See also * [Python SDK guide](/en/api/agent-sdk/python) - Tutorial and examples * [SDK overview](/en/api/agent-sdk/overview) - General SDK concepts * [TypeScript SDK reference](/en/docs/claude-code/typescript-sdk-reference) - TypeScript SDK documentation * [CLI reference](/en/docs/claude-code/cli-reference) - Command-line interface * [Common workflows](/en/docs/claude-code/common-workflows) - Step-by-step guides