- The Claude Agent SDK is Claude Code's agent loop as a library: npm package @anthropic-ai/claude-agent-sdk (0.3.293) or PyPI claude-agent-sdk (0.2.164), authenticated with an Anthropic API key or a cloud provider.
- query() streams the agent's work; tools, allowedTools, permissionMode and hooks decide what it can touch. Set permissionMode explicitly, because the default changed in v0.3.286.
- Custom tools run in-process as an MCP server; model, maxTurns and maxBudgetUsd control cost. We type-checked the TypeScript code in this guide but did not run it against the API.
Contents
The Claude Agent SDK is Anthropic's library for building agents on the same loop, built-in tools, permissions and hooks that power Claude Code. According to Anthropic's docs, you write a prompt and some configuration, and the SDK runs the model, executes tools such as Read, Edit and Bash, and streams back what happened. This guide takes you from install to a small, locked-down agent with a custom tool, a safety hook and a spending cap.
How we checked this code. On October 8, 2026, we installed @anthropic-ai/claude-agent-sdk 0.3.293 on Node.js 22 and type-checked the example code in this guide with TypeScript 7.0.2 in strict mode. The types catch mistakes: an invalid permission mode and a misspelled result field both failed the check. The code was type-checked only and never run against the Claude API. Descriptions of runtime behavior below come from Anthropic's documentation and the SDK's type definitions, not from our own runs. The source and lockfile are in our code examples folder (code-examples/D6-agent-sdk-example/).
Last verified: October 8, 2026
What the Agent SDK is (and isn't)
Anthropic's docs describe it as a way to "build production AI agents with Claude Code as a library." Practically:
- It runs the Claude Code binary under the hood. Both the TypeScript and Python packages bundle a native binary, so you usually don't need a separate Claude Code install. Version 0.3.293's
package.jsonpins Claude Code 2.1.293. - It is not the plain Claude API client (
@anthropic-ai/sdk), where you write the tool loop yourself, and not Anthropic's hosted Managed Agents product. Use the Agent SDK when you want Claude Code's tools and loop inside a process you operate.
The packages:
| Language | Package | Version (Oct 8, 2026) |
|---|---|---|
| TypeScript | @anthropic-ai/claude-agent-sdk (npm) | 0.3.293, published October 7, 2026 (the version this guide was type-checked against; 0.3.295 was published October 8) |
| Python | claude-agent-sdk (PyPI) | 0.2.165 (published October 8) |
Both release often (several npm versions shipped in the first week of October 2026 alone), so pin versions in production. Last verified: October 9, 2026.
Prerequisites
- Node.js 18+ (the docs' minimum) or Python 3.10+. We type-checked this guide on Node 22.
- An Anthropic API key from the Claude Console, or credentials for one of the supported cloud providers.
Step 1: Install
mkdir my-agent && cd my-agent
npm init -y
npm pkg set type=module
npm install @anthropic-ai/claude-agent-sdk
npm install --save-dev tsx"type": "module" allows top-level await; tsx runs TypeScript files directly. The SDK installs its platform binary through npm optional dependencies, so don't use --omit=optional, or you'll get no binary.
The SDK also lists zod, @anthropic-ai/sdk and @modelcontextprotocol/sdk (v1, ^1.29.0) as peer dependencies; recent npm versions install them automatically. Note that the Agent SDK uses the v1 MCP package, while our MCP server guide uses the newer v2 packages. They can coexist.
Python: pip install claude-agent-sdk (or uv add claude-agent-sdk).
Step 2: Authenticate
export ANTHROPIC_API_KEY=your-api-keyThe SDK reads the key from the process environment and does not load .env files by itself. Cloud alternatives are switched on with environment variables: CLAUDE_CODE_USE_BEDROCK=1 (Amazon Bedrock), CLAUDE_CODE_USE_VERTEX=1 (Google Cloud's Agent Platform), CLAUDE_CODE_USE_FOUNDRY=1 (Microsoft Foundry), or CLAUDE_CODE_USE_ANTHROPIC_AWS=1 with ANTHROPIC_AWS_WORKSPACE_ID (Claude Platform on AWS), each with that provider's credentials.
One policy point: Anthropic says that, unless previously approved, third-party developers may not offer claude.ai login or claude.ai rate limits in products built on the Agent SDK. Ship with API-key or cloud-provider auth.
Step 3: A minimal agent with query()
This is the shape of Anthropic's quickstart. Save as agent.ts:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",
options: {
allowedTools: ["Read", "Edit", "Glob"], // auto-approve these tools
permissionMode: "acceptEdits", // auto-approve file edits
},
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") console.log(block.text);
else if (block.type === "tool_use") console.log(`Tool: ${block.name}`);
}
} else if (message.type === "result") {
console.log(`Done: ${message.subtype}`);
}
}Anthropic's quickstart runs it with npx tsx agent.ts. Per the docs, query() returns an async iterator. Each item is a message: system setup, the model's text, tool calls, tool results, and finally a result message with a subtype such as success. The docs also say that by default the agent can work on files in the current directory and its subdirectories.
Step 4: Decide what the agent can touch
This is the part to get right. Three options do different jobs:
| Option | Layer | Effect |
|---|---|---|
tools: ["Read", "Grep"] | Availability | Only these built-ins are visible to Claude. MCP tools are unaffected. |
allowedTools: [...] | Permission | Listed tools run without asking. Unlisted tools still exist and go through the permission flow. |
disallowedTools: ["Bash"] | Both | A bare name removes the tool entirely; a scoped rule like "Bash(rm *)" denies matching calls. |
The common mistake: thinking allowedTools is an allowlist. It isn't. It pre-approves. The docs warn that allowedTools "does not constrain bypassPermissions": with allowedTools: ["Read"] and permissionMode: "bypassPermissions", every tool, including Bash, is approved.
Permission modes (PermissionMode type in 0.3.293):
| Mode | What it does |
|---|---|
default | No automatic approvals beyond allow rules; anything else goes to your canUseTool callback |
dontAsk | Anything that would prompt is denied; only pre-approved and no-approval-needed calls run |
acceptEdits | Auto-approves file edits and filesystem commands (mkdir, rm, mv…) inside the working directory |
plan | Explore and plan; edits are never auto-approved |
auto | A model classifier approves or blocks actions such as shell commands |
bypassPermissions | Approves almost everything. The docs say to use it only "in controlled environments where you trust all possible operations" |
Set permissionMode explicitly. Per Anthropic's docs, before TypeScript SDK v0.3.286 leaving it out meant default; now the starting mode comes from your settings files and otherwise from Claude Code's built-in default, "which can be auto mode." If your app assumes prompts will reach your callback, pass default.
For a headless, read-only agent, the docs recommend pairing allowedTools with dontAsk. Even then, some calls that need no approval in default mode, such as file reads inside the working directory, still run.
Step 5: Add a custom tool (in-process MCP server)
Custom tools are defined with tool() and served by createSdkMcpServer(), an MCP server that runs inside your process, not as a separate program. The name Claude sees is mcp__<server key>__<tool name>:
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const countTodos = tool(
"count_todos",
"Count TODO and FIXME markers in a block of source code.",
{ source: z.string().describe("Source code to scan") },
async ({ source }) => {
const todos = (source.match(/\bTODO\b/g) ?? []).length;
const fixmes = (source.match(/\bFIXME\b/g) ?? []).length;
return {
content: [{ type: "text", text: `TODO: ${todos}, FIXME: ${fixmes}` }],
structuredContent: { todos, fixmes },
};
},
{ annotations: { readOnlyHint: true } },
);
const repoTools = createSdkMcpServer({ name: "repo-tools", version: "1.0.0", tools: [countTodos] });
// later: options.mcpServers = { "repo-tools": repoTools }
// options.allowedTools includes "mcp__repo-tools__count_todos"Notes from the docs: the schema is a Zod shape and the handler's arguments are typed from it; a thrown error doesn't stop the agent, it comes back to Claude as an error result (return isError: true to write a better message); readOnlyHint: true lets Claude run the tool in parallel with other read-only calls, but it's a hint, not enforcement. Tool search is on by default, so Claude loads full tool schemas on demand. If you'd rather run a standalone MCP server, see our TypeScript MCP server guide; the mcpServers option also accepts stdio and HTTP servers. For when a tool, resource or prompt is the right choice, see MCP resources vs tools vs prompts.
Step 6: Add a safety hook
Hooks are callbacks that run at points in the loop. Anthropic's permissions docs say hooks run before every other permission check, and that a hook deny applies even in bypassPermissions mode, which makes PreToolUse the right place for rules that must never be skipped:
import type { HookCallback, PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
const blockSecrets: HookCallback = async (input) => {
const pre = input as PreToolUseHookInput;
const toolInput = pre.tool_input as Record<string, unknown>;
const path = String(toolInput?.file_path ?? toolInput?.path ?? "");
if (/(^|\/)\.env(\.|$)|id_rsa|\.pem$/.test(path)) {
return {
hookSpecificOutput: {
hookEventName: pre.hook_event_name,
permissionDecision: "deny",
permissionDecisionReason: `Reading ${path} is blocked by policy`,
},
};
}
return {}; // no opinion: continue normal permission checks
};
// options.hooks = { PreToolUse: [{ matcher: "Read|Grep", hooks: [blockSecrets] }] }Matchers filter on tool names only, so check paths inside the callback. Per the docs, a hook returning allow does not skip deny or ask rules. The TypeScript SDK supports many more events (PostToolUse, Stop, SessionStart, SubagentStop and others); the Python SDK supports fewer.
Step 7: Choose a model and cap the cost
modelpicks the model by API ID. Anthropic's models page on October 8, 2026 listsclaude-opus-5-5($4/$20 per million input/output tokens),claude-sonnet-5-5($2/$10),claude-haiku-5-5(listed "from" $0.10/$0.50) andclaude-fable-5-1($10/$50). Our Claude model guide covers which to use when; agents make many calls per task, so the cheaper tiers add up differently than in chat.fallbackModelnames models to try if the primary is overloaded.effort(lowtomax) trades depth of reasoning against cost and latency on models that support it.maxTurnscaps round-trips.maxBudgetUsd, per the SDK's types, stops the query with anerror_max_budget_usdresult once the SDK's estimated spend passes your limit.- The
resultmessage carriestotal_cost_usdand per-modelmodelUsage. The SDK's own type docs call the cost "an estimate, not a billing statement." Check the Console for real billing.
The complete example
Our full example combines the pieces. It configures a read-only reviewer that sees only Read, Glob and Grep plus the custom tool, uses dontAsk mode, has a hook set to deny reads of secret files, and is capped at 20 turns or an estimated $0.50. This is abridged from src/agent.ts, the file we type-checked. We did not run it:
import { query, tool, createSdkMcpServer, type HookCallback, type PreToolUseHookInput } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
// countTodos, repoTools and blockSecrets as defined in Steps 5 and 6
for await (const message of query({
prompt:
"Review the TypeScript files in ./src. Summarize what they do, " +
"use count_todos on each file, and list the three most important fixes.",
options: {
model: "claude-sonnet-5-5",
tools: ["Read", "Glob", "Grep"],
allowedTools: ["Read", "Glob", "Grep", "mcp__repo-tools__count_todos"],
permissionMode: "dontAsk",
mcpServers: { "repo-tools": repoTools },
hooks: { PreToolUse: [{ matcher: "Read|Grep", hooks: [blockSecrets] }] },
maxTurns: 20,
maxBudgetUsd: 0.5,
},
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "text") console.log(block.text);
else if (block.type === "tool_use") console.log(`[tool] ${block.name}`);
}
} else if (message.type === "result") {
console.log(`\nResult: ${message.subtype}`);
console.log(`Turns: ${message.num_turns}, estimated cost: $${message.total_cost_usd.toFixed(4)}`);
if (message.subtype === "success") console.log(message.result);
}
}To run it yourself: set ANTHROPIC_API_KEY, then npx tsx src/agent.ts. We haven't, so we can't tell you what it prints.
Troubleshooting
Not logged in/Invalid API key: the key isn't in the environment of the process running the agent. Remember.envisn't loaded automatically.- Binary not found: you installed with optional dependencies skipped. Reinstall without
--omit=optional, or install Claude Code natively and setpathToClaudeCodeExecutable. - Tool calls denied unexpectedly: in
dontAskmode, anything not pre-approved is denied. CheckallowedToolsspelling, including the fullmcp__server__toolname. The docs say unanchored globs like"mcp__*"inallowedToolsare ignored with a startup warning. - Your
canUseToolcallback never fires: an allow rule or mode already approved the call. Per the docs, the TypeScript SDK emits aCLAUDE_SDK_CAN_USE_TOOL_SHADOWEDprocess warning whenbypassPermissionsor bareallowedToolsentries would shadow the callback. Use aPreToolUsehook for checks that must always run. - Bypass mode refuses to start: per the docs, on Linux and macOS Claude Code won't start in
bypassPermissionsas root or under sudo outside a recognized sandbox.
Security checklist
- Start in
dontAskordefault; reach foracceptEditsonly in a disposable directory, andbypassPermissionsonly in a sandbox. Per the docs, subagents run in the parent's permission mode unless you set one on the subagent and the parent is indefault,dontAskorplan; a subagent never getsbypassPermissionsunless the parent has it. - Remove tools you don't need (
toolsor bare-namedisallowedTools) rather than relying on prompts to discourage them. - Put non-negotiable rules in
PreToolUsehooks. - Treat everything the agent reads (web pages, issues, files from others) as potentially hostile input; that's how prompt injection reaches tools. Our case against unattended agents covers the failure modes.
- Cap spend with
maxBudgetUsdandmaxTurns, and log results.
About this storyBased on the sources linked below. Code samples were type-checked by 9to5AI; they were not run against the Claude API. Editorial standards




