Claude Agent SDK in TypeScript: Build a Small Agent

Claude Agent SDK in TypeScript: Build a Small Agent

If you write JavaScript or TypeScript, you can now run Claude Code from your own program. The Claude Code SDK for TypeScript is now called the Claude Agent SDK, and a few lines of code are enough to start an agent that reads files, edits code and calls tools you wrote. In this guide you will install the package, build a tiny bug-fixing agent, and then give that agent a custom tool.

This is Part 42 of 45 in our Claude Code Deep Dives, part of our Claude Code tutorials. We built the same agent in Python in Part 41, so this post focuses on what is different in TypeScript.

Tested with Claude Code v2.1.322 on November 13, 2026, using @anthropic-ai/claude-agent-sdk 0.3.322 on macOS.

QuestionShort answer
What is it?A TypeScript library that runs the Claude Code agent inside your own program
Installnpm install @anthropic-ai/claude-agent-sdk
Node version18.14 or newer, according to the quickstart
Main functionquery(), which streams messages as Claude works
Your own toolstool() plus createSdkMcpServer(), with a Zod schema
What we ranA bug fixer and a custom notes tool, both for real

What the Claude Agent SDK Is and Why It Exists

An agent is a program that finishes a task by planning its own steps and calling tools. Claude Code already does this in your terminal. The Agent SDK gives you the same tools, the same agent loop and the same context handling as a package you can import.

Anthropic’s reference page explains that the package bundles a native Claude Code binary for your platform as an optional dependency. On our Mac, npm installed a folder named claude-agent-sdk-darwin-arm64 that contained a file called claude. That means most installs need no separate Claude Code install.

The docs also say the SDK version tracks the bundled Claude Code version. Our install was SDK 0.3.322, and it matched the Claude Code 2.1.322 we ran in the terminal. You can read the details in the TypeScript SDK reference.

The overview page also compares the SDK with other options. Use the Agent SDK when you want Claude Code’s agent inside a process you run. Use the command line for daily interactive work, and use the Client SDK when you want to call the Claude API directly and write the tool loop yourself.

Here is how a run flows through your program.

Diagram showing a TypeScript script calling query, the Claude Code engine doing the work, and messages streaming back to a for await loop
Your script sends a prompt, the engine works, and messages stream back to your loop

What You Need Before You Start

The quickstart lists Node.js 18.14 or newer and an Anthropic account. You also need a terminal and a folder where it is safe to let an agent edit files.

  • Node.js 18.14 or newer (we used Node 25.6.1 with npm 11.9.0)
  • An API key from the Claude Console for anything you plan to ship
  • A throwaway demo folder, never a real project
  • Do not skip optional dependencies when you install, because the native binary arrives that way

A note on sign-in. Anthropic’s docs say that, unless previously approved, third-party developers may not offer claude.ai login or its rate limits in their products. For products, use an API key or a cloud provider such as Amazon Bedrock or Google Cloud.

Our demo was a personal test on a Mac that was already signed in to Claude Code, and we set no API key. We explain the difference between a subscription and a key in our headless guide.

Step 1: Install the TypeScript Agent SDK

Make a folder and set up a small Node project. The quickstart gives these commands for a new TypeScript project.

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

Setting "type": "module" lets your script use top-level await, and tsx runs TypeScript files directly, so you need no build step. We also installed zod, because the custom tool later in this guide uses it for its input schema.

For a real project, set your key in the shell that runs the script with export ANTHROPIC_API_KEY=your-api-key. The SDK reads it from the environment and does not load .env files on its own, so use a package such as dotenv if you keep the key in a file.

After installing, we listed the top-level packages to confirm the version.

Terminal showing npm ls output with claude-agent-sdk version 0.3.322 highlighted
The SDK, tsx and zod are installed in our demo folder

Step 2: Create a Buggy File

The agent needs something to fix. We wrote a small utils.ts with two bugs.

The first returns NaN for an empty list, and the second crashes on a null user because of the ! non-null assertion.

export function calculateAverage(numbers: number[]): number {
  let total = 0;
  for (const num of numbers) {
    total += num;
  }
  return total / numbers.length;
}

export function getUserName(user: { name: string } | null): string {
  return user!.name.toUpperCase();
}

We also saved a copy as utils.ts.orig, so we could compare the files afterward with diff.

Step 3: Write Your First Agent With query()

Create agent.ts. The query() function returns an async generator, so you read it with for await. Each message that arrives is Claude’s reasoning, a tool call, a tool result or the final outcome.

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

