Claude Agent SDK: Build Production Agents with Claude Code as a Library (Python Tutorial) — Cesar Ayala
← All posts

Claude Agent SDK: Build Production Agents with Claude Code as a Library (Python Tutorial)

The Claude Agent SDK is "Claude Code as a library": instead of hand-writing the `while stop_reason == "tool_use"` loop against the raw Messages API, one `query()` call gives you Claude Code's engine — built-in tools, the agent loop, and context management — running the tools autonomously in Python or TypeScript. Install `claude-agent-sdk`, set `ANTHROPIC_API_KEY`, pass `allowed_tools`, and consume the stream.

The Claude Agent SDK is Claude Code as a library

The Claude Agent SDK gives you Claude Code as a library: instead of hand-writing the while stop_reason == "tool_use" loop against the raw Messages API, a single query() call hands you Claude Code’s engine — the built-in tools, the agent loop, and context management — running the tools autonomously from Python or TypeScript. You install claude-agent-sdk, set ANTHROPIC_API_KEY, pass the tools you want in allowed_tools, and consume the message stream. That is the whole shape of it. This post builds a real agent, plugs in your own tools over MCP, and shows the power features — hooks, subagents, permissions, sessions — code-heavy, in Python.

What the Agent SDK is, and how it relates to Claude Code

Claude Code is the coding agent you run in your terminal. The Agent SDK is that same engine, exposed as a programmable library so you can drive it from your own code instead of typing at a prompt. Per Anthropic’s docs, the SDK gives you the same built-in tools, agent loop, and context management that power Claude Code, in both Python and TypeScript.

That framing tells you what you are NOT signing up to build: no tool dispatcher, no conversation-history bookkeeping, no context compaction when it overflows — the engine does all of that. The SDK inherits Claude Code’s behavior, including loading your .claude/ config (skills, CLAUDE.md) from the working directory.

One branding rule, straight from Anthropic: when you ship a product on the SDK, you must NOT call it “Claude Code.” Use “Claude Agent” or “Powered by Claude.”

What it isClaude Code's engine as a library
LanguagesPython 3.10+ and TypeScript
You getBuilt-in tools + agent loop + context management
Core callquery() — async, streams messages
AuthANTHROPIC_API_KEY (or Bedrock/Vertex/Foundry)
Loads.claude/ config: skills, CLAUDE.md

The key distinction: raw Messages API vs the Agent SDK

This is the one idea to internalize, because it decides which tool you reach for. There are two Anthropic surfaces and they are not the same job.

With the Client SDK (the raw Messages API) you implement the tool loop yourself: you send the tool schemas, the model returns a tool_use block, and then while response.stop_reason == "tool_use": you run the tool, append the tool_result, and call the API again. That is exactly the loop built by hand in build an autonomous agent with Sonnet 5 and explained in how to use the Claude API. You are the runtime. You own the dispatch, the id-matching, the retries, the context window.

With the Agent SDK, Claude runs the tools autonomously. You do not write the loop at all — you query() and consume a stream of messages describing what the agent did. The tool execution, the loop, and context management happen inside the engine. That is the difference between building an agent against /v1/messages yourself and renting the agent that already exists.

Client SDK (raw Messages API)

  • You write the while stop_reason tool-use loop
  • You dispatch every tool call by hand
  • You match tool ids and manage history
  • You compact context when it overflows
  • Full control, more code — see the Sonnet 5 build

Agent SDK (Claude Code as a library)

  • Claude runs the tools autonomously
  • Built-in Read/Write/Edit/Bash/Grep/WebSearch
  • Agent loop + context management handled for you
  • You just consume the message stream
  • Least code to a working production agent

Neither is “better.” For a single narrow tool-use turn with total control over dispatch, the raw API is the right size. For a capable agent that reads files, runs commands, and searches the web without you writing execution code, the Agent SDK is the shortcut — and the same battle-tested engine that ships in Claude Code.

Install and authenticate

Python needs 3.10 or newer. Install the package:

pip install claude-agent-sdk

TypeScript is one npm install — and note it bundles a native Claude Code binary, so you do NOT install Claude Code separately:

npm install @anthropic-ai/claude-agent-sdk

Authenticate by setting ANTHROPIC_API_KEY from your Anthropic Console. Anthropic does not allow third-party products to offer claude.ai login, so use API-key auth:

export ANTHROPIC_API_KEY="sk-ant-..."

If you run on a cloud provider instead of Anthropic directly, point the SDK at it with an env flag rather than changing your code: CLAUDE_CODE_USE_BEDROCK=1 for Amazon Bedrock, CLAUDE_CODE_USE_VERTEX=1 for Google Vertex, or CLAUDE_CODE_USE_FOUNDRY=1. The query() call stays identical; only the environment changes.

Your first agent: query() plus allowed_tools

Here is a complete, runnable agent. query() is an async function, so it lives inside async code, and you iterate the stream with async for:

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Edit", "Bash"],
    )
    async for message in query(
        prompt="Read pyproject.toml and tell me the project name and version.",
        options=options,
    ):
        print(message)

asyncio.run(main())

That is the entire agent. Claude reads the file with the built-in Read tool, and you never wrote a line of tool-execution code — no tool_use handling, no id matching, no loop. The messages you iterate describe the run: assistant text, tool calls, tool results, and a final result message.

The TypeScript shape is the same idea with camelCase options:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Read package.json and report the name and version.",
  options: { allowedTools: ["Read", "Edit", "Bash"] },
})) {
  console.log(message);
}

The mental model: allowed_tools is the agent’s capability list. Nothing outside it can run — that list is also your first, coarsest permission control.

