Python SDK reference
Every function, class, option, message type, hook type, error and built-in tool schema in the claude-agent-sdk Python package, with notes on the traps.
This is the full reference for the claude-agent-sdk Python package. It is organised so you can jump to what you need: functions first, then the client class, then options, messages, errors, hooks, tool schemas and sandbox settings. Task-focused guides live on the other Agent SDK pages and are linked throughout.
Note: Fragments on this page that use
async fororawaitat top level are illustrative. Wrap them inasync def main(): ...and callasyncio.run(main())to run them.
Installing
System Python on recent Debian, Ubuntu and Homebrew installs refuses pip install with error: externally-managed-environment, so use a virtual environment:
python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk
The quickstart covers uv, Windows PowerShell and API key setup.
query() or ClaudeSDKClient?
query() | ClaudeSDKClient | |
|---|---|---|
| Session | New one per call unless you resume | One session across many exchanges |
| Conversation | One exchange | Many, with shared context |
| Connection | Handled for you | You connect and disconnect |
| Streaming input | Yes | Yes |
| Interrupts | No | Yes |
| Hooks and custom tools | Yes | Yes |
| Follow-ups | Via continue_conversation or resume | Automatic |
| Best for | One-off jobs, batch scripts | Chat UIs, flows where the next step depends on the reply |
Functions
query()
async def query(
*,
prompt: str | AsyncIterable[dict[str, Any]],
options: ClaudeAgentOptions | None = None,
transport: Transport | None = None,
) -> AsyncIterator[Message]
| Parameter | Meaning |
|---|---|
prompt | A string, or an async iterable of user message dicts for streaming input |
options | A ClaudeAgentOptions; None means defaults |
transport | A custom Transport instead of the default subprocess |
Returns an async iterator of Message objects. Each call starts a fresh session unless continue_conversation=True or resume is set. See sessions.
from claude_agent_sdk import query, ClaudeAgentOptions
opts = ClaudeAgentOptions(system_prompt="You are a careful Django reviewer.", permission_mode="plan")
async for msg in query(prompt="Review the views in orders/ for N+1 queries", options=opts):
print(msg)
tool()
Decorator that turns an async function into an in-process MCP tool.
def tool(
name: str,
description: str,
input_schema: type | dict[str, Any],
annotations: ToolAnnotations | None = None,
) -> Callable[[Callable[[Any], Awaitable[dict[str, Any]]]], SdkMcpTool[Any]]
The schema can take three forms:
| Form | Example | Notes |
|---|---|---|
| Type mapping | {"sku": str, "qty": int} | Simplest. All keys required. |
| JSON Schema dict | {"type": "object", "properties": {...}, "required": [...]} | For ranges, enums and optional fields |
TypedDict class | class Args(TypedDict): ... | NotRequired keys are left out of required. On 3.11+ import from typing; on 3.10 import TypedDict and NotRequired from typing_extensions (installed by the SDK there). |
In the mapping and TypedDict forms, Annotated[type, "description"] sets a field description.
from typing import Annotated, Any, NotRequired, TypedDict
from claude_agent_sdk import tool
class StockArgs(TypedDict):
sku: Annotated[str, "Product SKU, e.g. TEA-EB-250"]
warehouse: NotRequired[Annotated[str, "Warehouse code; defaults to MAN"]]
@tool("check_stock", "Return units on hand for a SKU", StockArgs)
async def check_stock(args: dict[str, Any]) -> dict[str, Any]:
wh = args.get("warehouse", "MAN")
units = await inventory.lookup(args["sku"], wh)
return {"content": [{"type": "text", "text": f"{units} units of {args['sku']} at {wh}"}]}
The handler returns a dict with a content list of MCP content blocks. More in custom tools.
ToolAnnotations
Optional hints passed as annotations. It extends the MCP SDK's mcp.types.ToolAnnotations with maxResultSizeChars. You can write hints in camelCase or snake_case (readOnlyHint=True or read_only_hint=True). When reading back, use the spelling your installed mcp package declares (.readOnlyHint on mcp 1.x, .read_only_hint on 2.x); .maxResultSizeChars works on both. A plain mcp.types.ToolAnnotations is accepted too.
| Field | Default | Meaning |
|---|---|---|
title | None | Human-readable title |
readOnlyHint | False | Tool does not change its environment |
destructiveHint | True | May make destructive changes (only meaningful if not read-only) |
idempotentHint | False | Repeating the same call has no further effect (only meaningful if not read-only) |
openWorldHint | True | Talks to external systems; False for a closed domain such as a memory store |
maxResultSizeChars | None | Characters of text result kept inline before Claude Code spills it to a file, up to 500,000. Image results unaffected. Sent in the tool's _meta as anthropic/maxResultSizeChars. |
Snake_case names and the typed maxResultSizeChars need SDK 0.2.140+. Versions 0.1.31 to 0.2.139 re-export the MCP class unchanged; from 0.1.55 you can still pass maxResultSizeChars as a keyword and it is forwarded. Hints are advisory and must not be relied on for security.
create_sdk_mcp_server()
def create_sdk_mcp_server(
name: str,
version: str = "1.0.0",
tools: list[SdkMcpTool[Any]] | None = None,
) -> McpSdkServerConfig
Bundles decorated tools into a server that runs inside your process. Register it in mcp_servers; tool names become mcp__<key>__<tool>.
from claude_agent_sdk import create_sdk_mcp_server, ClaudeAgentOptions
warehouse = create_sdk_mcp_server("warehouse", "1.2.0", tools=[check_stock])
opts = ClaudeAgentOptions(
mcp_servers={"wh": warehouse},
allowed_tools=["mcp__wh__check_stock"],
)
Session helpers
These are synchronous and read transcripts from disk.
| Function | Signature | Returns |
|---|---|---|
list_sessions | (directory=None, limit=None, offset=0, include_worktrees=True) | list[SDKSessionInfo], newest first |
get_session_messages | (session_id, directory=None, limit=None, offset=0) | list[SessionMessage] |
get_session_info | (session_id, directory=None) | SDKSessionInfo or None |
rename_session | (session_id, title, directory=None) | None |
tag_session | (session_id, tag, directory=None) | None |
Notes:
- Omitting
directorysearches every project. include_worktreesincludes sessions from all worktrees whendirectoryis inside a git repo.rename_sessionandtag_sessionappend entries, so repeat calls are safe and the latest wins. Passtag=Noneto clear. Both raiseValueErrorfor a non-UUID ID or an empty title or tag (tags are Unicode-sanitised first) andFileNotFoundErrorif the session is missing.
SDKSessionInfo
| Field | Type | Meaning |
|---|---|---|
session_id | str | Session ID |
summary | str | Display title: custom title, latest prompt, generated summary or first prompt |
last_modified | int | Milliseconds since epoch |
file_size | int | None | Bytes; None for remote stores |
custom_title | str | None | User-set or generated title |
first_prompt | str | None | First meaningful prompt |
git_branch | str | None | Branch at the end of the session |
cwd | str | None | Working directory |
tag | str | None | Tag from tag_session() |
created_at | int | None | Milliseconds since epoch |
SessionMessage
| Field | Type | Meaning |
|---|---|---|
type | "user" or "assistant" | Role |
uuid | str | Message ID |
session_id | str | Session ID |
message | Any | Raw content |
parent_tool_use_id | str | None | For subagent messages, the spawning Agent tool-use ID |
parent_agent_id | str | None | For nested subagents, the parent subagent's ID (SDK 0.2.140+) |
from claude_agent_sdk import list_sessions, tag_session
for s in list_sessions(directory="/srv/repos/storefront", limit=20):
if "refund" in (s.first_prompt or "").lower():
tag_session(s.session_id, "billing")
ClaudeSDKClient
Holds one conversation open across many exchanges. It is what you reach for in chat interfaces.
| Method | What it does |
|---|---|
__init__(options=None, transport=None) | Configure |
connect(prompt=None) | Open the connection, optionally with a first prompt or message stream |
query(prompt, session_id="default") | Send a new message (string or async iterable) |
receive_messages() | Every message, indefinitely |
receive_response() | Messages up to and including the next ResultMessage |
interrupt() | Stop the current turn |
set_permission_mode(mode) | Change permission mode mid-session |
set_model(model) | Change model; None resets to the default |
rewind_files(user_message_id) | Restore files to a checkpoint; needs enable_file_checkpointing=True (see file checkpointing) |
get_mcp_status() | McpStatusResponse for every MCP server |
get_context_usage() | ContextUsageResponse, the data behind /context |
reconnect_mcp_server(name) | Retry a failed or dropped MCP server |
toggle_mcp_server(name, enabled) | Enable or disable a server; disabling removes its tools |
stop_task(task_id) | Stop a background task; a TaskNotificationMessage with status stopped follows |
get_server_info() | Initialisation info, including commands and output styles |
disconnect() | Close |
Use it as an async context manager to connect and disconnect automatically:
from claude_agent_sdk import ClaudeSDKClient, AssistantMessage, TextBlock
async with ClaudeSDKClient() as client:
await client.query("Which module in this repo handles VAT?")
async for m in client.receive_response():
if isinstance(m, AssistantMessage):
print("".join(b.text for b in m.content if isinstance(b, TextBlock)))
await client.query("Does it handle the reverse charge for EU B2B sales?")
async for m in client.receive_response():
...
Warning: Avoid
breakinside these loops: it can cause asyncio clean-up problems. Let the loop finish, or set a flag.
Streaming input with the client
client.query() accepts an async iterable of user message dicts, useful for building content at send time or attaching images. Claude starts replying to the first yielded message straight away, and receive_response() stops at the result for that reply, so put everything Claude needs into one message and pair each query() with its own receive_response() loop.
async def build_prompt():
readings = await sensors.latest()
yield {"type": "user", "message": {"role": "user",
"content": f"Spot anomalies in these readings: {readings}"}}
async with ClaudeSDKClient() as client:
await client.query(build_prompt())
async for m in client.receive_response():
...
Interrupting
async with ClaudeSDKClient(ClaudeAgentOptions(allowed_tools=["Bash"])) as client:
await client.query("Run the full integration suite")
await asyncio.sleep(5)
await client.interrupt()
async for m in client.receive_response(): # drain the interrupted turn
if isinstance(m, ResultMessage):
print(m.terminal_reason) # "aborted_streaming" or "aborted_tools"
await client.query("Just run tests/unit instead")
async for m in client.receive_response():
...
interrupt() does not clear the buffer. The interrupted turn's messages, including its ResultMessage, are still waiting. Drain them before reading the next reply, or you will read the old turn's output by mistake.
Custom permission logic
from claude_agent_sdk.types import PermissionResultAllow, PermissionResultDeny, ToolPermissionContext
async def gatekeeper(tool_name: str, data: dict, ctx: ToolPermissionContext):
path = data.get("file_path", "")
if tool_name in ("Write", "Edit") and path.startswith("/srv/releases/"):
return PermissionResultDeny(message="Release artefacts are read-only", interrupt=True)
if tool_name == "Write" and path.endswith(".env"):
return PermissionResultAllow(updated_input={**data, "file_path": path + ".example"})
return PermissionResultAllow(updated_input=data)
opts = ClaudeAgentOptions(can_use_tool=gatekeeper)
Do not also list gated tools in allowed_tools: allow rules approve calls before can_use_tool is ever consulted. See permissions.
Types: dataclass or TypedDict?
The SDK uses both, and it matters at runtime:
- Dataclasses (
ResultMessage,AgentDefinition,TextBlock, ...) are objects. Use attributes:msg.result. - TypedDicts (
ThinkingConfigEnabled,McpStdioServerConfig,SyncHookJSONOutput, ...) are plain dicts. Use keys:cfg["budget_tokens"].
ClassName(field=value) works for both, but only dataclasses give you attributes.
ClaudeAgentOptions
Tools and permissions
| Option | Type | Default | Meaning |
|---|---|---|---|
tools | list[str] | ToolsPreset | None | None | Built-in tool set. {"type": "preset", "preset": "claude_code"} for the full default set |
allowed_tools | list[str] | [] | Auto-approve these. Does not restrict others, which fall through to the mode and can_use_tool. Naming a task-tracking tool opts the session in to those tools. |
disallowed_tools | list[str] | [] | Bare name removes the tool. Scoped rule such as "Bash(rm *)" denies matching calls in every mode, including bypassPermissions, matched as written |
permission_mode | PermissionMode | None | None | Starting mode |
can_use_tool | CanUseTool | None | None | Callback when the permission flow reaches a prompt. Never called for calls already approved |
permission_prompt_tool_name | str | None | None | MCP tool to use for permission prompts |
sandbox | SandboxSettings | None | None | Programmatic sandbox config (see Sandbox) |
Prompt, model and reasoning
| Option | Type | Default | Meaning |
|---|---|---|---|
system_prompt | str | SystemPromptPreset | SystemPromptCustom | SystemPromptFile | None | None | See system prompt types |
model | str | None | None | Alias or full model ID (see model configuration) |
fallback_model | str | None | None | Used if the primary fails; accepts a comma-separated list |
thinking | ThinkingConfig | None | None | Extended thinking; overrides max_thinking_tokens |
max_thinking_tokens | int | None | None | Deprecated; use thinking |
effort | EffortLevel | None | None | Reasoning depth |
betas | list[SdkBeta] | [] | Beta features |
output_format | dict | None | None | {"type": "json_schema", "schema": {...}} for structured outputs |
task_budget | TaskBudget | None | None | API-side token budget {"total": int}, sent as output_config.task_budget with the task-budgets-2026-03-13 beta header |
Limits
| Option | Type | Default | Meaning |
|---|---|---|---|
max_turns | int | None | None | Cap on tool-use round trips |
max_budget_usd | float | None | None | Stop when the client-side cost estimate reaches this. Counts this call only; restored session totals excluded. See cost tracking |
Sessions
| Option | Type | Default | Meaning |
|---|---|---|---|
continue_conversation | bool | False | Continue the most recent conversation |
resume | str | None | None | Session ID to resume |
session_id | str | None | None | Use this UUID instead of a generated one. Cannot combine with continue_conversation or resume unless fork_session is set |
fork_session | bool | False | When resuming, branch to a new session ID |
resume_session_at | str | None | None | Load only up to and including this message UUID. Use with resume, usually with fork_session (SDK 0.2.137+) |
resume_drops_turn | str | None | None | UUID of the prompt whose turn the truncation discards; the CLI refuses if the discarded range contains other entries (SDK 0.2.137+, Claude Code 2.1.223+) |
enable_file_checkpointing | bool | False | Track edits for rewinding |
session_store | SessionStore | None | None | Mirror transcripts to external storage (see session storage) |
session_store_flush | "batched" or "eager" | "batched" | Batched flushes once per turn or when the buffer fills; eager starts a background flush after every frame |
load_timeout_ms | int | 60000 | Timeout for session_store.load() and list_subkeys() while resuming |
Environment and process
| Option | Type | Default | Meaning |
|---|---|---|---|
cwd | str | Path | None | None | Working directory |
add_dirs | list[str | Path] | [] | Extra directories, passed as --add-dir. With the project source, their skills, commands and subagents load too |
env | dict[str, str] | {} | Merged over the inherited environment. Set CLAUDE_AGENT_SDK_CLIENT_APP to name your app in the User-Agent |
cli_path | str | Path | None | None | Use a specific Claude Code executable |
extra_args | dict[str, str | None] | {} | Extra CLI flags; None for a bare flag |
user | str | None | None | POSIX only: OS user to run the subprocess as. Environment, including HOME, is kept |
max_buffer_size | int | None | None | Max bytes buffered from CLI stdout |
stderr | Callable[[str], None] | None | None | Receives CLI stderr lines |
debug_stderr | Any | sys.stderr | Deprecated and ignored |
Configuration sources and extensions
| Option | Type | Default | Meaning |
|---|---|---|---|
setting_sources | list[SettingSource] | None | None | Which settings files load. [] disables user, project and local. If unset and skills is set, only user and project load |
settings | str | None | None | Settings file path or inline JSON string |
mcp_servers | dict | str | Path | {} | Server configs or a path to a config file |
strict_mcp_config | bool | False | Use only mcp_servers, ignoring .mcp.json, user settings, plugin servers and claude.ai connectors (--strict-mcp-config) |
agents | dict[str, AgentDefinition] | None | None | Programmatic subagents |
skills | list[str] | "all" | None | None | Skills Claude may invoke. Adds Skill to allowed_tools; include "Skill" if you pass tools. Bad names raise ValueError (SDK 0.2.129+) |
plugins | list[SdkPluginConfig] | [] | Local plugins |
hooks | dict[HookEvent, list[HookMatcher]] | None | None | Hook callbacks |
Stream shape
| Option | Type | Default | Meaning |
|---|---|---|---|
include_partial_messages | bool | False | Yield StreamEvents |
include_hook_events | bool | False | Yield hook lifecycle HookEventMessages |
forward_subagent_text | bool | False | Include text and thinking from foreground subagents (SDK 0.2.140+) |
verbatim_prompts | bool | False | Send every user message with client_composed=True, so Claude Code delivers prompts as written. Overrides per-message values while on (SDK 0.2.158+, Claude Code 2.1.248+) |
Timeouts for slow APIs
Pass these through env:
| Variable | Default | Effect |
|---|---|---|
API_TIMEOUT_MS | 600000 | Per-request timeout, main loop and subagents |
CLAUDE_CODE_MAX_RETRIES | 10 (max 15) | Retries, each with its own timeout window. CLAUDE_CODE_RETRY_WATCHDOG=1 retries capacity errors indefinitely and, from v2.1.199, raises the default for other transient errors to 300 and lifts the cap |
CLAUDE_ASYNC_AGENT_STALL_TIMEOUT_MS | CLAUDE_STREAM_IDLE_TIMEOUT_MS + 5 min while the stream watchdog is on, otherwise 600000 | Aborts a subagent that stops producing stream events and reports it to the parent; background subagents are marked failed with any partial result (before v2.1.257, always 600000) |
CLAUDE_ENABLE_STREAM_WATCHDOG | on | Set 0 to disable the watchdog that aborts a response whose body stops streaming |
CLAUDE_STREAM_IDLE_TIMEOUT_MS | 300000 (minimum) | Watchdog idle limit |
With include_partial_messages on, a gateway that holds a response open with keep-alives produces ping StreamEvents; treat them as signs of life (before v2.1.257 they stopped after five minutes).
Settings sources
SettingSource = Literal["user", "project", "local"]
| Value | File |
|---|---|
"user" | ~/.claude/settings.json |
"project" | .claude/settings.json |
"local" | .claude/settings.local.json |
When setting_sources is unset and skills is unset, query() loads all three, like the CLI. Endpoint-managed policy always loads; server-managed settings are fetched when authenticating with an organisation credential on an eligible configuration. Claude Code features in the SDK lists inputs that load regardless.
Precedence, highest first: local, project, user. Programmatic options (agents, allowed_tools, settings) beat all three; managed policy beats programmatic options.
CLAUDE.md loads with the project source. setting_sources=[] is the right choice for SDK-only apps that define everything in code.
Note: In Python SDK 0.1.59 and earlier, an empty list behaved like
Noneand did not disable anything. Upgrade if you rely on[].
System prompt types
| Type | Shape | Notes |
|---|---|---|
| plain string | "You are..." | Custom prompt |
SystemPromptPreset | {"type": "preset", "preset": "claude_code", "append"?: str, "exclude_dynamic_sections"?: bool, "snapshot"?: bool} | Claude Code's prompt plus your text |
SystemPromptCustom | {"type": "custom", "prompt": str, "snapshot"?: bool} | Same as a string but can set snapshot (SDK 0.2.153+) |
SystemPromptFile | {"type": "file", "path": str} | Maps to --system-prompt-file |
exclude_dynamic_sectionsmoves per-user context such as the auto memory location into the first user message so the prompt caches across users.snapshot: Falserebuilds the prompt every request instead of reusing the one recorded on the first request (SDK 0.2.153+).- String and
customprompts travel as a command-line argument. On Linux a single argument over roughly 128 KB fails withArgument list too long; on Windows the whole command line is capped near 32 KB. Use the file form for big prompts.
Details in modifying system prompts.
AgentDefinition
@dataclass
class AgentDefinition:
description: str
prompt: str
tools: list[str] | None = None
disallowedTools: list[str] | None = None
model: str | None = None
skills: list[str] | None = None
memory: Literal["user", "project", "local"] | None = None
mcpServers: list[str | dict[str, Any]] | None = None
initialPrompt: str | None = None
maxTurns: int | None = None
background: bool | None = None
effort: EffortLevel | int | None = None
permissionMode: PermissionMode | None = None
| Field | Meaning |
|---|---|
description | When to use this agent (required) |
prompt | Its system prompt (required) |
tools | Allowed tools; omit to inherit all subagent tools |
disallowedTools | Tools to remove; accepts mcp__server, mcp__server__*, mcp__* |
model | "sonnet", "opus", "haiku", "inherit" or a full ID |
skills | Skills preloaded at start |
memory | "user", "project" or "local" |
mcpServers | Server names or inline {name: config} dicts |
initialPrompt | First user turn when this agent is the main thread agent |
maxTurns | Turn cap |
background | Always run in the background |
effort | Named level or integer |
permissionMode | Mode inside this agent, subject to inheritance rules |
Warning: These field names are camelCase to match the wire format, unlike the snake_case
ClaudeAgentOptions. Passingmax_turns=raises aTypeError. There is noomitClaudeMdfield in Python; that is TypeScript-only. See subagents.
Enumerations
PermissionMode = Literal["default", "acceptEdits", "plan", "dontAsk", "bypassPermissions", "auto"]
EffortLevel = Literal["low", "medium", "high", "xhigh", "max"] # xhigh falls back to high where unsupported
SdkBeta = Literal["context-1m-2025-08-07"]
Warning: On the Claude API the
context-1m-2025-08-07beta is retired for Sonnet 4.5 and Sonnet 4; passing it with those models makes over-200K requests fail. For a 1M window, use a model that has it by default, such asclaude-sonnet-5-5orclaude-opus-5-5, or append[1m]to a model that offers a 1M variant, such asclaude-opus-4-6[1m].
ThinkingConfig
Three TypedDict variants (plain dicts at runtime):
| Variant | Keys | Effect |
|---|---|---|
ThinkingConfigAdaptive | type="adaptive", display? | Claude decides when to think |
ThinkingConfigEnabled | type="enabled", budget_tokens, display? | Thinking with a token budget |
ThinkingConfigDisabled | type="disabled" | Off |
display is "summarized" or "omitted". On Opus 4.7 and later the API defaults to "omitted", so set "summarized" to get text in ThinkingBlocks. Some providers (Amazon Bedrock, Google Cloud's Agent Platform) do not receive your display, so Opus 4.7+ thinking blocks come back empty there regardless.
ClaudeAgentOptions(thinking={"type": "enabled", "budget_tokens": 16000, "display": "summarized"})
Other small types
| Type | Shape |
|---|---|
ToolsPreset | {"type": "preset", "preset": "claude_code"} |
TaskBudget | {"total": int} |
OutputFormat | {"type": "json_schema", "schema": {...}} |
SdkPluginConfig | {"type": "local", "path": str} |
Permission callback types
CanUseTool = Callable[[str, dict[str, Any], ToolPermissionContext], Awaitable[PermissionResult]]
PermissionResult = PermissionResultAllow | PermissionResultDeny
The callback stands in for the interactive prompt and runs only when the evaluation flow reaches one. For a check on every call, use a PreToolUse hook.
ToolPermissionContext
| Field | Meaning |
|---|---|
signal | Reserved |
suggestions | list[PermissionUpdate] from the CLI. Bash prompts include one with destination localSettings; returning it in updated_permissions persists the rule to .claude/settings.local.json |
tool_use_id | The call this prompt is for; always set |
agent_id | Subagent ID, or None for the main agent |
blocked_path | Path that triggered the prompt, when relevant |
decision_reason | Why the prompt happened; carries a PreToolUse hook's reason when it returned ask |
title | Full prompt sentence, e.g. Claude wants to read foo.txt |
display_name | Short action label for buttons, e.g. Read file |
description | Subtitle for your UI |
PermissionResultAllow: behavior="allow", updated_input: dict | None, updated_permissions: list[PermissionUpdate] | None.
PermissionResultDeny: behavior="deny", message: str = "", interrupt: bool = False (stop the whole turn).
PermissionUpdate
| Field | Values |
|---|---|
type | addRules, replaceRules, removeRules, setMode, addDirectories, removeDirectories |
rules | list[PermissionRuleValue] (each tool_name, optional rule_content) |
behavior | allow, deny, ask |
mode | A PermissionMode for setMode |
directories | For directory operations |
destination | userSettings, projectSettings, localSettings, session |
MCP configuration types
McpServerConfig = McpStdioServerConfig | McpSSEServerConfig | McpHttpServerConfig | McpSdkServerConfig
| Type | Keys |
|---|---|
McpStdioServerConfig | type?: "stdio" (optional for backwards compatibility), command, args?, env? |
McpSSEServerConfig | type: "sse", url, headers? |
McpHttpServerConfig | type: "http", url, headers? |
McpSdkServerConfig | type: "sdk", name, instance |
Status types
get_mcp_status() returns McpStatusResponse: {"mcpServers": list[McpServerStatus]}.
McpServerStatus key | Meaning |
|---|---|
name | Server name |
status | connected, failed, needs-auth, pending or disabled |
serverInfo | {"name", "version"} |
error | Failure reason |
config | McpServerStatusConfig: any transport config, plus McpSdkServerConfigStatus (type="sdk", name, no instance) and McpClaudeAIProxyServerConfig (type="claudeai-proxy", url, id) |
scope | Configuration scope |
tools | Each with name, description, annotations |
ContextUsageResponse
What get_context_usage() returns: the same payload as /context, including display fields such as color and gridRows. Building it makes several token-counting API requests that do not appear in the stream (not billed on the Anthropic API).
| Key | Meaning |
|---|---|
categories | List of {name, tokens, color, isDeferred?} |
totalTokens | Current context use |
maxTokens / rawMaxTokens | The window measured against (model window, or the lower auto-compact window); both carry the same value |
percentage | Use as a percentage |
model | Model |
isAutoCompactEnabled, autoCompactThreshold? | Auto-compact state |
memoryFiles, mcpTools, agents, gridRows | Breakdown lists |
slashCommands?, skills?, messageBreakdown? | Further breakdowns |
apiUsage? | Usage from the latest API response, not a session total |
deferredBuiltinTools, systemTools and systemPromptSections are declared but left unset.
Messages
Message = UserMessage | AssistantMessage | SystemMessage | ResultMessage | StreamEvent | RateLimitEvent | ConversationResetMessage
UserMessage
| Field | Meaning |
|---|---|
content | String or content blocks |
uuid | Message ID (needs replay-user-messages for checkpoint UUIDs) |
parent_tool_use_id | Set when the message is a tool result |
tool_use_result | The tool's structured output (see Built-in tool schemas) |
origin | Provenance on injected turns such as task notifications and peer messages (SDK 0.2.137+) |
For results from an external MCP server that contain resource_link blocks, tool_use_result has a resourceLinks list (SDK 0.2.150+, Claude Code 2.1.257+). Claude sees each link as a line of text; read resourceLinks to render them. Absent when there are no links and on subagent results; capped at 50 links or 64 KiB of JSON. In-process tool() tools never produce it, because their links are flattened to text.
AssistantMessage
| Field | Meaning |
|---|---|
content | List of content blocks |
model | Model that responded |
parent_tool_use_id | Set inside a subagent |
error | AssistantMessageError if the response failed |
usage | Per-message usage; output_tokens is a placeholder |
message_id | API message ID, shared by messages from the same response |
stop_reason | e.g. end_turn, tool_use |
session_id, uuid | Identifiers |
AssistantMessageError lists authentication_failed, billing_error, rate_limit, invalid_request, server_error and unknown, but the CLI can emit others such as max_output_tokens. Treat unlisted values like unknown.
SystemMessage
subtype: str and data: dict. Subtypes without their own class arrive as this. With CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS=1, read data["state"] on session_state_changed messages. Those can arrive after a result, so use receive_messages() rather than receive_response() to see them.
ResultMessage
The final message of a query() (or of each turn in streaming input mode).
| Field | Meaning |
|---|---|
subtype | success, error_during_execution, error_max_turns, error_max_budget_usd, error_max_structured_output_retries |
duration_ms, duration_api_ms | Wall and API time |
is_error | Always True for error_*; True on success when the final request failed |
num_turns | Turns used |
session_id | Session |
stop_reason | API stop reason |
total_cost_usd | Client-side estimate, optional |
usage | Main loop only, per turn in streaming mode |
result | Final text on success, None otherwise; may hold an API error string when is_error |
structured_output | Parsed output when output_format is set |
model_usage | Per-model usage including subagents and internal calls |
permission_denials | Calls that were denied |
deferred_tool_use | Set when a hook deferred a call |
errors | Loop-level errors on error_* |
api_error_status | HTTP status of a terminating API error, on success only |
uuid | ID |
terminal_reason | completed, max_turns, api_error, aborted_streaming, aborted_tools, etc. None on old CLIs, local command results and synthesised fatal errors |
origin | Origin of the triggering prompt; None or {"kind": "human"} for your own (SDK 0.2.137+) |
Fields that do not apply to a subtype are None.
usage keys: input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens. Subagents are excluded, so prefer model_usage.
model_usage covers the main loop, subagents, compaction and Workflow agents; helper calls outside that pipeline (the permission classifier, token counting) are excluded. It is cumulative across turns in streaming mode and includes restored totals on resume. Each value is a ModelUsage (from claude_agent_sdk.types import ModelUsage) with camelCase keys:
| Key | Meaning |
|---|---|
inputTokens, outputTokens | Tokens |
cacheReadInputTokens, cacheCreationInputTokens | Cache tokens |
webSearchRequests | Web searches |
thinkingTokens | Already included in outputTokens; not declared, read with .get() (SDK 0.2.150+) |
costUSD | Estimate |
contextWindow, maxOutputTokens | Model limits |
canonicalModel | ID used for pricing; not always present |
provider | firstParty, bedrock, vertex, foundry, anthropicAws, mantle or gateway; not always present |
costBasis | list, managed or unknown; read with .get() (Claude Code 2.1.246+) |
StreamEvent
Only with include_partial_messages=True. Import from claude_agent_sdk.types. Fields: uuid, session_id, event (the raw API stream event) and parent_tool_use_id, which is always None because stream events are main-session only.
RateLimitEvent and RateLimitInfo
Emitted when rate-limit status changes. rate_limit_info carries:
| Field | Meaning |
|---|---|
status | allowed, allowed_warning (getting close) or rejected |
resets_at | Unix time the window resets |
rate_limit_type | five_hour, seven_day, seven_day_opus, seven_day_sonnet, overage |
utilization | 0.0 to 1.0 |
overage_status, overage_resets_at, overage_disabled_reason | Pay-as-you-go state |
raw | The full dict from the CLI |
ConversationResetMessage
Emitted when the conversation is replaced without disconnecting, for example after /clear (SDK 0.2.137+). Fields: new_conversation_id (opaque, not the next session_id), uuid, and session_id (the session that was reset). Messages afterwards carry a new session_id.
Background task messages
A background task is a backgrounded Bash command, a Monitor watch, a subagent or a remote agent. (Nothing to do with the old Task tool name.)
| Message | Key fields |
|---|---|
TaskStartedMessage | task_id, description, tool_use_id, task_type (local_bash, local_agent, remote_agent) |
TaskProgressMessage | task_id, description, usage: TaskUsage, last_tool_name |
TaskNotificationMessage | task_id, status (completed, failed, stopped), output_file, summary, usage |
All three subclass SystemMessage and include uuid and session_id. TaskUsage is {"total_tokens", "tool_uses", "duration_ms"}.
When the CLI backgrounds a long MCP tool call, the tool result holds a placeholder and the real result comes in the completed notification, with a resource_links list in message.data (SDK 0.2.150+, Claude Code 2.1.257+). Match it to the call by tool_use_id.
Content blocks
ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock | ServerToolUseBlock | ServerToolResultBlock
| Block | Fields |
|---|---|
TextBlock | text |
ThinkingBlock | thinking, signature |
ToolUseBlock | id, name, input |
ToolResultBlock | tool_use_id, content, is_error |
Errors
| Exception | Parent | When |
|---|---|---|
ClaudeSDKError | Exception | Base for everything |
CLIConnectionError | ClaudeSDKError | Could not connect to Claude Code |
CLINotFoundError | CLIConnectionError | Claude Code not installed or not found; has cli_path |
ProcessError | ClaudeSDKError | The process failed; has exit_code, stderr |
ResultError | ProcessError | The run ended with an error result (SDK 0.2.140+) |
CLIJSONDecodeError | ClaudeSDKError | Unparseable output; has line, original_error |
ResultError attributes: subtype, errors (list, possibly empty), result, api_error_status, terminal_reason, session_id, data (raw payload). Check terminal_reason before subtype: a failed final request such as an API error comes through with subtype="success" and terminal_reason="api_error", while your own limits produce error_* subtypes.
from claude_agent_sdk import query, CLINotFoundError, ResultError, ProcessError, CLIJSONDecodeError
try:
async for m in query(prompt="Tidy the README"):
...
except CLINotFoundError:
print("Claude Code missing: pip install --force-reinstall claude-agent-sdk")
except ResultError as e: # before ProcessError
print("Run failed:", e.terminal_reason or e.subtype, e)
except ProcessError as e:
print("Process exited", e.exit_code)
except CLIJSONDecodeError as e:
print("Bad output line:", e.line)
The troubleshooting page maps each error message to a fix.
Hook types
Full guide: hooks in the SDK.
HookEvent = Literal["PreToolUse", "PostToolUse", "PostToolUseFailure", "UserPromptSubmit",
"Stop", "SubagentStop", "PreCompact", "Notification",
"SubagentStart", "PermissionRequest"]
HookCallback = Callable[[HookInput, str | None, HookContext], Awaitable[HookJSONOutput]]
TypeScript supports many more events. SessionStart and SessionEnd are only available to Python as shell hooks in settings files.
HookMatcher: matcher: str | None (tool name or pattern such as "Write|Edit"), hooks: list[HookCallback], timeout: float | None in seconds (defaults per event: 600 for most, 30 for UserPromptSubmit).
HookContext: {"signal": None}, reserved.
Hook inputs
Every input includes session_id, transcript_path, cwd and optionally permission_mode, plus hook_event_name.
| Input type | Extra fields |
|---|---|
PreToolUseHookInput | tool_name, tool_input, tool_use_id, agent_id?, agent_type? |
PostToolUseHookInput | tool_name, tool_input, tool_response, tool_use_id, agent_id?, agent_type? |
PostToolUseFailureHookInput | tool_name, tool_input, tool_use_id, error, is_interrupt?, agent_id?, agent_type? |
UserPromptSubmitHookInput | prompt |
StopHookInput | stop_hook_active |
SubagentStopHookInput | stop_hook_active, agent_id, agent_transcript_path, agent_type |
PreCompactHookInput | trigger (manual or auto), custom_instructions |
NotificationHookInput | message, title?, notification_type |
SubagentStartHookInput | agent_id, agent_type |
PermissionRequestHookInput | tool_name, tool_input, permission_suggestions?, agent_id?, agent_type? |
is_interrupt is true when the failure reached Claude Code as an abort rather than a tool-reported error. Cancelling a tool with interrupt() does not fire this hook at all.
Hook outputs
HookJSONOutput = AsyncHookJSONOutput | SyncHookJSONOutput
SyncHookJSONOutput
| Key | Meaning |
|---|---|
continue_ | Whether to carry on (default true); sent as continue |
suppressOutput | Hide stdout from the transcript |
stopReason | Message when continue_ is false |
decision | "block" |
systemMessage | Warning for the user |
reason | Feedback for Claude |
hookSpecificOutput | Event-specific fields below |
hookSpecificOutput by event (always include hookEventName):
| Event | Fields |
|---|---|
PreToolUse | permissionDecision (allow, deny, ask, defer), permissionDecisionReason, updatedInput, additionalContext |
PostToolUse | additionalContext, updatedToolOutput, updatedMCPToolOutput (MCP only; prefer updatedToolOutput) |
PostToolUseFailure | additionalContext |
UserPromptSubmit | additionalContext |
Notification | additionalContext |
SubagentStart | additionalContext |
PermissionRequest | decision (dict) |
AsyncHookJSONOutput: async_: True (sent as async) and optional asyncTimeout in milliseconds.
async def block_force_push(data, tool_use_id, ctx):
cmd = data["tool_input"].get("command", "")
if "git push" in cmd and ("--force" in cmd or " -f" in cmd):
return {"hookSpecificOutput": {"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Force pushes are not allowed from agents"}}
return {}
opts = ClaudeAgentOptions(hooks={"PreToolUse": [HookMatcher(matcher="Bash", hooks=[block_force_push], timeout=60)]})
Built-in tool schemas
The Python SDK does not export types for these, but this is the shape of tool_use input and of UserMessage.tool_use_result output for each tool. Keys appear exactly as Claude Code emits them; optional keys are omitted when they do not apply.
Agent
Name Agent (Task accepted as an alias; the init message's tools list still says Task).
| Input | Meaning |
|---|---|
description | 3 to 5 word summary |
prompt | The task |
subagent_type | Which agent |
model | sonnet, opus, haiku or fable |
effort | low to max |
run_in_background | Defaults to background; False to wait |
name | Name for the spawned agent |
isolation | worktree or remote |
team_name, mode | Deprecated and ignored |
Output is discriminated on status:
completed:agentId,agentType,content(text blocks with optionalcitations),resolvedModel(Claude Code 2.1.174+),modelsUsed(only set when the model was swapped mid-run),totalToolUseCount,totalDurationMs,totalTokensandusage(both from the final API request, not the whole run),toolStats(readCount,searchCount,bashCount,editFileCount,linesAdded,linesRemoved,otherToolCount,frameCount),prompt, andworktreePath/worktreeBranchif a worktree was kept.usageincludesoutput_tokens_details.thinking_tokens(SDK 0.2.136+) andfallback_credit(SDK 0.2.162+).async_launched:isAsync,agentId,description,resolvedModel(at the backgrounding point),modelsUsed,prompt,outputFile,canReadOutputFile.remote_launched:taskId,sessionUrl,description,prompt,outputFile.
AskUserQuestion
Input: questions (1 to 4), each with question, header (up to 12 characters), options (2 to 4, each label of 1 to 5 words, description, optional preview) and multiSelect. Also answers (filled by the permission system; multi-select answers are comma-joined), annotations (per question, preview and notes) and metadata.
Output: questions, answers (question text to answer), response (a free-form reply instead of answers; Claude then sees The user responded: ...), annotations, and afkTimeoutMs when the dialog auto-resolved after inactivity. See user input.
Bash
Input: command, timeout (ms; foreground capped at 600000 by default; with run_in_background on Claude Code 2.1.285+ it is the background limit, default 1800000, capped at 7200000 unless raised), description, run_in_background.
Output: stdout (stdout and stderr interleaved), stderr (notices from the tool itself), interrupted, isImage, backgroundTaskId.
Monitor
Input: exactly one of command (each stdout line is an event) or ws ({"url", "protocols"}; each text frame is an event; Claude Code 2.1.195+), plus description and timeout_ms (default 300000, max 3600000, effective at most 1800000). Commands follow Bash permissions; WebSocket watches prompt separately.
Output: taskId, timeoutMs, persistent (false: every watch has a deadline).
Edit
Input: file_path, old_string, new_string, replace_all.
Output: filePath, oldString, newString, originalFile, structuredPatch (hunks with oldStart, oldLines, newStart, newLines, lines), userModified, replaceAll, gitDiff (filename, status modified or added, additions, deletions, changes, patch, repository).
Read
Input: file_path, offset, limit.
Output varies by type:
type | file contents |
|---|---|
text | filePath, content, numLines, startLine, totalLines, truncatedByTokenCap |
image | base64, type (jpeg, png, gif, webp), originalSize, dimensions |
notebook | filePath, cells |
pdf | filePath, base64, originalSize |
parts | filePath, originalSize, count, outputDir; plus top-level firstPage |
file_unchanged | filePath; plus source: "seeded" when the earlier copy came from a CLAUDE.md or memory file |
Write
Input: file_path, content.
Output: type (create or update), filePath, content, structuredPatch (empty for new files or skipped diffs), originalFile (None for new or very large files), gitDiff, userModified.
Glob
Input: pattern, path.
Output: durationMs, numFiles, filenames, truncated (100-file limit), totalMatches and countIsComplete (Claude Code 2.1.191+).
Grep
Input: pattern, path, glob, type, output_mode (content, files_with_matches, count), -i, -n, -B, -A, -C, context, -o, head_limit, offset, multiline.
Output: mode, numFiles (0 in content mode), filenames, content, numLines, numMatches, totalFiles (2.1.208+), totalLines (2.1.210+), appliedLimit, appliedOffset.
NotebookEdit
Input: notebook_path, cell_id, new_source, cell_type (code or markdown), edit_mode (replace, insert, delete).
Output: new_source, old_source, cell_id, cell_type, language, edit_mode, error, notebook_path, original_file, updated_file.
WebFetch and WebSearch
| Tool | Input | Output |
|---|---|---|
WebFetch | url, prompt | bytes, code, codeText, result, durationMs, url |
WebSearch | query, allowed_domains, blocked_domains | query, results, durationSeconds |
Task list tools
Available by default only on Claude 3.x, Opus 4 to 4.7, Sonnet 4 to 4.6 and Haiku 4.5 (Claude Code 2.1.268+). Elsewhere, opt in as described in todo tracking. CLAUDE_CODE_ENABLE_TASKS=0 swaps the four Task tools for TodoWrite.
| Tool | Input | Output |
|---|---|---|
TodoWrite | todos: list of content, status (pending, in_progress, completed), activeForm | oldTodos, newTodos |
TaskCreate | subject, description, activeForm, metadata | task: {id, subject} |
TaskUpdate | taskId, status (adds deleted), subject, description, activeForm, addBlocks, addBlockedBy, owner, metadata | success, taskId, updatedFields, error, statusChange: {from, to} |
TaskGet | taskId | task: id, subject, description, status, blocks, blockedBy, or None |
TaskList | none | tasks: id, subject, status, owner, blockedBy |
Background task tools
TaskStop(aliasesKillShell,KillBash). Input:task_id(or deprecatedshell_id). Output:message,task_id,task_type,command.TaskOutputwas removed in Claude Code 2.1.277 (as was itsBashOutputalias). Claude reads a background task's output file withRead. Deny rules naming either are silently ignored.
ExitPlanMode
Input: plan. Output: plan, isAgent, filePath, hasTaskTool, planWasEdited, awaitingLeaderApproval, requestId.
MCP resources
| Tool | Input | Output |
|---|---|---|
ListMcpResourcesTool | server (optional) | A list (not a dict) of uri, name, mimeType, description, server |
ReadMcpResourceTool | server, uri | contents (each uri, mimeType, text, blobSavedTo), error |
A complete chat loop
A small terminal chat that keeps context across turns and supports new and interrupt:
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions, AssistantMessage, TextBlock
async def chat():
client = ClaudeSDKClient(ClaudeAgentOptions(allowed_tools=["Read", "Grep"], permission_mode="default"))
await client.connect()
try:
while True:
line = await asyncio.to_thread(input, "\nyou> ")
if line == "exit":
break
if line == "new":
await client.disconnect()
await client.connect()
print("(fresh session)")
continue
if line == "interrupt":
await client.interrupt()
continue
await client.query(line)
async for m in client.receive_response():
if isinstance(m, AssistantMessage):
print("".join(b.text for b in m.content if isinstance(b, TextBlock)), end="")
finally:
await client.disconnect()
asyncio.run(chat())
Sandbox
SandboxSettings
| Key | Default | Meaning |
|---|---|---|
enabled | False | Sandbox Bash commands |
autoAllowBashIfSandboxed | True | Auto-approve Bash when sandboxed |
excludedCommands | [] | Commands that always run outside the sandbox, such as ["docker *"], with no model involvement |
allowUnsandboxedCommands | True | Let the model set dangerouslyDisableSandbox on a call, which then goes through normal permissions |
network | None | SandboxNetworkConfig |
ignoreViolations | None | {"file": [...], "network": [...]} patterns to ignore |
enableWeakerNestedSandbox | False | Weaker nested sandbox for compatibility |
The sandbox needs platform support, and on Linux bubblewrap and socat. If it cannot start, Python's default is to run unsandboxed with a warning on stderr (TypeScript defaults to failing). Add "failIfUnavailable": True to stop instead: it is not declared on the TypedDict but is forwarded and honoured, and the run ends in an error_during_execution result with the reason in errors.
opts = ClaudeAgentOptions(sandbox={
"enabled": True,
"failIfUnavailable": True,
"network": {"allowedDomains": ["pypi.org", "files.pythonhosted.org"], "allowLocalBinding": True},
})
SandboxNetworkConfig
Applies to sandboxed Bash only, not WebFetch (which uses permission rules).
| Key | Meaning |
|---|---|
allowedDomains | Domains sandboxed processes may reach |
deniedDomains | Blocked domains; beats allowedDomains |
allowManagedDomainsOnly | Managed settings only; no effect from SDK options |
allowUnixSockets | macOS only: socket paths allowed |
allowAllUnixSockets | Allow every Unix socket |
allowLocalBinding | Allow binding local ports, for dev servers |
allowMachLookup | macOS only: XPC/Mach service names, trailing wildcard allowed |
httpProxyPort, socksProxyPort | Proxy ports |
Warning: Allowing
/var/run/docker.sockeffectively hands over the whole host through the Docker API. The built-in proxy also filters by requested hostname without inspecting TLS, so domain fronting can get round it; see secure deployment.
Handling unsandboxed requests
When allowUnsandboxedCommands is on and the model sets dangerouslyDisableSandbox: True, the call goes to can_use_tool, so you can decide. Note that can_use_tool needs streaming input, and a PreToolUse hook that simply returns {"continue_": True} keeps the stream open for it:
async def gate(tool, data, ctx):
if tool == "Bash" and data.get("dangerouslyDisableSandbox"):
if data.get("command", "").startswith("docker compose "):
return PermissionResultAllow()
return PermissionResultDeny(message="Unsandboxed execution not permitted")
return PermissionResultAllow()
async def keep_open(data, tid, ctx):
return {"continue_": True}
async def prompt():
yield {"type": "user", "message": {"role": "user", "content": "Bring up the dev stack"}}
async for m in query(prompt=prompt(), options=ClaudeAgentOptions(
sandbox={"enabled": True, "allowUnsandboxedCommands": True},
permission_mode="default",
can_use_tool=gate,
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[keep_open])]})):
...
Warning: With
bypassPermissionsandallowUnsandboxedCommandstogether, the model can leave the sandbox without asking anyone, apart from the actions no mode auto-approves. Do not combine them.