Quickstart: Build an Agent with Claude
Two levels: the Client SDK (you manage the tool loop) and the Agent SDK (Claude manages it for you).
Level 1: Client SDK (DIY Tool Loop)
You implement the agentic loop yourself. Full control, more code.
pip install anthropic
export ANTHROPIC_API_KEY=sk-ant-...
Minimal Agent with Tool Use
The simplest possible agent that can call tools:
import anthropic
import json
client = anthropic.Anthropic()
# Define tools
tools = [
{
"name": "get_weather",
"description": "Get current weather for a city",
"input_schema": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name"}
},
"required": ["city"]
}
}
]
# Tool implementation
def execute_tool(name, input):
if name == "get_weather":
# In reality, call a weather API
return f"72F and sunny in {input['city']}"
# The agentic loop
def run_agent(user_message):
messages = [{"role": "user", "content": user_message}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
)
# Check if model wants to use tools
if response.stop_reason == "tool_use":
# Add assistant's response (contains tool_use blocks)
messages.append({"role": "assistant", "content": response.content})
# Execute each tool call
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
messages.append({"role": "user", "content": tool_results})
else:
# No tool calls - return final text
return response.content[0].text
# Run it
print(run_agent("What's the weather in Tokyo and London?"))
This will make two tool calls (one for each city), then synthesize a response.
With Extended Thinking
Add a reasoning step before tool use - useful for complex tasks:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=8096,
thinking={
"type": "enabled",
"budget_tokens": 4096 # tokens allocated for reasoning
},
tools=tools,
messages=messages,
)
# Response now contains thinking blocks you can inspect
for block in response.content:
if block.type == "thinking":
print(f"Reasoning: {block.thinking}")
elif block.type == "tool_use":
print(f"Tool call: {block.name}({block.input})")
Level 2: Agent SDK (Claude Manages the Loop)
The Agent SDK gives you Claude Code’s full agentic engine as a library. You call query(), it handles tool execution, subagents, and context management.
pip install claude-agent-sdk # Python
# or
npm install @anthropic-ai/claude-agent-sdk # TypeScript
Minimal Agent SDK Usage
from claude_agent_sdk import query
# query() is an async iterator - it streams messages as the agent works
async for message in query(
prompt="What files are in the current directory?",
options={
"model": "claude-sonnet-4-6",
"permissionMode": "default", # ask for write permissions
}
):
if message.type == "text":
print(message.text, end="")
elif message.type == "tool_use":
print(f"[Using {message.tool_name}...]")
With Custom Tools (In-Process MCP)
Define tools that run inside your process:
from claude_agent_sdk import query, create_sdk_mcp_server, tool
# Create an in-process MCP server with custom tools
server = create_sdk_mcp_server("my-tools")
@server.tool
def lookup_user(user_id: str) -> str:
"""Look up a user by ID in the database."""
return db.get_user(user_id).to_json()
@server.tool
def send_notification(user_id: str, message: str) -> str:
"""Send a push notification to a user."""
return notifications.send(user_id, message)
# The agent can now use your custom tools alongside built-in ones
async for msg in query(
prompt="Notify user 123 about their overdue payment",
options={"mcpServers": [server]},
):
print(msg)
With External MCP Servers
async for msg in query(
prompt="Create an issue for the bug in auth.py",
options={
"mcpServers": [
{"name": "github", "command": "npx", "args": ["@modelcontextprotocol/server-github"]},
{"name": "filesystem", "command": "npx",
"args": ["@modelcontextprotocol/server-filesystem", "--root", "/path/to/project"]},
],
"permissions": {
"allow": ["mcp__github__*"], # wildcard permission patterns
},
},
):
print(msg)
Hooks (Guardrails & Logging)
Intercept tool calls for validation, logging, or blocking:
from claude_agent_sdk import query, Hook
async def audit_hook(event):
"""Log all tool calls to an audit trail."""
if event.type == "PreToolUse":
log.info(f"Agent calling {event.tool_name} with {event.input}")
# Return {"block": True, "reason": "..."} to prevent the call
return None # allow it
async def block_destructive(event):
"""Block dangerous bash commands."""
if event.type == "PreToolUse" and event.tool_name == "Bash":
if "rm -rf" in event.input.get("command", ""):
return {"block": True, "reason": "Destructive command blocked"}
return None
async for msg in query(
prompt="Clean up the temp directory",
options={
"hooks": [
Hook("PreToolUse", audit_hook),
Hook("PreToolUse", block_destructive),
],
},
):
print(msg)
Subagents (Parallel Specialists)
Define isolated agents with restricted tool sets:
from claude_agent_sdk import query, AgentDefinition
# Define specialist subagents
security_agent = AgentDefinition(
name="security-reviewer",
description="Reviews code for security vulnerabilities",
instructions="Focus on OWASP top 10. Be specific about line numbers.",
tools=["Read", "Grep", "Glob"], # read-only tools
model="claude-opus-4-6", # can override model per agent
)
quality_agent = AgentDefinition(
name="quality-reviewer",
description="Reviews code for quality and maintainability",
instructions="Focus on readability, naming, and patterns.",
tools=["Read", "Grep", "Glob"],
)
# Main agent can spawn subagents for parallel work
async for msg in query(
prompt="Review src/auth.py for security and quality issues",
options={
"agents": [security_agent, quality_agent],
# Main agent will decide when to delegate to subagents
},
):
print(msg)
Note: Subagents cannot nest (no subagent-of-subagent). Each gets its own context window.
Sessions (Resume Conversations)
# Start a session
session_id = None
async for msg in query(
prompt="Read the codebase and understand the auth module",
options={"model": "claude-sonnet-4-6"},
):
if msg.type == "session_start":
session_id = msg.session_id
print(msg)
# Resume later with full context
async for msg in query(
prompt="Now refactor the auth module based on what you learned",
options={"sessionId": session_id},
):
print(msg)
Advanced API Features
Advanced Tool Use (Beta)
Three features that improve tool use efficiency:
| Feature | What it does | Impact |
|---|---|---|
| Tool Search Tool | Auto-selects relevant tools from large sets | 85% token reduction |
| Programmatic Tool Calling | Claude writes Python to orchestrate tools | 37% token savings |
| Tool Use Examples | Add input_examples to tool definitions | 72% to 90% accuracy |
# Tool Use Examples - add examples to your tool definitions
tools = [{
"name": "query_database",
"description": "Query the user database",
"input_schema": {
"type": "object",
"properties": {
"sql": {"type": "string"},
},
},
"input_examples": [
{"sql": "SELECT * FROM users WHERE created_at > '2026-01-01'"},
{"sql": "SELECT COUNT(*) FROM orders WHERE user_id = ?"},
]
}]
Streaming (Client SDK)
with client.messages.stream(
model="claude-sonnet-4-6",
max_tokens=1024,
tools=tools,
messages=messages,
) as stream:
for event in stream:
if event.type == "content_block_delta":
if event.delta.type == "text_delta":
print(event.delta.text, end="", flush=True)
What’s Next
- Protocols - understand MCP for connecting to tools
- Multi-Agent Orchestration - advanced patterns
- SDK Comparison - how this compares to OpenAI/Copilot