HomeGuidesHow to Build an AI Agent with the Claude Agent SDK

How to Build an AI Agent with the Claude Agent SDK

Last updated: June 2026 · By Ignacy Kwiecień, founder & editor-in-chief, DecodeTheFuture.org

To build an AI agent with the Claude Agent SDK, do five things: install the SDK (pip install claude-agent-sdk or npm install @anthropic-ai/claude-agent-sdk), set your ANTHROPIC_API_KEY, write a minimal query() loop with ClaudeAgentOptions, give it a custom tool via @tool + create_sdk_mcp_server, then run it. The key idea most tutorials skip: the Agent SDK runs the agent loop and executes tools for you — unlike the regular Anthropic API where you hand-roll that loop yourself. You can have a working agent in about 10 minutes.

pip install claude-agent-sdk set ANTHROPIC_API_KEY query() loop @tool + create_sdk_mcp_server Python + TypeScript

Build a Claude agent in 5 steps

The fastest path from an empty folder to a running agent is five steps: (1) set up a project and install the SDK, (2) set ANTHROPIC_API_KEY, (3) write a minimal query() loop, (4) pick a model, and (5) add a custom tool. This guide is a copy-paste build, not an architecture essay — every code block runs as written with the SDK as of June 2026. Examples default to the latest capable model (Opus 4.8) and ship in both Python and TypeScript.

If you have only ever called the Anthropic Messages API, the most important thing to internalize first is what the Agent SDK does that the plain API does not — so you stop hand-rolling tool loops. That distinction is next.

What is the Claude Agent SDK, and how is it different from the regular Anthropic API?

The Claude Agent SDK is a Python and TypeScript library that runs the full agent loop for you: it sends your prompt to Claude, executes any tools Claude decides to call, feeds the results back, and repeats until the task is done — managing context, retries, and orchestration along the way. The regular Anthropic Client SDK (the Messages API) does none of that orchestration: you write the loop.

With the Client SDK you typically write something like this yourself:

Python · Client SDK (you build the loop)
# With the plain Anthropic Client SDK YOU run the loop:
while response.stop_reason == "tool_use":
    tool_call   = get_tool_use(response)
    tool_result = run_my_tool(tool_call)          # you execute it
    response    = client.messages.create(...)     # you re-send it
# ...and you handle retries, context limits, and parsing by hand.

The Agent SDK collapses all of that into one call. You describe the agent and iterate over the messages it streams back; the SDK runs the loop, calls the tools, and stops when finished. It also ships with built-in tools out of the box — Read, Write, Edit, Bash, Monitor, Glob, Grep, WebSearch, WebFetch, and AskUserQuestion — so a file-editing or web-research agent needs almost no custom code. (On the TypeScript side the SDK bundles a native Claude Code binary as an optional dependency, so you do not install Claude Code separately.)

 Anthropic Client SDK (Messages API)Claude Agent SDK
Agent loopYou write it (while stop_reason == "tool_use")Run for you
Tool executionYou execute and re-send resultsExecuted automatically
Built-in toolsNoneRead, Write, Bash, Grep, WebSearch, etc.
Context & retriesYour problemManaged by the SDK
Best forOne-shot completions, full controlMulti-step agents, tool use, automation

Rule of thumb: if your task is a single request-response (summarize this, classify that), the Client SDK is fine. The moment the task needs multiple tool calls and reasoning between them, reach for the Agent SDK instead of rebuilding the loop. If you want the conceptual background on what an agent even is, see our primer on what an AI agent is and the deeper AI agent architecture explained.

What do you need before you start?

You need three things: a supported runtime, the SDK package, and an Anthropic API key. Specifically:

  • Runtime: Python 3.10+ (the package errors with No matching distribution found for claude-agent-sdk on older interpreters) or Node.js 18+.
  • The SDK: claude-agent-sdk on PyPI, or @anthropic-ai/claude-agent-sdk on npm.
  • An API key: create one in the Anthropic Console and keep it out of source control.

