Claude Agent SDK: Build Custom AI Agents Programmatically
Use the Claude Agent SDK in Python and TypeScript to build AI agents that read files, run commands, and edit code autonomously.
Key Takeaways#
- The Claude Agent SDK (formerly Claude Code SDK) lets you build AI agents that autonomously read files, run shell commands, search the web, and edit code — all programmable from Python or TypeScript.
- It exposes the same tools, agent loop, and context management used by Claude Code itself, so your agents behave identically to interactive sessions.
- Use
allowed_toolsto gate what an agent can do, hooks to intercept tool calls, andmcp_serversto extend capabilities with external tool providers. - Session resumption lets you carry agent state across multiple calls — ideal for long-running workflows or CI pipelines.
- The SDK is the right choice over the CLI when you need programmatic control, multi-agent orchestration, or integration into automated systems.
What Is the Agent SDK#
The Claude Agent SDK — previously called the Claude Code SDK, later renamed to reflect its broader scope — is a developer library for building custom AI agents. These agents can autonomously read files, run shell commands, search the web, and edit source code. The SDK wraps the exact same tools, agent loop, and context management that power Claude Code, but makes them fully programmable from Python and TypeScript.
Instead of running claude in a terminal, you import the SDK, configure an agent, and drive it from your own code. This unlocks use cases the CLI cannot serve: CI pipelines that lint and fix code, multi-agent orchestration where agents collaborate, custom approval flows that gate dangerous operations, and any workflow that needs structured output rather than streamed text.
Installation#
TypeScript:
npm install @anthropic-ai/claude-agent-sdk
Python:
pip install claude-agent-sdk
The TypeScript SDK bundles the native Claude Code binary as an optional dependency. If you already have Claude Code installed globally, the SDK will detect and use it; otherwise the bundled binary is used.
Note: Opus 4.7 requires SDK v0.2.111 or later. Older versions will not route to that model correctly.
API Key Setup#
The SDK authenticates via your Anthropic API key. Set the environment variable before running your code:
export ANTHROPIC_API_KEY="sk-ant-..."
For third-party providers, set the corresponding flag alongside your provider credentials:
| Provider | Environment Variable |
|---|---|
| Amazon Bedrock | CLAUDE_CODE_USE_BEDROCK=1 |
| Google Vertex | CLAUDE_CODE_USE_VERTEX=1 |
| Anthropic Foundry | CLAUDE_CODE_USE_FOUNDRY=1 |
Each provider also requires its own credential environment variables (e.g., AWS_REGION, GOOGLE_CLOUD_PROJECT). Refer to the provider documentation for specifics.
First Agent#
Here is a minimal Python agent that lists files in the current directory:
import asyncio from claude_agent_sdk import query, ClaudeAgentOptions async def main(): async for message in query( prompt="What files are in this directory?", options=ClaudeAgentOptions(allowed_tools=["Bash", "Glob"]), ): if hasattr(message, "result"): print(message.result) asyncio.run(main())
The equivalent in TypeScript:
import { query, ClaudeAgentOptions } from "@anthropic-ai/claude-agent-sdk"; const stream = query({ prompt: "What files are in this directory?", options: new ClaudeAgentOptions({ allowedTools: ["Bash", "Glob"] }), }); for await (const message of stream) { if ("result" in message) { console.log(message.result); } }
The query function returns an async iterator. Each yielded message represents a step in the agent loop — a tool call, a tool result, or the final response. Filter on result to get the agent's final output.
Built-in Tools#
The SDK exposes the same tool set as Claude Code. Each tool maps to a string name you pass in allowed_tools or blocked_tools.
| Tool | Name | What It Does |
|---|---|---|
| Read | Read | Read file contents with line numbers |
| Write | Write | Create or overwrite files |
| Edit | Edit | Targeted find-and-replace in files |
| Bash | Bash | Execute shell commands |
| Monitor | Monitor | Watch background processes |
| Glob | Glob | Find files by glob pattern |
| Grep | Grep | Search file contents by regex |
| WebSearch | WebSearch | Search the web |
| WebFetch | WebFetch | Fetch and parse a URL |
| AskUserQuestion | AskUserQuestion | Prompt the human for input |
By default, no tools are enabled. You must explicitly list them in allowed_tools.
Hooks in the SDK#
Hooks let you intercept the agent at key lifecycle points. This is how you add custom logging, approval gates, or side effects without modifying the agent itself.
Available hooks:
| Hook | When It Fires |
|---|---|
PreToolUse | Before a tool executes |
PostToolUse | After a tool executes |
Stop | When the agent stops |
SessionStart | When a session begins |
SessionEnd | When a session ends |
Python example — log every file the agent writes:
from claude_agent_sdk import query, ClaudeAgentOptions, HookEvent async def on_post_tool_use(event: HookEvent): if event.tool_name == "Write": print(f"[LOG] Agent wrote to: {event.tool_input.get('file_path')}") async def main(): async for message in query( prompt="Refactor the auth module to use async/await", options=ClaudeAgentOptions( allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"], hooks={"PostToolUse": on_post_tool_use}, ), ): if hasattr(message, "result"): print(message.result) asyncio.run(main())
In PreToolUse, you can return deny to block a tool call entirely — useful for safety rails in production agents.
Subagents via the SDK#
The agents parameter lets you define subagents that the primary agent can delegate work to. Each subagent is an AgentDefinition with its own prompt, tool set, and model.
from claude_agent_sdk import query, ClaudeAgentOptions, AgentDefinition async def main(): async for message in query( prompt="Build a REST API for a todo app", options=ClaudeAgentOptions( allowed_tools=["Read", "Write", "Edit", "Bash"], agents=[ AgentDefinition( name="test-writer", prompt="You write tests. Given source code, produce comprehensive test files.", allowed_tools=["Read", "Write"], ), ], ), ): if hasattr(message, "result"): print(message.result) asyncio.run(main())
The primary agent decides when to invoke subagents based on the task. Subagents inherit the session context but operate with their own tool permissions.
MCP in the SDK#
Model Context Protocol (MCP) servers extend the agent with external tools. Pass them via the mcp_servers parameter.
from claude_agent_sdk import query, ClaudeAgentOptions, MCPServerConfig async def main(): async for message in query( prompt="Take a screenshot of example.com and describe the layout", options=ClaudeAgentOptions( allowed_tools=["Bash", "WebSearch", "WebFetch"], mcp_servers=[ MCPServerConfig( name="playwright", command="npx", args=["@anthropic-ai/mcp-server-playwright"], ), ], ), ): if hasattr(message, "result"): print(message.result) asyncio.run(main())
The SDK launches each MCP server as a subprocess, negotiates capabilities, and registers the server's tools so the agent can call them. Any MCP-compliant server works — Playwright, filesystem, databases, or your own custom servers.
Permissions#
Permission control is straightforward:
allowed_tools: Only the tools listed are available. Omit a tool to block it.- Omit
allowed_toolsentirely: The agent has no tools — it can only reason. blocked_tools: Explicitly block specific tools even if they are inallowed_tools.
# Agent can read and search, but cannot write or execute commands options=ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep", "WebSearch"])
For production systems, always set allowed_tools to the minimum set the agent needs. Combine with PreToolUse hooks for fine-grained approval logic.
Sessions#
Each query call starts a new session by default. The agent's first message is a SystemMessage with subtype="init" that contains a session_id. Capture it to resume later.
from claude_agent_sdk import query, ClaudeAgentOptions async def main(): session_id = None # First call — start a session async for message in query( prompt="Analyze the codebase and list all TODO comments", options=ClaudeAgentOptions(allowed_tools=["Read", "Glob", "Grep"]), ): if hasattr(message, "session_id"): session_id = message.session_id if hasattr(message, "result"): print(message.result) # Second call — resume the same session with full context async for message in query( prompt="Now fix the top 3 TODOs", options=ClaudeAgentOptions( allowed_tools=["Read", "Write", "Edit", "Bash"], resume=session_id, ), ): if hasattr(message, "result"): print(message.result) asyncio.run(main())
Resuming a session means the agent retains all prior context — files read, commands run, decisions made. This avoids re-processing and keeps long workflows coherent.
When to Use the SDK vs the CLI#
| Use the SDK When | Use the CLI When |
|---|---|
| You need programmatic control over the agent loop | You are working interactively in a terminal |
| You are integrating agents into CI/CD pipelines | You want to run quick, ad-hoc tasks |
| You are orchestrating multiple agents | You prefer a conversational workflow |
| You need custom hooks, approval gates, or logging | You do not need structured output |
| You are building a product that wraps Claude Code | You are exploring or prototyping |
The SDK is not a replacement for the CLI — it is the programmatic interface to the same engine. Use the CLI for day-to-day development. Use the SDK when you need to embed Claude Code's capabilities into a larger system.