claude-agent-sdk
Claude Agent SDK
The Agent SDK provides Claude with autonomous tool execution. Unlike the Messages API where you implement the tool loop, agents execute tools automatically.
Quick Start
Python:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
async def main():
async for message in query(
prompt="Find and fix the bug in auth.py",
options=ClaudeAgentOptions(allowed_tools=["Read", "Edit", "Bash"])
):
print(message)
asyncio.run(main())
TypeScript:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Find and fix the bug in auth.py",
options: { allowedTools: ["Read", "Edit", "Bash"] }
})) {
console.log(message);
}
When to Use Agent SDK vs Messages API
| Scenario | Use |
|---|---|
| Autonomous multi-step tasks | Agent SDK |
| File operations, code editing | Agent SDK |
| Simple Q&A, text generation | Messages API |
| Custom tool execution control | Messages API |
| Browser automation, web tasks | Agent SDK |
Core APIs
query() - One-off Tasks
Creates new session each time. Best for independent tasks.
async for message in query(
prompt="Your task here",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Write", "Edit", "Bash"],
permission_mode="acceptEdits" # Auto-approve edits
)
):
if hasattr(message, 'result'):
print(message.result)
ClaudeSDKClient - Conversations
Maintains context across exchanges. Use for interactive applications.
async with ClaudeSDKClient(options=options) as client:
await client.connect() # Establish connection
await client.query("Read the auth module")
async for msg in client.receive_messages(): # Stream all messages
print(msg)
await client.query("Now refactor it") # Remembers context
async for msg in client.receive_messages():
print(msg)
await client.interrupt() # Cancel current operation
await client.rewind_files() # Restore file checkpoints
status = await client.get_mcp_status() # Check MCP server status
await client.disconnect() # Clean up
Built-in Tools
| Tool | Purpose |
|---|---|
Read |
Read files in working directory |
Write |
Create new files |
Edit |
Make precise edits to existing files |
Bash |
Run terminal commands and scripts |
BashOutput |
Retrieve output from background bash shells |
KillBash |
Kill running background bash shells |
Glob |
Find files by pattern (**/*.ts) |
Grep |
Search file contents with regex |
NotebookEdit |
Edit Jupyter notebook cells |
WebSearch |
Search the web |
WebFetch |
Fetch and parse web pages |
AskUserQuestion |
Request user clarification |
TodoWrite |
Create/manage structured task lists |
Task |
Spawn subagents |
ExitPlanMode |
Exit planning mode |
ListMcpResources |
List available MCP resources |
ReadMcpResource |
Read content from MCP resources |
Custom Tools
Custom tools use in-process MCP servers. Tool names follow: mcp__{server}__{tool}
Python:
from claude_agent_sdk import tool, create_sdk_mcp_server
@tool("get_weather", "Get temperature for coordinates",
{"latitude": float, "longitude": float})
async def get_weather(args):
# Fetch weather data
return {"content": [{"type": "text", "text": f"72°F"}]}
server = create_sdk_mcp_server(
name="weather",
version="1.0.0",
tools=[get_weather]
)
options = ClaudeAgentOptions(
mcp_servers={"weather": server},
allowed_tools=["mcp__weather__get_weather"]
)
TypeScript:
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const server = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [
tool("get_weather", "Get temperature", {
latitude: z.number(),
longitude: z.number()
}, async (args) => ({
content: [{ type: "text", text: "72°F" }]
}))
]
});
Important: Custom MCP tools require streaming input mode (async generator).
See custom-tools.md for complete patterns.
Hooks System
Hooks intercept agent execution at lifecycle points.
| Hook | When | Python | TypeScript |
|---|---|---|---|
PreToolUse |
Before tool executes | Yes | Yes |
PostToolUse |
After tool succeeds | Yes | Yes |
PostToolUseFailure |
After tool fails | Partial* | Yes |
UserPromptSubmit |
User submits prompt | Yes | Yes |
Stop |
Agent stops | Yes | Yes |
SubagentStop |
Subagent completes | Yes | Yes |
PreCompact |
Before context compaction | Yes | Yes |
Notification |
Status messages | No | Yes |
SessionStart |
Session begins | No | Yes |
SessionEnd |
Session ends | No | Yes |
SubagentStart |
Subagent begins | No | Yes |
PermissionRequest |
Permission prompt shown | No | Yes |
*PostToolUseFailure: Present in Python SDK types.py (HookEvent Literal) but not yet in the official HookInput union type or documentation. May work in recent SDK versions (>= ~0.1.26). Test with your specific version before relying on it.
Permission Decisions
Hooks can return permission decisions (checked in order):
- deny - Block immediately
- ask - Require user approval
- allow - Auto-approve
Example: Block .env modifications
async def protect_env(input_data, tool_use_id, context):
file_path = input_data['tool_input'].get('file_path', '')
if file_path.endswith('.env'):
return {
'hookSpecificOutput': {
'hookEventName': 'PreToolUse',
'permissionDecision': 'deny',
'permissionDecisionReason': 'Cannot modify .env files'
}
}
return {}
options = ClaudeAgentOptions(
hooks={'PreToolUse': [HookMatcher(matcher='Write|Edit', hooks=[protect_env])]}
)
See hooks-reference.md for all hooks and patterns.
Subagents
Spawn specialized agents for focused subtasks with parallelization.
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Task"],
agents={
"code-reviewer": AgentDefinition(
description="Expert code reviewer for quality and security",
prompt="Analyze code quality and suggest improvements",
tools=["Read", "Glob", "Grep"],
model="sonnet" # "sonnet" | "opus" | "haiku" | "inherit"
),
"test-writer": AgentDefinition(
description="Test generation specialist",
prompt="Write comprehensive unit tests",
tools=["Read", "Write", "Bash"],
model="sonnet"
)
}
)
See multi-agent.md for orchestration patterns.
Session Management
Resume sessions to maintain context across queries:
session_id = None
# First query: capture session ID
async for message in query(prompt="Read auth.py", options=options):
if hasattr(message, 'subtype') and message.subtype == 'init':
session_id = message.session_id
# Resume with full context
async for message in query(
prompt="Find all callers", # "auth.py" understood from context
options=ClaudeAgentOptions(resume=session_id)
):
print(message)
Configuration Options
ClaudeAgentOptions(
allowed_tools=["Read", "Edit"], # Whitelist tools
permission_mode="acceptEdits", # default|acceptEdits|plan|bypassPermissions
mcp_servers={"name": server}, # External MCP servers
cwd="/path/to/project", # Working directory
max_turns=10, # Prevent infinite loops
resume="session-id", # Resume session
hooks={...}, # Lifecycle hooks
agents={...}, # Subagent definitions
can_use_tool=permission_handler, # Programmatic permission callback (requires streaming input)
model="claude-sonnet-4-5-20250929", # Model selection (or "claude-sonnet-4-5", "claude-opus-4-5", "claude-haiku-4-5")
fallback_model="claude-haiku-4-5", # Fallback if primary model unavailable
system_prompt="Custom instructions", # Override system prompt
max_budget_usd=1.0, # Maximum spend limit
max_thinking_tokens=10000, # Extended thinking budget
enable_file_checkpointing=True, # Enable file state snapshots for rewind
output_format={"type": "json", "schema": {...}}, # Structured JSON output
plugins=[...], # Plugin extensions
sandbox=True, # Run in sandboxed environment
setting_sources=[...], # Custom setting sources
env={"API_KEY": "..."} # Environment variables
)
Security Best Practices
- Deny-by-default: Only whitelist necessary tools
- Confirm sensitive actions: Use hooks for git push, infrastructure changes
- Block dangerous commands: rm -rf, sudo, etc.
- Protect secrets: Never expose credentials in agent-visible context
- Use short-lived tokens: Rotate credentials aggressively
- Log all tool invocations: Audit trail with timestamps
Error Handling
from claude_agent_sdk import (
CLINotFoundError, # Claude Code not installed
CLIConnectionError, # Connection issues
ProcessError, # Process failed
CLIJSONDecodeError # JSON parsing issues
)
try:
async for message in query(prompt="Task", options=options):
pass
except ProcessError as e:
print(f"Failed with exit code: {e.exit_code}")
Message Types
from claude_agent_sdk import ResultMessage, AssistantMessage
# Stream yields different message types
async for message in query(prompt="Task", options=options):
if isinstance(message, AssistantMessage):
print(message.content)
elif isinstance(message, ResultMessage):
if message.subtype == "success":
print(f"Completed: {message.result}")
print(f"Cost: ${message.total_cost_usd}")
break # IMPORTANT: query() does not auto-terminate after ResultMessage
Authentication
- Anthropic API:
export ANTHROPIC_API_KEY=your-key - Amazon Bedrock:
CLAUDE_CODE_USE_BEDROCK=1+ AWS credentials - Google Vertex AI:
CLAUDE_CODE_USE_VERTEX=1+ GCP credentials - Microsoft Azure AI Foundry:
CLAUDE_CODE_USE_FOUNDRY=1+ Azure credentials
OpenRouter
Route Agent SDK requests through OpenRouter for access to multiple model providers, fallbacks, and cost optimization:
export ANTHROPIC_BASE_URL="https://openrouter.ai/api"
export ANTHROPIC_AUTH_TOKEN="$OPENROUTER_API_KEY"
export ANTHROPIC_API_KEY="" # Must be explicitly empty
The Agent SDK inherits Claude Code's model override environment variables, so you can route to different OpenRouter models:
# Route to specific models via OpenRouter
export ANTHROPIC_DEFAULT_SONNET_MODEL="anthropic/claude-sonnet-4"
export ANTHROPIC_DEFAULT_OPUS_MODEL="anthropic/claude-opus-4"
Important: ANTHROPIC_API_KEY must be set to an empty string — if omitted entirely, the SDK will fail with an authentication error. The actual auth is handled by ANTHROPIC_AUTH_TOKEN.
Structured Outputs
Use output_format for validated JSON responses:
options = ClaudeAgentOptions(
output_format={
"type": "json",
"schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"issues": {"type": "array", "items": {"type": "string"}},
"severity": {"type": "string", "enum": ["low", "medium", "high"]}
},
"required": ["summary", "issues", "severity"]
}
}
)
File Checkpointing
Enable file state snapshots for safe rollback:
options = ClaudeAgentOptions(
enable_file_checkpointing=True,
allowed_tools=["Read", "Edit", "Write"]
)
# Later, rewind all file changes made by the agent
async with ClaudeSDKClient(options=options) as client:
await client.connect()
await client.query("Refactor the auth module")
async for msg in client.receive_messages():
pass
# If results are unsatisfactory:
await client.rewind_files() # Restores all files to pre-query state
Extended Thinking
Enable deeper reasoning with thinking token budgets:
options = ClaudeAgentOptions(
max_thinking_tokens=10000, # Budget for extended thinking
allowed_tools=["Read", "Glob", "Grep"]
)
TypeScript Query Object Methods
The TypeScript SDK's Query object (returned by query()) exposes additional control methods:
const q = query({ prompt: "Analyze codebase", options });
// Control methods
await q.interrupt(); // Cancel current operation
await q.rewindFiles(); // Restore file checkpoints
await q.setPermissionMode("acceptEdits");
await q.setModel("claude-opus-4-5");
await q.setMaxThinkingTokens(10000);
// Informational methods
const commands = await q.supportedCommands();
const models = await q.supportedModels();
const mcpStatus = await q.mcpServerStatus();
const account = await q.accountInfo();
Known Issues & Troubleshooting
In-process MCP servers unreliable with subagents
create_sdk_mcp_server() can fail with "Stream closed" when used with parallel subagents due to message-queue backpressure. The SDK's internal transport gets blocked when multiple subagents compete for the MCP connection.
Recommendation: Use stdio subprocess MCP servers for production multi-agent setups. In-process servers are fine for single-agent simple use cases. (Python SDK #425, TS SDK #41)
acceptEdits does NOT auto-approve MCP tools
The acceptEdits permission mode only auto-approves file operations (Edit, Write) and filesystem Bash commands (mkdir, rm, mv, cp). MCP tools still require explicit permission resolution.
Options for MCP tool approval:
bypassPermissions- Approves everything (use with caution)can_use_toolcallback - Programmatic per-tool decisions (requires streaming input mode)- PreToolUse hooks returning
allow- Most reliable for headless agents
"Stream closed" in headless mode
Without a can_use_tool callback or explicit PreToolUse allow decisions, tools requiring permission trigger an interactive prompt. When running the SDK without a terminal (headless/server mode), this fails with "Stream closed" because there is no UI to display the prompt.
Workaround: Use PreToolUse hooks to return explicit allow/deny decisions for all tools that would otherwise trigger a permission prompt. See hooks-reference.md for permission interaction details.
query() does NOT auto-terminate after ResultMessage
The async for loop over query() does not end when ResultMessage arrives. Internally, query() waits for an "end" event from _read_messages(), which only fires after the transport closes. You must break explicitly after ResultMessage.
With a plain string prompt this may appear to work (the stream eventually closes), but with streaming input (async generator + stream_done pattern), omitting break causes a circular deadlock: the loop waits for the stream to close → the stream waits for the generator to finish → the generator waits for stream_done.set() in finally → finally only runs after the loop exits.
# CORRECT
async for message in query(prompt=generate_messages(), options=options):
if isinstance(message, ResultMessage):
result = message
break # Required — prevents deadlock
See custom-tools.md Known Issues for the full stream_done pattern.
Generator exhaustion causes "Stream closed"
When using query() with an async generator prompt, the SDK closes stdin after the generator finishes yielding + a 60-second timeout. For long-running agents, this causes "Stream closed" errors on hooks and can_use_tool callbacks even when permissions are correctly configured.
Fix: Keep the generator alive with an asyncio.Event until the agent completes. See custom-tools.md Known Issues for the full stream_done pattern.
Background subagents can't access MCP tools
Subagents spawned with run_in_background: true silently fail when calling MCP tools. The background process doesn't inherit MCP server connections from the parent agent. Use foreground subagents for MCP-dependent tasks. (Claude Code #13254)
can_use_tool empty arguments bug
When using the can_use_tool callback, always pass updated_input=tool_input (the original arguments) in PermissionResultAllow. Omitting updated_input causes the tool to receive empty arguments. (Python SDK #320)
Reference Files
- Python Patterns - Complete Python examples
- TypeScript Patterns - Complete TypeScript examples
- Hooks Reference - All hooks with examples
- Custom Tools - Tool creation guide
- Multi-Agent Patterns - Orchestration patterns
More from escapewu/skills
project-analysis
深度项目分析工具。用于在现有 docs 不足、代码链路复杂、需要梳理系统架构、模块数据流、时序或性能风险时进行只读取证和结构化分析。常与 `project-docs-workflow` 配套使用,作为其升级步骤;也可在用户明确要求架构分析、数据流分析、时序图、调用链梳理或性能排查时直接使用。默认应落文档:优先新建或更新 `docs/` 下合适文档,不再停留在仅终端输出的 analysis-only 模式。
19canvas-design
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.
1mcp-builder
Guide for creating high-quality MCP (Model Context Protocol) servers that enable LLMs to interact with external services through well-designed tools. Use when building MCP servers to integrate external APIs or services, whether in Python (FastMCP) or Node/TypeScript (MCP SDK).
1internal-comms
A set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).
1frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
1skill-creator
Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.
1