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:

FeatureWhat it doesImpact
Tool Search ToolAuto-selects relevant tools from large sets85% token reduction
Programmatic Tool CallingClaude writes Python to orchestrate tools37% token savings
Tool Use ExamplesAdd input_examples to tool definitions72% 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