That is the whole dependency list. No separate Claude Code install, no extra services. Let’s build.

Step 1Set up the project and install the SDK

Create a folder, make a virtual environment, and install the SDK. The commands differ slightly between platforms — and Windows PowerShell has one gotcha (activation is blocked by default) that no competing tutorial seems to mention, so it is covered here explicitly.

macOS / Linux · bash
mkdir my-agent && cd my-agent
python3 -m venv .venv
source .venv/bin/activate
pip install claude-agent-sdk
Windows · PowerShell
mkdir my-agent; cd my-agent
py -m venv .venv

# If activation is blocked ("running scripts is disabled on this system"),
# allow it for THIS session only, then activate:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned
.\.venv\Scripts\Activate.ps1

pip install claude-agent-sdk

Prefer faster modern tooling? uv works as a drop-in: uv venv then uv pip install claude-agent-sdk. For a TypeScript project instead:

TypeScript · npm
mkdir my-agent && cd my-agent
npm init -y
npm install @anthropic-ai/claude-agent-sdk
# (a native Claude Code binary ships as an optional dependency)

Step 2Authenticate: set ANTHROPIC_API_KEY

The SDK authenticates by default via the ANTHROPIC_API_KEY environment variable. Set it in your shell or load it from a .env file — never hard-code it in your script.

Set the key (pick one)
# macOS / Linux (current shell)
export ANTHROPIC_API_KEY="sk-ant-..."

# Windows PowerShell (current session)
$env:ANTHROPIC_API_KEY = "sk-ant-..."

# Or a .env file in the project root (add .env to .gitignore!)
# ANTHROPIC_API_KEY=sk-ant-...
Using a cloud provider instead of the Anthropic API?

The API key is the default, but the SDK can route through third-party hosts with environment flags: CLAUDE_CODE_USE_BEDROCK=1 (Amazon Bedrock), CLAUDE_CODE_USE_VERTEX=1 (Google Vertex AI), CLAUDE_CODE_USE_FOUNDRY=1 (Microsoft Foundry), or CLAUDE_CODE_USE_ANTHROPIC_AWS=1. For most readers, the plain API key is the right starting point.

Step 3Write the minimal agent: the query() loop

The main entry point is query(), which returns an async iterator. You iterate over it to stream messages back from the agent. Options — system prompt, allowed tools, model, permissions — are passed via ClaudeAgentOptions in Python (an options object in TypeScript). Here is a complete, runnable agent that can read and search files in the current directory:

Python · agent.py
import anyio
from claude_agent_sdk import query, ClaudeAgentOptions

async def main():
    options = ClaudeAgentOptions(
        system_prompt="You are a concise coding assistant.",
        allowed_tools=["Read", "Grep", "Glob"],   # read-only: safe to start
        permission_mode="acceptEdits",
        model="opus",                              # alias -> Opus 4.8 (see Step 4)
        max_turns=8,
    )

    async for message in query(
        prompt="Find every TODO comment in this folder and list the files.",
        options=options,
    ):
        print(message)

anyio.run(main)

Run it with python agent.py. The TypeScript equivalent uses for await over the same query() call:

TypeScript · agent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "Find every TODO comment in this folder and list the files.",
  options: {
    systemPrompt: "You are a concise coding assistant.",
    allowedTools: ["Read", "Grep", "Glob"],
    permissionMode: "acceptEdits",
    model: "opus",
    maxTurns: 8,
  },
})) {
  console.log(message);
}

That is a working agent. It will plan, call Grep/Glob as many times as it needs, and return a final answer — and you never wrote a tool loop. The raw message stream is noisy, though; see Running and debugging below for how to print it cleanly.

How does the agent loop actually work?

The agent loop is the cycle the SDK runs on your behalf: it sends your prompt to Claude, Claude reasons and may emit a tool call, the SDK executes the tool, feeds the tool result back to Claude, and repeats until Claude returns a final answer — delivered as a ResultMessage. Each message in the async for stream is one step of that cycle: assistant reasoning, a tool-use block, a tool result, then the result.