for await (const message of query({
  prompt:
    "Review utils.ts for bugs that would cause wrong results or crashes. Fix them. Keep your final answer under 60 words.",
  options: {
    allowedTools: ["Read", "Edit", "Glob"], // auto-approved tools
    permissionMode: "acceptEdits", // auto-approve file edits
    maxTurns: 8, // safety limit
  },
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "text") console.log("Claude:", block.text);
      else if (block.type === "tool_use") console.log("Tool call:", block.name);
    }
  } else if (message.type === "result") {
    console.log(
      "Done:", message.subtype,
      "| turns:", message.num_turns,
      "| cost: $" + (message.total_cost_usd ?? 0).toFixed(4),
    );
  }
}

The file has three main parts. The prompt says what you want, and the options object configures the agent.

The loop prints what happens, and it ends when Claude finishes or hits an error.

Terminal showing the agent.ts file with the import line and the allowedTools, permissionMode and maxTurns options highlighted
The whole agent fits on one screen

What the three options do

According to the reference, allowedTools lists tools to approve without prompting. It does not limit Claude to only those tools. Other tools fall through to permissionMode and your permission callback.

The option permissionMode: "acceptEdits" approves file edits automatically. The option maxTurns caps the number of agent turns, which protects you from a loop that never ends. If you want to block a tool completely, the docs say to use disallowedTools.

OptionWhat it doesOur value
allowedToolsApproves these tools without a promptRead, Edit, Glob
permissionModeSets how other actions are approvedacceptEdits
maxTurnsLimits agent turns8
disallowedToolsDenies tools outrightNot used

Step 4: Run the Agent

Run the file with npx tsx agent.ts. Here is what happened on our Mac.

Terminal output showing Glob, Read and Write tool calls, Claude's summary of two fixed bugs, and a Done success line with 4 turns
The agent searched, read, wrote and finished in four turns

Claude called Glob to find files, Read to open utils.ts, and Write to save the fix. The run ended with Done: success | turns: 4 and a printed cost of $0.0781.

One detail surprised us. We only listed Read, Edit and Glob, yet Claude used Write.

That is consistent with the docs, because allowedTools does not restrict the agent, and acceptEdits approved the file change. If you need a strict list, add the rest to disallowedTools.

Claude also told us, “I didn’t run or test the changes.” That is worth remembering. The agent edited the file, but it did not prove the fix is correct.

Step 5: Check What Changed

Never trust an agent’s summary without reading the diff. We compared the original and the edited file.

Terminal showing a diff that adds an empty-array check and replaces the non-null assertion with optional chaining
The diff shows exactly two small changes

The agent added if (numbers.length === 0) return 0; and changed the user line to user?.name?.toUpperCase() ?? "". Both are reasonable.

Returning 0 for an empty list is a design choice, though, so decide for yourself whether it fits your program.

Step 6: Give Your Agent a Custom Tool

Built-in tools are useful, but the real power is your own code. The SDK lets you define a tool with tool() and wrap it in an in-process server with createSdkMcpServer(). According to Anthropic’s custom tools guide, the server runs inside your application, not as a separate process.

A tool has four parts: a name, a description, an input schema and a handler. In TypeScript the schema is a Zod object, and calling .describe() on a field gives Claude a hint about it.

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

const NOTES: Record<string, string> = {
  monday: "ship the report",
  tuesday: "call the dentist",
  friday: "plan the trip",
};

const getNote = tool(
  "get_note",
  "Look up the note saved for a weekday",
  { day: z.string().describe("Weekday name, such as tuesday") },
  async (args) => {
    const note = NOTES[args.day.toLowerCase()] ?? "no note for that day";
    return { content: [{ type: "text", text: note }] };
  },
);

const server = createSdkMcpServer({ name: "notes", version: "1.0.0", tools: [getNote] });

Then pass the server to query() through mcpServers, and approve the tool by its full name. The docs say that name follows the pattern mcp__{server_name}__{tool_name}, so ours is mcp__notes__get_note.

for await (const message of query({
  prompt: "What is on my notes for Tuesday and Friday? Answer in one short sentence.",
  options: {
    mcpServers: { notes: server },
    allowedTools: ["mcp__notes__get_note"],
    maxTurns: 6,
  },
})) {
  // print assistant text, tool calls and the result, as before
}
Terminal showing the notes-agent.ts file with the tool definition and the mcpServers and allowedTools options highlighted
The tool definition, and where it is registered with the agent

We ran it with npx tsx notes-agent.ts.

Terminal output showing two calls to mcp__notes__get_note for tuesday and friday, and Claude's one-sentence answer
Claude called our function once for each day

Claude called get_note twice, once for tuesday and once for friday, then answered: “Your notes say to call the dentist on Tuesday and plan the trip on Friday.” The run finished in four turns.

Why a ToolSearch call appears first

Before our tool ran, the output showed a ToolSearch call asking for select:mcp__notes__get_note. We did not write that.

