From scratch
Ren Okabe5 min read4 views

Build an AI Agent with the Claude Agent SDK in TypeScript (2026)

A runnable 2026 quickstart: install the Claude Agent SDK, wire a custom tool with tool() and createSdkMcpServer(), and let the agent loop call it for you in about 40 lines of TypeScript.

TypeScript source code on a dark editor screen, representing a Claude Agent SDK quickstart
TypeScript source code on a dark editor screen, representing a Claude Agent SDK quickstart
On this page

Quick Answer

The Claude Agent SDK is Anthropic's official TypeScript library for building agents that loop, call tools, and manage their own context, so you do not have to hand-write the tool-use loop. As of 2026 you install @anthropic-ai/claude-agent-sdk, call query() with a prompt, and register custom tools with tool() and createSdkMcpServer(). This tutorial ships a runnable agent with a real network tool in about 40 lines of TypeScript.

Ren here. Last week I walked through claude-agent-sdk-vs-writing-the-agent-loop-yourself so you know when the SDK earns its keep and when a hand-rolled loop is the honest call. This post is the other half: the copy-paste quickstart. We will build a small agent that answers questions and can fetch the current top story on Hacker News through a tool it decides to call on its own.

Assumptions: Node 20+, a terminal, and an ANTHROPIC_API_KEY. No framework, no build step beyond tsx.

What we are building

A single-file agent that:

  1. Takes a natural-language prompt.
  2. Has one custom tool, get_top_hn_story, that hits the public Hacker News API.
  3. Runs the full agent loop (model decides to call the tool, reads the result, answers) with zero loop code written by us.

The SDK owns the loop. We own the tool.

Install

bash
mkdir hn-agent && cd hn-agent
npm init -y
npm install @anthropic-ai/claude-agent-sdk zod
npm install -D tsx typescript
export ANTHROPIC_API_KEY="sk-ant-..."

zod is used to describe tool inputs. tsx lets us run TypeScript directly. Billing is standard Anthropic API model usage; check the current rates on Anthropic's 2026 pricing page before you run a loop in production.

The smallest possible agent

Create agent.ts:

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

for await (const message of query({
  prompt: "In one sentence, what is an agent loop?",
  options: { model: "sonnet" },
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "text") process.stdout.write(block.text);
    }
  }
}

Run it:

bash
npx tsx agent.ts

Two things to notice. query() returns an async iterable, so you consume it with for await. And model: "sonnet" is an alias: the SDK resolves aliases (fable, opus, sonnet, haiku) to the current model, which is why this snippet does not go stale when Anthropic ships a new version. If you need a pinned build, pass the full model id instead.

Define a real tool

A tool is a name, a description, an input schema, and a handler. The description is not decoration; it is what the model reads to decide whether to call the tool, so write it like an API doc.

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