Because it is an async iterator, you see the steps as they happen rather than waiting for one giant blob. The SDK also handles the things you would otherwise babysit: context window management, transient-error retries, and stopping at max_turns. The diagram below shows the cycle.

The Claude Agent SDK agent loop A vertical cycle: your prompt enters query(), Claude reasons and emits a tool call, the SDK executes the tool, the tool result is fed back to Claude, and the loop repeats until a final ResultMessage is returned. The SDK manages context, retries and orchestration so you do not write the loop yourself. The Claude Agent SDK agent loop DecodeTheFuture.org Claude Agent SDK, agent loop, query(), tool call, tool result, ResultMessage, build an AI agent with Claude The agent loop the Claude Agent SDK runs for you: prompt to reasoning to tool call to tool execution to tool result, repeating until a final ResultMessage. Diagram image/svg+xml en © DecodeTheFuture.org The SDK runs this loop for you Your prompt → query() + ClaudeAgentOptions Claude reasons decides: answer now, or call a tool? SDK executes the tool call Read / Grep / Bash / your @tool Tool result fed back SDK manages context + retries repeat until done Final ResultMessage loop stops; you get the answer You iterate the stream with async for (Python) or for await (TypeScript). You never write the loop.

This is exactly the orchestration you would otherwise rebuild with the Client SDK — tool dispatch, result feedback, turn limits — which is why the Agent SDK exists. For how this fits the wider agent landscape, our AI agents explained guide maps the moving parts.

Step 4Choose your model: aliases and current IDs

Set the model with the model option, which accepts either an alias ("opus", "sonnet", "haiku", "fable", "best") or a full model ID like "claude-opus-4-8". If you leave it unset, the default on the Anthropic API is Opus 4.8. Most SERP tutorials still hard-code Sonnet 4.5 — here are the IDs verified as current in June 2026:

AliasResolves toFull IDUse when
"opus" (default)Opus 4.8claude-opus-4-8Hardest reasoning, default capable model
"sonnet"Sonnet 4.6claude-sonnet-4-6Balanced speed/quality workhorse
"haiku"Haiku 4.5claude-haiku-4-5-20251001Fast, cheap, high-volume tasks
"fable"Fable 5claude-fable-5Longest autonomous tasks (not the default)
Python · selecting a model
# Any of these work as the `model` option:
options = ClaudeAgentOptions(model="opus")             # alias
options = ClaudeAgentOptions(model="claude-opus-4-8")  # explicit ID
options = ClaudeAgentOptions(model="sonnet")           # cheaper workhorse
# model=None (the default) -> Opus 4.8 on the Anthropic API

For day-to-day agents, "sonnet" is the cost-efficient workhorse and "opus" is the default when you want maximum reasoning. Reserve "fable" for genuinely long, multi-hour autonomous runs — it is the most capable for that, but it is not the default and you should opt in deliberately.

Step 5Give the agent a custom tool

To let the agent do something the built-in tools cannot, define a custom tool with the @tool decorator (Python) or tool() helper (TypeScript), wrap it in create_sdk_mcp_server / createSdkMcpServer, register it through mcp_servers, and pre-approve it with the fully-qualified name mcp__{server_name}__{tool_name}. These tools run in-process — no separate MCP server to host. Here is a complete temperature-converter agent:

Python · custom tool
import anyio
from claude_agent_sdk import (
    query, ClaudeAgentOptions, tool, create_sdk_mcp_server,
)

# 1) Define the tool. Signature: tool(name, description, input_schema)
@tool("convert_temp", "Convert Celsius to Fahrenheit", {"celsius": float})
async def convert_temp(args):
    c = args["celsius"]
    f = c * 9 / 5 + 32
    return {"content": [{"type": "text", "text": f"{c}C = {f}F"}]}

# 2) Wrap it in an in-process MCP server
my_server = create_sdk_mcp_server(
    name="units", version="1.0.0", tools=[convert_temp],
)

