Claude Agent SDK
The Claude Agent SDK packages the same agent engine that powers Claude Code as a library you import — Python (claude-agent-sdk) or TypeScript (@anthropic-ai/claude-agent-sdk). You get the built-in tool set (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch, Monitor, AskUserQuestion), the agent loop, hooks, subagents, skills, MCP integration, and session management — without writing the tool dispatcher yourself. Compare to the Anthropic Client SDK (claude-api-and-sdks), which gives raw API access and leaves the tool loop to you.
See also
- claude-api-and-sdks — the lower-level Messages API SDKs
- claude-code-cli — CLI that uses this SDK under the hood
- claude-code-subagents-and-skills — same primitives, exposed in SDK
- claude-code-hooks-deep — same hooks system, callback-based in SDK
- mcp-protocol — MCP server configuration in SDK
1. Install
# TypeScript
npm install @anthropic-ai/claude-agent-sdk
# Python
pip install claude-agent-sdkThe TypeScript SDK bundles a native Claude Code binary for your platform as an optional dependency — no separate install needed.
2. Auth
export ANTHROPIC_API_KEY=your-keyOr cloud-provider auth via env vars (same as Claude Code CLI):
CLAUDE_CODE_USE_BEDROCK=1+ AWS credsCLAUDE_CODE_USE_VERTEX=1+ GCP credsCLAUDE_CODE_USE_FOUNDRY=1+ Azure credsCLAUDE_CODE_USE_ANTHROPIC_AWS=1+ANTHROPIC_AWS_WORKSPACE_ID
Note from docs: “Unless previously approved, Anthropic does not allow third-party developers to offer claude.ai login or rate limits for their products.” Use API key auth.
Also: starting 2026-06-15, Agent SDK + claude -p usage on subscription plans draws from a new monthly Agent SDK credit, separate from interactive usage limits.
3. The two main APIs
3.1 query — fire-and-forget streaming
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) # AssistantMessage / UserMessage / SystemMessage / ResultMessage
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Find and fix the bug in auth.ts",
options: { allowedTools: ["Read", "Edit", "Bash"] }
})) {
console.log(message);
}query returns an async iterator of message events. Iterate until ResultMessage arrives — that’s the final result with summary and cost.
3.2 ClaudeSDKClient — persistent session
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
async with ClaudeSDKClient(options=ClaudeAgentOptions(...)) as client:
await client.query("Analyze the codebase")
async for msg in client.receive_messages():
if hasattr(msg, "result"):
print(msg.result)
# Same session, more turns
await client.query("Now fix the bug you found")
async for msg in client.receive_messages():
print(msg)Use for multi-turn agents where session state matters.
4. ClaudeAgentOptions — the configuration surface
Mirrors the CLI flag set. Selected fields:
| Field | Type | Purpose |
|---|---|---|
model | string | Model alias or full ID |
allowed_tools / allowedTools | list of strings | Tools that bypass permission prompts |
disallowed_tools / disallowedTools | list of strings | Tools to deny |
permission_mode / permissionMode | string | default, acceptEdits, plan, auto, dontAsk, bypassPermissions |
system_prompt / systemPrompt | string | dict | Custom or preset system prompt (see §5) |
append_system_prompt / appendSystemPrompt | string | Append to default |
custom_system_prompt / customSystemPrompt | string | Replace default |
max_thinking_tokens / maxThinkingTokens | int | Cap thinking budget |
max_turns / maxTurns | int | Cap agentic turns |
cwd | string | Working directory |
add_dirs / addDirs | list of strings | Additional working dirs |
setting_sources / settingSources | list | ["user", "project", "local"] — restricts which settings.json files load |
mcp_servers / mcpServers | dict | MCP server configs (same shape as .mcp.json) |
agents | dict | Inline subagent definitions |
hooks | dict | Hook callbacks (event → list of HookMatcher) |
skills | dict | Inline skills |
plugins | list | Plugin paths/URLs |
resume | string | Session ID or name to resume |
session_id / sessionId | string (UUID) | Force specific session UUID |
effort | string | low/medium/high/xhigh/max |
output_format / outputFormat | string | text / json / stream-json |
include_partial_messages / includePartialMessages | bool | Stream partial events |
5. System prompt presets
The SDK exposes presets you can opt into (instead of writing your own).
options = ClaudeAgentOptions(
system_prompt={"type": "preset", "name": "claude_code"},
# or override
custom_system_prompt="You are a Python expert assistant.",
append_system_prompt="Always use type hints.",
)The claude_code preset is the full Claude Code system prompt (long, dense, includes tool-use guidance + safety + coding conventions). Use when you want a Claude Code-like agent. Omit for a leaner agent with no default prompt — you’re responsible for tool guidance.
6. Built-in tools
Available out of the box (no implementation needed):
| Tool | Purpose |
|---|---|
Read | Read any file in cwd |
Write | Create new files |
Edit | Precise edits to existing files (str_replace) |
MultiEdit | Multiple edits to a file in one call |
Bash | Terminal commands, scripts, git |
Glob | Find files by pattern |
Grep | Search file contents (ripgrep-based) |
WebSearch | Web search with citations |
WebFetch | Fetch + parse a URL |
Monitor | Watch a background script, react per output line |
AskUserQuestion | Multiple-choice clarifying questions |
Task / Agent | Dispatch a subagent (Task renamed to Agent in v2.1.63) |
Skill | Invoke a skill |
NotebookEdit | Edit Jupyter notebooks |
TaskStop | Stop a running task |
ExitWorktree / EnterWorktree | Worktree navigation |
ToolSearch | Search for deferred MCP tools |
WaitForMcpServers | Wait for slow MCP servers |
Add to your agent via allowed_tools=[...]. Tool restriction prevents Claude from even seeing the tool when omitted.
7. Hooks (callback-based)
Same hook system as Claude Code CLI, but with callback functions instead of subprocesses.
import asyncio
from datetime import datetime
from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher
async def log_file_change(input_data, tool_use_id, context):
file_path = input_data.get("tool_input", {}).get("file_path", "unknown")
with open("./audit.log", "a") as f:
f.write(f"{datetime.now()}: modified {file_path}\n")
return {} # or {"hookSpecificOutput": {"permissionDecision": "deny"}}
async def main():
async for message in query(
prompt="Refactor utils.py",
options=ClaudeAgentOptions(
permission_mode="acceptEdits",
hooks={
"PostToolUse": [
HookMatcher(matcher="Edit|Write", hooks=[log_file_change])
]
},
),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())TypeScript:
import { query, HookCallback } from "@anthropic-ai/claude-agent-sdk";
import { appendFile } from "fs/promises";
const logFileChange: HookCallback = async (input) => {
const filePath = (input as any).tool_input?.file_path ?? "unknown";
await appendFile("./audit.log", `${new Date().toISOString()}: modified ${filePath}\n`);
return {};
};
for await (const message of query({
prompt: "Refactor utils.py to improve readability",
options: {
permissionMode: "acceptEdits",
hooks: { PostToolUse: [{ matcher: "Edit|Write", hooks: [logFileChange] }] }
}
})) {
if ("result" in message) console.log(message.result);
}Same events available as CLI hooks (see claude-code-hooks-deep).
8. Subagents
Inline definitions:
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep", "Agent"],
agents={
"code-reviewer": AgentDefinition(
description="Expert code reviewer.",
prompt="Analyze code quality and suggest improvements.",
tools=["Read", "Glob", "Grep"],
model="sonnet",
)
},
)TypeScript:
options: {
allowedTools: ["Read", "Glob", "Grep", "Agent"],
agents: {
"code-reviewer": {
description: "Expert code reviewer.",
prompt: "Analyze code quality and suggest improvements.",
tools: ["Read", "Glob", "Grep"]
}
}
}Messages from inside a subagent’s context include a parent_tool_use_id field for tracking.
9. MCP servers
Same .mcp.json-style config:
options = ClaudeAgentOptions(
mcp_servers={
"playwright": {"command": "npx", "args": ["@playwright/mcp@latest"]},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {"Authorization": f"Bearer {os.environ['GH_TOKEN']}"},
},
},
)Also accepts inline server definitions inside subagent configs (per-subagent MCP, see claude-code-subagents-and-skills).
10. Permissions
options = ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep"], # auto-approve these
disallowed_tools=["Bash(rm *)"], # deny specific patterns
permission_mode="default", # default | acceptEdits | plan | auto | dontAsk | bypassPermissions
)For interactive approval flows in headless mode, implement permission_prompt_tool (point to an MCP tool that decides). See Handle approvals and user input.
The AskUserQuestion built-in tool lets the agent ask multiple-choice questions and waits for user response.
11. Sessions
Capture session ID on first call, resume later:
session_id = None
async for msg in query(prompt="Read the auth module", options=ClaudeAgentOptions(allowed_tools=["Read", "Glob"])):
if isinstance(msg, SystemMessage) and msg.subtype == "init":
session_id = msg.data["session_id"]
# Resume with full context
async for msg in query(prompt="Now find all callers", options=ClaudeAgentOptions(resume=session_id)):
if isinstance(msg, ResultMessage):
print(msg.result)Sessions persist as JSONL on disk in the same location as Claude Code CLI.
12. Message types
query yields these (per docs + SDK source):
SystemMessage— startup info, session ID.subtypefield:"init", etc.AssistantMessage— content from Claude.contentis a list of blocks (text, tool_use, thinking, etc.).UserMessage— user / tool result content.ResultMessage— final summary. Includesresult(text),total_cost_usd,usage,duration_ms, etc.
async for msg in query(prompt="..."):
if isinstance(msg, ResultMessage):
print(f"Done in {msg.duration_ms}ms, cost ${msg.total_cost_usd:.4f}")
print(msg.result)13. Skills
Same skill format as Claude Code. Loaded from .claude/skills/ and ~/.claude/skills/ by default. Restrict via setting_sources:
options = ClaudeAgentOptions(setting_sources=["project"]) # only project skillsOr define skills inline via skills option (less common — usually loaded from disk).
14. Plugins
options = ClaudeAgentOptions(plugins=["./my-plugin", "https://example.com/plugin.zip"])Loads plugin’s commands, agents, hooks, MCP servers, skills.
15. Streaming partial messages
options = ClaudeAgentOptions(include_partial_messages=True)
async for msg in query(prompt="..."):
# Includes partial events (token-level streaming)
print(msg)Requires print-mode equivalent settings. Useful for live UI updates.
16. Structured outputs
output_format and JSON schema:
options = ClaudeAgentOptions(
output_format="json",
json_schema={"type": "object", "properties": {"summary": {"type": "string"}, "files_changed": {"type": "array"}}, "required": ["summary"]},
)The agent’s final response conforms to the schema.
17. Compare to other Claude tools
| Agent SDK | Client SDK | Claude Code CLI | Managed Agents (REST) | |
|---|---|---|---|---|
| Tool execution | Built-in | You implement | Built-in | Anthropic-hosted |
| Tool loop | Auto | You write | Auto | Auto |
| Session state | JSONL on your filesystem | None (you store) | JSONL on filesystem | Anthropic-hosted event log |
| Custom tools | Python/TS functions in-process | Anything you can dispatch | MCP servers | Triggers come to you; you execute and return |
| Interface | Library | Library | CLI / IDE | REST API |
| Best for | Production agents with local files | Custom integrations, OpenAI-style flows | Daily dev work, ad-hoc scripts | Sandboxed long-running agents |
A common path: prototype with Agent SDK locally, move to Managed Agents for production when you don’t want to operate sandboxes.
18. Worked example: bug-fix agent
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def fix_bug(repo_path: str, bug_description: str):
async for msg in query(
prompt=f"Find and fix this bug: {bug_description}",
options=ClaudeAgentOptions(
cwd=repo_path,
model="claude-opus-4-7",
permission_mode="acceptEdits",
allowed_tools=["Read", "Edit", "Bash", "Glob", "Grep"],
max_turns=20,
append_system_prompt="""
Always run the tests after editing. Use ripgrep for searches.
If you can't reproduce the bug, ask the user for a repro.
""".strip(),
),
):
if isinstance(msg, ResultMessage):
return {
"summary": msg.result,
"cost_usd": msg.total_cost_usd,
"duration_ms": msg.duration_ms,
}
result = asyncio.run(fix_bug("/path/to/repo", "Login button doesn't work on mobile"))19. Common gotchas
- Forgetting to await async iteration — yields are nothing without
async for. Agentinallowed_tools— required for subagent dispatch (otherwise Claude can see them but can’t auto-approve).setting_sourcesdefaults to loading everything (["user", "project", "local"]) — restrict for hermetic agents.- Hook callbacks must return a dict (or empty
{}for no-op). - Skills load asynchronously — for one-shot scripts, may not find skills if you call
queryimmediately after startup. UseClaudeSDKClientfor reliable skill loading. include_partial_messagesis expensive — most use cases don’t need token-level streaming.- Resuming sessions across SDK versions — transcript format is stable but skill content may have changed.
- TypeScript bundle ships native binary — large install, but means no separate Claude Code install.