const getTopHnStory = tool(
  "get_top_hn_story",
  "Fetch the current #1 story on Hacker News. Returns its title, url and score.",
  { limit: z.number().min(1).max(5).default(1) },
  async ({ limit }) => {
    const ids: number[] = await fetch(
      "https://hacker-news.firebaseio.com/v0/topstories.json",
    ).then((r) => r.json());

    const stories = await Promise.all(
      ids.slice(0, limit).map((id) =>
        fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json`).then(
          (r) => r.json(),
        ),
      ),
    );

    const text = stories
      .map((s) => `${s.title} (score ${s.score}) ${s.url ?? ""}`)
      .join("\n");

    return { content: [{ type: "text", text }] };
  },
);

The handler returns a CallToolResult: an object with a content array. Return text, and the model gets text back. The Hacker News Firebase API needs no key, which keeps this tutorial runnable in one paste.

Register the tool and let the agent loop

Tools reach the model through an in-process MCP server. Wrap the tool with createSdkMcpServer(), then pass it in options.mcpServers.

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

const hnServer = createSdkMcpServer({
  name: "hn-tools",
  version: "1.0.0",
  tools: [getTopHnStory],
});

for await (const message of query({
  prompt: "What is the top story on Hacker News right now, and why might it be trending?",
  options: {
    model: "sonnet",
    systemPrompt:
      "You are a concise news assistant. Use tools for live data. Never invent a headline.",
    mcpServers: { "hn-tools": hnServer },
    allowedTools: ["mcp__hn-tools__get_top_hn_story"],
  },
})) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "text") process.stdout.write(block.text);
    }
  }
}

Run it again. The transcript now goes: model reads your question, decides it needs live data, calls get_top_hn_story, reads the JSON result, then writes an answer grounded in the real headline. You wrote a tool and a system prompt. You did not write a single if (toolUse) branch, no result marshalling, no second API round-trip. That is the entire pitch of the SDK.

Note the allowedTools entry. In-process MCP tools are namespaced mcp__{server}__{tool}, so ours is mcp__hn-tools__get_top_hn_story. Listing it in allowedTools means the agent runs the tool without pausing for a permission prompt, which is what you want for a trusted, read-only tool.

Watch the loop instead of just the answer

For debugging, iterate over every message type, not only assistant. The result message carries the final text and the run cost.

typescript
for await (const message of query({ prompt, options })) {
  if (message.type === "assistant") {
    for (const block of message.message.content) {
      if (block.type === "tool_use") {
        console.log(`[tool call] ${block.name}`, block.input);
      }
    }
  }
  if (message.type === "result") {
    console.log("\n[final]", message.result);
  }
}

Now you can see exactly when the model chose to call your tool and what arguments it passed. When a tool misfires, the fix is almost always the tool description or the Zod schema, not the loop.

When to reach for the SDK (and when not to)

The SDK is the right default when you want tool orchestration, sub-agents, MCP servers, and context management handled for you. If your entire job is one deterministic API call with no branching, a raw messages.create call is smaller and cheaper, which is the honest tradeoff I break down in build-first-ai-agent-from-scratch. And if your stack is not Anthropic-first, OpenAI ships a comparable Agents SDK for JavaScript with the same loop-and-tools shape, so the mental model here transfers.

For the full option surface (streaming, hooks, permission modes, sub-agents), the Claude Agent SDK TypeScript reference is the source of truth, and the SDK source on GitHub is worth reading when a type does not do what you expect.

Limitations and open questions

  • Cost visibility. The loop can call a tool, re-read, and call again. Each hop is billed. Log the result message's usage in any long-running agent so a runaway loop does not surprise your invoice.
  • In-process vs external MCP. createSdkMcpServer() runs tools in your Node process, which is great for latency but means a crashing tool can take down the agent. External stdio MCP servers isolate that blast radius at the cost of a process boundary.
  • Permission model. allowedTools is coarse. For tools that write or spend money, the SDK's permission callbacks are safer than a blanket allow, and I have not yet found a clean pattern for per-argument approval. Open question for a later post.
  • Model pinning. Aliases keep tutorials evergreen but move under you. Production agents should pin a full model id and upgrade deliberately.

Postscript: the first time an agent calls a tool you wrote and comes back with a real answer, it feels like cheating. It is not. You just moved the boring part into a library.

Ren Okabe

Written by

Ren Okabe

Ren builds agent infrastructure and writes copy-paste tutorials for engineers shipping LLM tool-use systems.

Frequently asked questions

What is the Claude Agent SDK for TypeScript?

It is Anthropic's official TypeScript library (@anthropic-ai/claude-agent-sdk) for building agents that run the tool-use loop, call custom tools, connect MCP servers, and manage context automatically, so you do not hand-write the loop yourself.

How do I install the Claude Agent SDK in 2026?

Run npm install @anthropic-ai/claude-agent-sdk, add zod for tool schemas, set an ANTHROPIC_API_KEY environment variable, and call query() with a prompt. tsx lets you run the TypeScript file directly without a build step.

How do I add a custom tool?

Use tool(name, description, zodSchema, handler) to define it, wrap it with createSdkMcpServer(), then pass that server in options.mcpServers. The handler returns a CallToolResult with a content array.

Which model should I set?

Pass an alias like 'sonnet', 'opus', or 'haiku' to options.model and the SDK resolves it to the current model, which keeps code from going stale. Pin a full model id when you need a fixed, deliberately upgraded build in production.

Do I still need to write the agent loop myself?

No. The SDK owns the loop: the model decides to call a tool, reads the result, and answers, without you writing tool-dispatch branches. You write only the tool and, optionally, the system prompt.

Is the Claude Agent SDK the only option?

No. For non-deterministic tool orchestration it is a strong default on Anthropic stacks, but a raw messages.create call is smaller for single deterministic calls, and OpenAI ships a comparable Agents SDK for JavaScript with the same loop-and-tools shape.

From scratch

Build your first AI agent from scratch in 30 minutes

An AI agent is just a loop: you call a model, the model asks to run a tool, you run it, you feed the result back, and you repeat until the model is done. In this tutorial you build that loop yourself in plain TypeScript against the Anthropic Messages API, no framework. You will wire up two tools (read a file, run a calculation), let the model orchestrate them, add a turn cap and basic guardrails, then verify the whole thing end to end. The result is a small research agent you fully understand and can extend with your own tools.

6 min read82