async def main():
    options = ClaudeAgentOptions(
        mcp_servers={"units": my_server},          # register under a name
        allowed_tools=["mcp__units__convert_temp"], # mcp__{server}__{tool}
        model="sonnet",
    )
    async for message in query(
        prompt="What is 100 degrees Celsius in Fahrenheit?",
        options=options,
    ):
        print(message)

anyio.run(main)

Note the naming carefully: the server is registered as "units", the tool is convert_temp, so the allow-list entry is mcp__units__convert_temp — double underscores, mcp__ prefix. To allow every tool on a server use the wildcard mcp__units__*. The TypeScript shape is identical in spirit:

TypeScript · custom tool
import { z } from "zod";
import {
  query, tool, createSdkMcpServer,
} from "@anthropic-ai/claude-agent-sdk";

const convertTemp = tool(
  "convert_temp",
  "Convert Celsius to Fahrenheit",
  { celsius: z.number() },
  async (args) => ({
    content: [{ type: "text", text: `${args.celsius}C = ${args.celsius * 9 / 5 + 32}F` }],
  }),
);

const myServer = createSdkMcpServer({
  name: "units", version: "1.0.0", tools: [convertTemp],
});

for await (const message of query({
  prompt: "What is 100 degrees Celsius in Fahrenheit?",
  options: {
    mcpServers: { units: myServer },
    allowedTools: ["mcp__units__convert_temp"],
    model: "sonnet",
  },
})) {
  console.log(message);
}
Custom tools here are not Agent Skills

This in-process @tool pattern gives the agent a single new capability inside your process. If you instead want to package reusable, on-demand expertise the agent loads only when relevant (via SKILL.md and progressive disclosure), that is Agent Skills — the natural next step covered in our SDK + Skills guide.

Custom tools and MCP are the same protocol underneath. If “MCP” is new to you, our explainer on the Model Context Protocol covers why everything is named mcp__.

How do you control what the agent is allowed to do?

You control the agent’s powers with three option fields plus a permission mode. allowed_tools is the allow-list of tool names the agent may call; disallowed_tools explicitly blocks names; and tools (where supported) scopes the full tool set. The permission_mode then decides what happens when the agent reaches for something:

permission_modeBehavior
defaultRequires a canUseTool callback to approve/deny each request
acceptEditsAuto-approves file edits and common filesystem commands
dontAskDenies anything not in allowed_tools (no prompts)
auto (TS only)A classifier approves or denies each request
bypassPermissionsRuns everything — use only in sandboxes
Safety note

Start read-only (allowed_tools=["Read","Grep","Glob"]) and add write/exec tools deliberately. Never run bypassPermissions against a real filesystem, production credentials, or anything you cannot afford the agent to delete. Treat Bash like handing a stranger your terminal.

Running and debugging your agent

To run the agent, execute the file (python agent.py / node agent.ts via your TS runner). The raw message stream is verbose, so filter it for readable output instead of printing every object. A simple pattern is to print only assistant text and the final result:

Python · readable output
from claude_agent_sdk import AssistantMessage, TextBlock, ResultMessage

async for message in query(prompt=..., options=options):
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, TextBlock):
                print(block.text)            # what Claude is "saying"
    elif isinstance(message, ResultMessage):
        print("DONE:", message.result)       # final answer + usage/cost

For a one-shot call you simply read the last ResultMessage; for an interactive or long-running agent you keep iterating and react to each step as it streams. If something looks stuck, the usual cause is a tool the agent wants but is not in allowed_tools — which is the first item below.

Common pitfalls and how to fix them

