I broke my first Claude Agent SDK script by leaving permissionMode on its default and handing the agent a repo with a half-finished migration script sitting in it. It ran npm run migrate without asking, because I'd set allowedTools too wide. Nothing was lost, it was a throwaway branch, but it's the fastest way to learn that this SDK gives you a real agent, not a chatbot wrapper.
Quick take: The Claude Agent SDK (
@anthropic-ai/claude-agent-sdk) exposes Claude Code's actual agent loop as a TypeScript library: multi-turn reasoning, built-in tools, and session state, callable fromquery()instead of a CLI. Add your own tools withtool()and a Zod schema, and keepallowedToolsexplicit until you trust what you've built.
What Is the Claude Agent SDK, and How Is It Different From the Claude API?
The plain Claude API gives you one thing: a request goes in, a response comes back. Multi-step reasoning, tool calls, and file access are all things you build yourself on top of it. The Claude Agent SDK is a different layer entirely. It's the same agent loop that powers Claude Code, packaged as an npm library you call from your own Node process.
Install it with npm install @anthropic-ai/claude-agent-sdk. That single package gives you query(), the built-in tool set (Read, Write, Edit, Bash, Glob, Grep, WebSearch, WebFetch), permission hooks, and session resume, all without shelling out to a CLI binary. If you've used Claude Code from the terminal, this is that same engine, just embedded in your own TypeScript code instead of a chat interface.
How Do You Get an Anthropic API Key?
You need a key before any of the code below will run. Here's the fast path:
- Go to console.anthropic.com and sign in, or create an account if you don't have one yet.
- Open Settings -> API Keys in the left sidebar.
- Click Create Key, give it a name (something like
agent-sdk-tutorialso you remember what it's for), and confirm. - Copy the key immediately. It starts with
sk-ant-and Anthropic only shows the full value once. - Add billing under Settings -> Billing if the workspace doesn't already have credit. Agent SDK calls bill the same as regular Claude API usage, per token, not a flat fee.
- Store the key as an environment variable, never in source code:
export ANTHROPIC_API_KEY=sk-ant-...in your shell, or a.envfile that's in.gitignore.
Treat the key like a password. Anyone who has it can run up your bill, so keep it out of client-side JavaScript, screenshots, and public repos. Two habits are worth adopting from the first key you create. Scope a separate key per project rather than reusing one everywhere, because revoking a leaked key is instant and painless when only one service depends on it. And set a spend limit on the workspace under Billing before you start iterating, since an agent loop that retries a failing tool call can burn through tokens far faster than a chat session ever will. The Anthropic documentation recommends rotating keys on a schedule too, which sounds like security theatre until the first time a key ends up in a committed .env file and you need to know exactly which one to kill.
How Do You Install and Run Your First Agent?
Set your API key, install the package, and call query(). It returns an async generator, so you stream messages with a plain for await loop:
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "List the TypeScript files in this project and summarize what each one does.",
options: {
cwd: process.cwd(),
allowedTools: ["Read", "Glob"],
permissionMode: "default",
},
})) {
if (message.type === "assistant") {
console.log(message.message);
}
if (message.type === "result") {
console.log("done:", message);
}
}
Two options matter more than the rest on your first run. allowedTools is a hard allowlist, anything not on it gets rejected before it executes, not just flagged. cwd scopes the built-in file tools to that directory, so Read and Glob can't wander outside your project by accident. Leave both unset and the agent inherits broader defaults than you probably want for a first test. The third option, permissionMode, is the one to leave alone until you understand the other two, default prompts before anything writes, which is exactly the behaviour you want while you're still learning what the agent decides to do on its own. Expect the first run on a mid-sized repo to take somewhere between 15 and 40 seconds, most of it spent reading files rather than generating text.
How Do You Give the Agent a Custom Tool?
Built-in tools cover file access and shell commands. For anything domain-specific, an internal API, a database query, a linter you wrote, you define your own with tool() and a Zod schema:
import { tool, createSdkMcpServer, query } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const lintCheck = tool(
"lint_check",
"Run the project's ESLint config against a file path and return violations",
{ filePath: z.string() },
async ({ filePath }) => {
const violations = await runEslint(filePath); // your own function
return {
content: [{ type: "text", text: JSON.stringify(violations) }],
};
},
);
const devTools = createSdkMcpServer({
name: "dev-tools",
tools: [lintCheck],
});
for await (const message of query({
prompt: "Check src/index.ts for lint violations and explain the worst one.",
options: {
mcpServers: { "dev-tools": devTools },
allowedTools: ["mcp__dev-tools__lint_check"],
},
})) {
console.log(message);
}
The Zod schema does double duty here. It validates the arguments Claude sends before your handler ever runs, and TypeScript infers the handler's argument types straight from it, so filePath is a string with zero manual typing. The description string is the only thing the model sees when deciding whether to call this tool, so write it like documentation, not a variable name. Note the naming convention on the allowlist too: in-process tools get addressed as mcp__<server>__<tool>, and getting that string wrong is the single most common reason a custom tool silently never fires. The agent just won't have it available, and nothing in the output says why. When I first wired up 3 internal tools this way, two of them sat unused for an hour because I'd written the server name with a hyphen in one place and an underscore in the other.
What Permission Modes Should You Use in Production?
Four modes exist: default (each unlisted tool asks for permission, allowlisted tools run freely), plan (the agent proposes actions without executing anything), acceptEdits (file edits auto-approve, everything else still asks), and bypassPermissions (nothing asks, ever). The gap between default and bypassPermissions is the whole safety story here.
For a CI script or a scheduled job, don't reach for bypassPermissions just because there's no human around to click "approve." Set allowedTools to the exact list the job needs and leave permissionMode on default, that combination gives you the same unattended behavior with a hard boundary instead of an honor system. Reserve bypassPermissions for sandboxed environments where a wrong file write genuinely can't matter, a throwaway container, not a real repo.
canUseTool is the escape hatch when an allowlist alone isn't precise enough. It's an async callback you pass in options that inspects the actual tool call, so you can allow Bash in general but deny any command string containing rm -rf, something a static allowlist can't express.
Where Does the SDK Fit Next to Your Other TypeScript Agent Tooling?
If you've already built MCP servers for Claude Code or Cursor, nothing here replaces them, mcpServers in the query() options accepts the same external MCP servers over stdio or HTTP, alongside your in-process tool() definitions. The full mechanics of that protocol, including resources and the newer authorization flow, are in the MCP guide. And if you're comparing this against a framework-first approach instead of Anthropic's own SDK, Mastra and the Vercel AI SDK covers that other path, provider-agnostic, more scaffolding, less tied to Claude specifically.
The honest tradeoff: the Claude Agent SDK gets you Claude Code's actual agent loop with almost no glue code, at the cost of being Anthropic-specific. If your product is committed to Claude anyway, that's not a real cost. If you need to swap models later, budget the time to rebuild this layer on something provider-agnostic. For a rough sense of scale, the working example in this guide is under 40 lines of TypeScript across 2 files, and the equivalent framework setup runs closer to 3 files plus a config module before the first agent call happens.