The reference mentions a tool search feature that can defer tool schemas, and both tool() and createSdkMcpServer() accept an alwaysLoad setting to keep schemas in the first prompt. We did not test that setting, so check the tool search page if the extra step matters to you.

Python or TypeScript: Which Should You Pick?

The two SDKs do the same job, so pick the one your team already uses. Here are the differences we saw while building both agents.

TopicTypeScriptPython
Package@anthropic-ai/claude-agent-sdkclaude-agent-sdk
Read the streamfor await over query()async for inside asyncio.run
Message checkmessage.type === "result"isinstance(message, ResultMessage)
Option namesallowedTools, maxTurnsallowed_tools, max_turns
Tool schemaZod objectDict of types
Run a scriptnpx tsx agent.tspython agent.py

If you want the Python version of this tutorial, read the Python guide. For scheduled runs, see Routines versus /loop.

Try It Yourself

Practice with a new bug. Add a third function to utils.ts and ask the agent to fix only that one.

export function firstWord(text: string): string {
  return text.split(" ")[0].toUpperCase();
}

Then change the prompt in agent.ts to the one below and run it again. Afterward, compare the file with diff to see what changed.

Fix only the firstWord function so it never crashes on an empty string. Do not touch other functions.

Start small, cap the turns, and read every diff the agent makes.

Common Mistakes

  • Skipping "type": "module" in package.json. Top-level await then fails, unless you name the file agent.mts.
  • Installing with npm ci --omit=optional. The docs say the native binary comes as an optional dependency, so the SDK cannot start without it.
  • Forgetting that the SDK does not read .env files. Load your key yourself.
  • Assuming allowedTools is a strict list. It only pre-approves tools. Use disallowedTools to block them.
  • Skipping maxTurns. A limit protects you from a loop that keeps going.
  • Pointing the agent at a real project. Start in a copy or a demo folder.
  • Assuming the fix is correct. Our agent said it had not run the code, so run your tests.

Pro Tips

  • Print total_cost_usd from the result message while you experiment, so you notice expensive prompts early. The docs call it an estimate, not a billing statement.
  • Check that subtype is success before you trust the result. The reference lists other subtypes for failures.
  • Keep prompts narrow. Our agent finished in four turns because the task was small.
  • Give each field in a Zod schema a .describe() hint, so Claude knows what to pass.
  • Set readOnlyHint in the tool’s annotations for tools with no side effects. The custom tools guide says this lets Claude call such tools in parallel.
  • If your agent should use helpers of its own, read subagent best practices next.

Cheat Sheet

Cheat sheet card listing setup commands, core SDK calls, options and good habits for the TypeScript Agent SDK
The main pieces of a small TypeScript agent on one card

Frequently Asked Questions

Is the Claude Code SDK the same as the Claude Agent SDK?

Yes. The package was renamed, and Anthropic’s docs include a migration guide for people moving from the old Claude Code SDK packages. Today you install @anthropic-ai/claude-agent-sdk.

Do I need to install Claude Code separately?

Usually not. The package bundles a native Claude Code binary through an optional dependency. If your package manager skips optional dependencies, install Claude Code yourself and set pathToClaudeCodeExecutable to its path.

Which Node.js version do I need?

The quickstart says Node.js 18.14 or newer. We ran our demo on Node 25.6.1.

Why use tsx?

It runs TypeScript files directly, so you can try an agent without a build step. For a real app you can compile your code in the usual way.

How should I sign in?

For products, use an API key from the Claude Console or a supported cloud provider. The docs say third-party developers may not offer claude.ai login unless Anthropic approved it. Our personal demo used an existing sign-in on one Mac.

Conclusion

The Claude Agent SDK turns Claude Code into a TypeScript package: install it, call query(), and read the messages as they stream back. In our test, a short script fixed two bugs in four turns, and a custom tool() let Claude call our own function. Start small, set maxTurns, read every diff, and move to an API key before you build anything other people will use. You can find more guides in our Claude Code tutorials and learn about the model behind it on our Claude AI page.

Previous in the series: Part 41: Claude Agent SDK in Python: Build a Small Agent

Next in the series: Part 43: Claude Code in a CI/CD Pipeline: A Working Example.

Newsletter

New AI tools in your inbox

The best new tools and the AI news that matters, in one short email. Unsubscribe any time.

Free · No spam · Unsubscribe any time

✓ Looks like you’re subscribed

Loading Google’s sign-up form…

Looks like you’re already subscribed

Thanks for reading! New AI tools and the AI news that matters will land in your inbox.

Not subscribed yet? Refresh the page to load the sign-up form.

The sign-up form couldn’t load. If you use an ad or content blocker, allow news.google.com and reload the page.

Free instant check before you pay. Takes a few seconds.

Scroll to Top