Five errors account for most failed first builds. Here is each, with the fix:

  • API Error 400 · thinking.type.enabled is not supported for this model when selecting claude-opus-4-7 (or newer Opus). Opus 4.7 and later use thinking.type.adaptive and reject the older fixed-budget thinking.type.enabled, which stale SDK builds may still send. Fix: upgrade to the latest Agent SDK (pip install -U claude-agent-sdk) so it sends the adaptive form.
  • The whole query() call crashes when a tool errors. If your tool handler raises an uncaught exception, it kills the entire run. Fix: return {"is_error": True} (Python) / { isError: true } (TypeScript) in the tool result instead of raising — this keeps the agent loop alive so Claude can read the error and react.
  • The agent ignores your custom tool. Almost always a name mismatch. The allow-list entry must be exactly mcp__{server_name}__{tool_name} with double underscores — e.g. server "units" + tool convert_tempmcp__units__convert_temp. Fix: match the registered server name, or use mcp__units__*.
  • No matching distribution found for claude-agent-sdk. Your Python is older than 3.10. Fix: install Python 3.10+ and recreate the venv.
  • Auth errors / 401. ANTHROPIC_API_KEY is not set in the process that runs the agent (a common .env-not-loaded mistake). Fix: confirm the variable is exported in the same shell, or that your .env loader runs before query().
Python · resilient tool (don’t raise)
@tool("fetch_price", "Fetch a stock price", {"ticker": str})
async def fetch_price(args):
    try:
        price = await get_price(args["ticker"])
        return {"content": [{"type": "text", "text": f"{args['ticker']}: {price}"}]}
    except Exception as e:
        # Keeps the loop alive so Claude can retry or apologize:
        return {"content": [{"type": "text", "text": f"Lookup failed: {e}"}],
                "is_error": True}

Where to go next: Skills, subagents and hosting

Once the basic agent runs, four directions deepen it: Agent Skills (reusable on-demand expertise packaged as SKILL.md with progressive disclosure), subagents (defined via AgentDefinition and invoked through the built-in Agent tool), hooks (PreToolUse, PostToolUse, Stop, SessionStart, and more for production control), and additional MCP servers for external integrations.

The single best next read is our companion guide on building AI agents with Claude: Agent SDK + Skills — it covers when to add Skills versus more tools, and how progressive disclosure keeps your context window lean. For full working projects, Anthropic publishes example agents (an email assistant and a research agent) in the claude-agent-sdk-demos repository. And if you are weighing the SDK against other ways to build, our roundup of the best AI agent frameworks of 2026 puts it in context, while context engineering covers the discipline that keeps long-running agents reliable.

What does this mean in practice?

In practice, the Claude Agent SDK is the right tool when you want Claude to act — call tools, edit files, search, iterate — inside your own process or infrastructure, in Python or TypeScript, with you owning the runtime. That is its lane, and it is a strong one: you get the production-grade agent loop, built-in tools, hooks, and subagents without gluing a framework together.

Three honest boundaries. First, if you want elaborate multi-agent graphs with explicit state machines, framework-style orchestrators like LangGraph or CrewAI give you more topological control (at the cost of more wiring) — though the Agent SDK’s subagents cover many of those cases. Second, if you do not want to run the loop on your own infrastructure at all, Anthropic’s Managed Agents is a hosted REST API where Anthropic runs the agent and sandbox for you; a common path is to prototype with the SDK, then move to Managed Agents for production scale. Third, the SDK is genuinely the simplest route from zero to a working tool-using agent — which is exactly why it is the right place to start, and why most teams start here before deciding they need anything heavier.

My take after building with it: the value is not the model, it is that the loop and tool plumbing are done. The hours you used to spend re-implementing while stop_reason == "tool_use" and parsing tool blocks now go into the one thing that is actually your product — your custom tools and your prompts.

FAQ

What is the Claude Agent SDK?

The Claude Agent SDK is a Python and TypeScript library from Anthropic that runs the full agent loop for you: it sends your prompt to Claude, executes any tools Claude calls, feeds results back, and repeats until the task is done. It ships with built-in tools (Read, Write, Bash, Grep, WebSearch and more) and lets you add custom tools, so you build tool-using agents without writing the orchestration yourself.

How do I install the Claude Agent SDK?

For Python run pip install claude-agent-sdk (requires Python 3.10 or newer). For TypeScript run npm install @anthropic-ai/claude-agent-sdk (requires Node.js 18 or newer); a native Claude Code binary ships as an optional dependency, so you do not install Claude Code separately. Then set the ANTHROPIC_API_KEY environment variable from the Anthropic Console.

