ailiteracynepal 🇳🇵
Text size

Chapter 04 · Section III · 12 min read

4.3 TypeScript SDK: query, messages, SDK MCP

The TypeScript SDK — the query function, the message types you'll pattern-match on, and how to expose local functions as MCP tools without spawning a server.

The TypeScript SDK gives you the same headless Claude Code power as the Python one, plus a Node-native ergonomic and — the small superpower — the ability to expose in-process functions as MCP tools directly. Reach for it when your integration lives in a Node/Bun app.

Installation

npm install @anthropic-ai/claude-code-sdk

Node 20+.

Minimal example

import { query, type Message } from "@anthropic-ai/claude-code-sdk";

for await (const msg of query({
  prompt: "Summarise src/README.md",
  options: {
    model: "claude-sonnet-4-6",
    permissionMode: "acceptEdits",
  },
})) {
  if (msg.type === "assistant") {
    console.log(msg.content);
  }
}

query is the one-shot entry point. It returns an async iterator of Message objects.

The message types

The Message union covers everything you might yield:

type Message =
  | { type: "assistant"; content: string; /* ... */ }
  | { type: "user"; content: ToolResult[]; /* ... */ }
  | { type: "result"; subtype: "success" | "error"; /* ... */ }
  | { type: "system"; /* metadata */ };

Pattern-match on msg.type. TypeScript narrows the union so you get typed access to whichever branch you are in.

switch (msg.type) {
  case "assistant":
    console.log(msg.content);
    break;
  case "result":
    console.log(`cost $${msg.total_cost_usd}`);
    break;
}

SDK MCP servers

The killer feature: expose local TypeScript functions as MCP tools without running a separate server process.

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

const analytics = createSdkMcpServer({
  name: "analytics",
  tools: [
    tool({
      name: "count_users_since",
      description: "Return the number of users signed up since a date.",
      input: z.object({ since: z.string() }),
      async run({ since }) {
        return { count: await db.users.countSince(since) };
      },
    }),
  ],
});

for await (const msg of query({
  prompt: "how many new users in the last 30 days?",
  options: { mcpServers: [analytics] },
})) {
  /* ... */
}

Claude sees mcp__analytics__count_users_since as a tool; when it calls it, your function runs in-process. No subprocess, no serialisation across a pipe, no separate lifecycle to manage.

When to reach for SDK MCP

  • Your service already has typed clients for the data Claude needs — reuse them.
  • Latency matters — subprocess spawn + stdio has overhead.
  • You want tight control over what Claude can call — the function signature is the schema.

Long-lived sessions

For interactive-shaped applications, hold the query iterator open and feed it new prompts through the SDK’s session-management primitives (version-dependent — check the docs for Session / resume).

Check your understanding

Quick check

You are building a Node service that lets Claude query a Postgres database using your typed ORM. You want zero subprocesses. Which SDK feature is right?