Guide · 12 steps

Build your first agent with the Claude Agent SDK (TypeScript, October 2026)

Install @anthropic-ai/claude-agent-sdk, call query(), lock down tools and permission modes, add a custom MCP tool and a safety hook, and cap spend. Code type-checked against v0.3.293.

By ShajanthanReviewed 7 min read
ByShajanthanFounder & Editor
Published
Reading7 MIN
Versions coveredClaude Agent SDK (TypeScript) v0.3.293 · TypeScript 7.0.2 (as of Oct 9, 2026)
Flow diagram of a Claude Agent SDK tool call passing through hooks, rules and permission modes
In 20 seconds
  1. 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.
  2. 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.
  3. 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.json pins 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:

LanguagePackageVersion (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)
Pythonclaude-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

BASH
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

BASH
export ANTHROPIC_API_KEY=your-api-key

The 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:

TYPESCRIPT · 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:

OptionLayerEffect
tools: ["Read", "Grep"]AvailabilityOnly these built-ins are visible to Claude. MCP tools are unaffected.
allowedTools: [...]PermissionListed tools run without asking. Unlisted tools still exist and go through the permission flow.
disallowedTools: ["Bash"]BothA 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):

ModeWhat it does
defaultNo automatic approvals beyond allow rules; anything else goes to your canUseTool callback
dontAskAnything that would prompt is denied; only pre-approved and no-approval-needed calls run
acceptEditsAuto-approves file edits and filesystem commands (mkdir, rm, mv…) inside the working directory
planExplore and plan; edits are never auto-approved
autoA model classifier approves or blocks actions such as shell commands
bypassPermissionsApproves 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>:

TYPESCRIPT
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:

TYPESCRIPT
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

  • model picks the model by API ID. Anthropic's models page on October 8, 2026 lists claude-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) and claude-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.
  • fallbackModel names models to try if the primary is overloaded.
  • effort (low to max) trades depth of reasoning against cost and latency on models that support it.
  • maxTurns caps round-trips.
  • maxBudgetUsd, per the SDK's types, stops the query with an error_max_budget_usd result once the SDK's estimated spend passes your limit.
  • The result message carries total_cost_usd and per-model modelUsage. 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:

TYPESCRIPT
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 .env isn't loaded automatically.
  • Binary not found: you installed with optional dependencies skipped. Reinstall without --omit=optional, or install Claude Code natively and set pathToClaudeCodeExecutable.
  • Tool calls denied unexpectedly: in dontAsk mode, anything not pre-approved is denied. Check allowedTools spelling, including the full mcp__server__tool name. The docs say unanchored globs like "mcp__*" in allowedTools are ignored with a startup warning.
  • Your canUseTool callback never fires: an allow rule or mode already approved the call. Per the docs, the TypeScript SDK emits a CLAUDE_SDK_CAN_USE_TOOL_SHADOWED process warning when bypassPermissions or bare allowedTools entries would shadow the callback. Use a PreToolUse hook for checks that must always run.
  • Bypass mode refuses to start: per the docs, on Linux and macOS Claude Code won't start in bypassPermissions as root or under sudo outside a recognized sandbox.

Security checklist

  • Start in dontAsk or default; reach for acceptEdits only in a disposable directory, and bypassPermissions only 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 in default, dontAsk or plan; a subagent never gets bypassPermissions unless the parent has it.
  • Remove tools you don't need (tools or bare-name disallowedTools) rather than relying on prompts to discourage them.
  • Put non-negotiable rules in PreToolUse hooks.
  • 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 maxBudgetUsd and maxTurns, and log results.
Did this guide work for you?

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

Was this useful?Report an error
Comments
0

More on Anthropic & developer tools

The Week in AI

New guides and explainers, every Friday.

0