What is the difference between the Claude Agent SDK and the regular Anthropic API?

With the regular Anthropic Client SDK (the Messages API) you write the agent loop yourself, executing each tool and re-sending the result in a while response.stop_reason == "tool_use" loop. The Agent SDK runs that loop and executes tools for you, manages context and retries, and provides built-in tools. Use the Client SDK for one-shot completions; use the Agent SDK for multi-step, tool-using agents.

Which model does the Claude Agent SDK use by default?

If you do not set the model option, the default on the Anthropic API is Opus 4.8 (claude-opus-4-8). You can pass an alias — "opus", "sonnet" (Sonnet 4.6), "haiku" (Haiku 4.5), "fable" (Fable 5), or "best" — or a full model ID. Use "sonnet" as a cost-efficient workhorse and "fable" only for long autonomous tasks.

How do I add a custom tool to a Claude agent?

Define the tool with the @tool decorator (Python) or tool() helper (TypeScript), wrap it in create_sdk_mcp_server / createSdkMcpServer, register it through the mcp_servers option, and pre-approve it in allowed_tools with the fully-qualified name mcp__{server_name}__{tool_name} (or the wildcard mcp__server__*). These tools run in-process, so there is no separate server to host.

Why does my custom tool error crash the whole agent?

If a tool handler raises an uncaught exception, the entire query() call fails. Instead, return a result with {"is_error": True} in Python or { isError: true } in TypeScript. That keeps the agent loop alive so Claude can read the error message and react — retry, adjust, or report it — rather than the run dying.

Why do I get a thinking.type error with claude-opus-4-7?

Older Agent SDK versions fail with API Error 400 thinking.type.enabled is not supported for this model when you select claude-opus-4-7 (or newer Opus), because Opus 4.7 and later use thinking.type.adaptive instead of the older fixed-budget form. Upgrade to the latest Agent SDK (pip install -U claude-agent-sdk) so it sends the adaptive form.

Bibliography (7 sources)

Sources prioritise primary Anthropic / Claude developer documentation and the official SDK source repositories. Package names, API signatures, model IDs and the version-specific pitfall are vendor-documented and current as of June 2026; verify exact versions and IDs on the linked pages before relying on them in production. Links accessed June 2026.

  1. Anthropic / Claude Docs — Agent SDK overview. Primary vendor docs for package names, the query() / ClaudeAgentOptions entry points, the built-in tools table, and the Agent SDK vs Client SDK vs Managed Agents comparison. code.claude.com/agent-sdk/overview
  2. Anthropic / Claude Docs — Agent SDK Quickstart. Step-by-step install, API-key setup, the minimal runnable query() agent, permission modes, and the claude-opus-4-7 thinking.type troubleshooting note. code.claude.com/agent-sdk/quickstart
  3. Anthropic / Claude Docs — Give Claude custom tools. The @tool / tool() definition, create_sdk_mcp_server, mcp__{server}__{tool} naming, and error handling with is_error. code.claude.com/agent-sdk/custom-tools
  4. Anthropic / Claude Docs — Python SDK reference. Exact @tool and create_sdk_mcp_server signatures and the ClaudeAgentOptions.model field type and default. code.claude.com/agent-sdk/python
  5. Anthropic / Claude Docs — Model configuration. Verified current model IDs and aliases (opus = Opus 4.8 / claude-opus-4-8, sonnet = Sonnet 4.6, Fable 5 = claude-fable-5) and the default model. code.claude.com/model-config
  6. Anthropic — claude-agent-sdk-python (GitHub repo / CHANGELOG). Version history and Python SDK install reference; check the CHANGELOG for the exact build that adds thinking.type.adaptive support. github.com/anthropics/claude-agent-sdk-python
  7. Anthropic — claude-agent-sdk-demos. Official example agents (email assistant, research agent) cited as the fuller “real agents” reference. github.com/anthropics/claude-agent-sdk-demos

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -

Most Popular

Recent Comments