Those tool names are the built-ins, and this is the whole point of the SDK: each one already has execution code inside the engine. You add a name to allowed_tools and it runs. The set is Read, Write, Edit (files), Bash (shell), Glob and Grep (find and search), WebSearch and WebFetch (the web), plus Monitor and AskUserQuestion. Grant only what a task needs — a code-review agent gets ["Read", "Grep", "Glob"] and nothing that can write; a research agent gets ["WebSearch", "WebFetch", "Write"]. Compare that to the raw-API build, where every one of those is a schema you write plus a function you dispatch to. Here they are strings.

  1. Installpip install claude-agent-sdk (Python 3.10+)
  2. Authenticateexport ANTHROPIC_API_KEY from the Console
  3. ConfigureClaudeAgentOptions(allowed_tools=[...])
  4. Call query()async for message in query(prompt, options)
  5. Consume the streamMessages describe tool calls, results, final answer

Connect YOUR tools with mcp_servers

Built-in tools cover the filesystem, the shell, and the web. Your business logic — your database, your billing system, your internal API — is not in that list. You expose it the standard way: as an MCP server, connected through the mcp_servers option.

The Agent SDK speaks the Model Context Protocol, so any MCP server you already built plugs straight in. If you have not built one yet, walk through connect an AI agent to your data with MCP; the server you ship there is exactly what you register here.

from claude_agent_sdk import query, ClaudeAgentOptions

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep"],
    mcp_servers={
        "billing": {
            "command": "python",
            "args": ["-m", "my_billing_mcp_server"],
        },
    },
)

Once connected, the tools that server exposes become callable by the agent alongside the built-ins — same autonomous loop, no extra wiring. The docs use a Playwright MCP server as the canonical example (browser automation), but the plumbing is identical for your own server.

Connecting a server hands the agent real capability against real systems, so treat the connection as a trust boundary — see how to secure MCP servers.

Power features: hooks, subagents, permissions, sessions

Because the engine is Claude Code, the SDK inherits Claude Code’s whole capability surface. Four features move an agent from prototype to production.

Hooks let you run your own code at lifecycle points — PreToolUse before a tool runs, PostToolUse after. You register callback functions with a HookMatcher, which is how you add logging, block a dangerous command, or inspect a tool call before it fires. A PreToolUse hook that refuses rm -rf is the difference between a demo and something you trust with Bash.

from claude_agent_sdk import ClaudeAgentOptions, HookMatcher

async def block_destructive_bash(input_data, tool_use_id, context):
    command = input_data.get("tool_input", {}).get("command", "")
    if "rm -rf" in command:
        return {"permissionDecision": "deny", "reason": "Refused: destructive command."}
    return {}

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Bash"],
    hooks={
        "PreToolUse": [HookMatcher(matcher="Bash", hooks=[block_destructive_bash])],
    },
)

Subagents let the main agent delegate to a specialized helper with its own instructions and its own tool set. You describe one with AgentDefinition, and the agent invokes it through the Agent tool — so you must add "Agent" to allowed_tools for delegation to work:

from claude_agent_sdk import ClaudeAgentOptions, AgentDefinition

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Grep", "Agent"],  # "Agent" enables delegation
    agents={
        "reviewer": AgentDefinition(
            description="Reviews a diff for bugs and security issues.",
            prompt="You are a strict code reviewer. Report only real defects.",
            tools=["Read", "Grep"],
        ),
    },
)

Permissions are your safety envelope. The coarse control is allowed_tools — a tool absent from the list cannot run. The finer control is permission_mode; for example permission_mode="acceptEdits" auto-approves file edits so an unattended run does not stall waiting for a human. Set it deliberately: acceptEdits is right for CI, wrong for an agent touching production.

Sessions give you continuity. The SDK emits an init message carrying a session_id; capture it, and pass resume=session_id on a later query() to continue the same conversation with its context intact — the backbone of a multi-turn assistant that remembers earlier turns:

options = ClaudeAgentOptions(
    allowed_tools=["Read", "Edit"],
    resume=saved_session_id,  # continue a prior session
)

When to use the SDK vs the CLI vs Managed Agents

Three ways to run this engine, and the right answer is often “more than one.”

The Claude Code CLI and the Agent SDK share the same capabilities and differ only in interface. The CLI is for interactive, one-off developer work at your terminal. The SDK is for CI/CD, custom apps, and production automation where the agent runs inside your own code. Many teams use both — the CLI to explore and debug, the SDK to ship.

Managed Agents is the other axis. With the Agent SDK, the agent loop runs in YOUR process, on YOUR infra — you control the environment, the tools, and the data path. Managed Agents is a hosted REST API where Anthropic runs the agent loop and a sandbox for you. Reach for the SDK when you need the agent inside your own systems, your own network, your own security boundary. Reach for Managed Agents when you would rather not operate the runtime at all.

Claude Code CLI
Agent SDK — your process
Managed Agents — hosted REST API

Ship it

The recipe in one breath: pip install claude-agent-sdk, set ANTHROPIC_API_KEY, build ClaudeAgentOptions(allowed_tools=[...]), and iterate query(prompt, options) with async for. You get Claude Code’s built-in tools, its agent loop, and its context management without writing the tool-use loop yourself — that is the entire pitch versus the raw Messages API. Add your own tools through mcp_servers, harden the run with hooks and permissions, delegate with AgentDefinition, and keep continuity with resume.

If you want the concept before the code, start with what is an AI agent. More patterns live in the AI-agents hub.

Because this surface is new and moves fast, confirm every option, tool name, and flag against the official docs before you ship: the Agent SDK overview, the quickstart, and the Model Context Protocol spec for your MCP servers. Then go put the engine to work.