Claude Media
ClaudeのTypeScript SDKで実装するStreaming・Tool・MCPヘルパー

ClaudeのTypeScript SDKで実装するStreaming・Tool・MCPヘルパー

公式TypeScript SDKのStreamingヘルパー、Zod対応のToolヘルパー、MCPヘルパーの使い方をコード例で解説します。

TypeScript SDKの導入 — インストールと対応ランタイム

公式のTypeScript SDKはnpm install @anthropic-ai/sdkの一行で導入できます。TypeScript 5.0以上に対応します。

実行環境はNode.js 20 LTS以降(非EOLに限る)・Deno 1.28.0以降・Bun 1.0以降・Cloudflare Workers・Vercel Edge Runtimeなどです。Jestは28以降の"node"環境のみで、"jsdom"は非対応です。Nitroは2.6以降で、React Nativeは非対応です。

ブラウザ実行はデフォルトで無効化されています。APIキーをクライアント側に持ち出すと第三者に抜き取られる危険があるためです。社内限定ツールや一時的なデバッグ用途など、キーの露出リスクを許容できる場面に限りdangerouslyAllowBrowser: trueで明示的に有効化します。エラーの出方と代替構成はdangerouslyAllowBrowserエラーの原因で扱っています。

npm install @anthropic-ai/sdk

APIキーは環境変数ANTHROPIC_API_KEYから自動で読み込まれます。個人アカウントキーやサービスアカウントキーが複数のワークスペースにアクセスできる場合は、リクエストヘッダーanthropic-workspace-idでワークスペースを指定します。なお、content[0].textを決め打ちで読むと型エラーになることがあります(原因と直し方はこちら)。次のコードは、その型エラーを避けてtype === "text"で絞り込んでから読む形です。

import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic({
  apiKey: process.env["ANTHROPIC_API_KEY"] // デフォルト値なので省略可能
});
 
const message = await client.messages.create({
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello, Claude" }],
  model: "claude-opus-5-5"
});
 
for (const block of message.content) {
  if (block.type === "text") {
    console.log(block.text);
  }
}

サンプルはトップレベルのawaitを使います。CommonJSのプロジェクトで型検査すると、awaitの位置にTS1309(CommonJSではトップレベルawaitを使えない)が出ます。package.jsonに"type": "module"を足し、tsconfig.jsonのmoduleとmoduleResolutionをNodeNextにすると通ります。

SDK v0.131.0を一時ディレクトリに入れ、絞り込みなしのmessage.content[0].textを型検査にかけたときの出力が次です。

npx tsc --noEmit --pretty false
bad.ts(8,32): error TS2339: Property 'text' does not exist on type 'ContentBlock'.
  Property 'text' does not exist on type 'BashCodeExecutionToolResultBlock'.

ContentBlockはテキスト以外のブロック(コード実行の結果など)も含む型なので、typeで絞らない限りtextは読めません。

ストリーミングとStreaming helpersで応答を逐次処理する

長い応答を待たずに処理を始めたいとき、SDKはServer-Sent Eventsによるストリーミングをサポートします。client.messages.create({ ..., stream: true })はイベントのasync iterableだけを返す軽量な経路で、最終メッセージをメモリ上に組み立てません。途中で打ち切りたければループをbreakするかstream.controller.abort()を呼びます。

イベント処理よりテキストだけ欲しい場合はclient.messages.stream()のStreaming helpersが便利です。on("text", ...)でテキスト断片を受け取りつつ、finalMessage()で完成したメッセージオブジェクトも取得できます。

const anthropic = new Anthropic();
 
const stream = anthropic.messages
  .stream({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Say hello there!" }]
  })
  .on("text", (text) => {
    console.log(text);
  });
 
const message = await stream.finalMessage();
console.log(message);

Tool helpersでZod / JSON Schemaからツールを定義する

ツール呼び出しのループを自分で書くと、Claudeの応答を受け取り→該当ツールを実行し→結果を返す、という往復を手動で管理することになります。TypeScript SDKのclient.beta.messages.toolRunner()はこの往復を自動化し、ZodスキーマまたはJSON Schemaで入力を定義するだけでループが完結します。

import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod";
import { z } from "zod";
 
const anthropic = new Anthropic();
 
const weatherTool = betaZodTool({
  name: "get_weather",
  inputSchema: z.object({
    location: z.string()
  }),
  description: "Get the current weather in a given location",
  run: (input) => {
    return `The weather in ${input.location} is foggy and 60°F`;
  }
});
 
const finalMessage = await anthropic.beta.messages.toolRunner({
  model: "claude-opus-5-5",
  max_tokens: 1000,
  messages: [{ role: "user", content: "What is the weather in San Francisco?" }],
  tools: [weatherTool]
});
 
console.log(finalMessage.content);

ツール実行中にエラーを返したいときは、プレーンなErrorではなくToolErrorを投げます。ToolErrorは画像などの構造化コンテンツを受け付けるため、「なぜ失敗したか」をスクリーンショット付きでモデルへ返せます。

公式ドキュメントのサンプルは@anthropic-ai/sdk/lib/tools/BetaRunnableToolからToolErrorをimportします。v0.131.0で型検査すると、このパスはTS2305(has no exported member 'ToolError')で落ちました。同じ版で@anthropic-ai/sdk/lib/tools/ToolErrorは型検査も実行も通り、new ToolError("x") instanceof Errorはtrueでした。BetaRunnableTool側が公開している関数はrunRunnableTool・toolErrorContent・toolNameの3つです。

import { ToolError } from "@anthropic-ai/sdk/lib/tools/ToolError";
 
const screenshotTool = betaZodTool({
  name: "take_screenshot",
  inputSchema: z.object({ url: z.string() }),
  run: async (input) => {
    if (!isValidUrl(input.url)) {
      throw new ToolError(`Invalid URL: ${input.url}`);
    }
    const result = await takeScreenshot(input.url);
    if (result.error) {
      throw new ToolError([
        { type: "text", text: `Failed to load page: ${result.error}` },
        { type: "image", source: { type: "base64", data: result.screenshot, media_type: "image/png" } }
      ]);
    }
    return { type: "image", source: { type: "base64", data: result.screenshot, media_type: "image/png" } };
  }
});

MCP helpersでMCPサーバーとの往復を短くする

TypeScript SDKにはModel Context Protocol(MCP)サーバーと連携するための専用ヘルパーが用意されています。mcpTools・mcpMessages・mcpResourceToContent・mcpResourceToFileの4関数が、MCP側の型をClaude API側の型へ変換します。定型的な変換コードを自分で書かずに済みます。

APIにはmcp_serversパラメータもあり、URLでアクセスできるリモートサーバーへClaudeを直接つなげます。どちらを選ぶかは、接続先とやりたいことで決まります。

くらべる

mcp_servers とMCP helpersの使い分け

API側でつなぐ

mcp_servers

接続先がURLで届くリモートサーバーで、ツール呼び出しだけで足りる場合の選択肢です。クライアント側のコードがほとんど要りません。

SDK側でつなぐ

MCP helpers

stdio接続のローカルサーバーを使う場合や、プロンプト・リソースも取り込みたい場合に向きます。MCP接続をより細かく制御したいときも、こちらです。

import {
  mcpTools,
  mcpMessages,
  mcpResourceToContent,
  mcpResourceToFile
} from "@anthropic-ai/sdk/helpers/beta/mcp";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
 
const anthropic = new Anthropic();
const transport = new StdioClientTransport({ command: "mcp-server", args: [] });
const mcpClient = new Client({ name: "my-client", version: "1.0.0" });
await mcpClient.connect(transport);
 
// MCPのプロンプトをそのままMessages APIへ渡す
const { messages } = await mcpClient.getPrompt({ name: "my-prompt" });
await anthropic.beta.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 1024,
  messages: mcpMessages(messages)
});
 
// MCPのツール一覧をtoolRunnerにそのまま渡す
const { tools } = await mcpClient.listTools();
const finalMessage = await anthropic.beta.messages.toolRunner({
  model: "claude-opus-5-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Use the available tools" }],
  tools: mcpTools(tools, mcpClient)
});

MCPのリソースはmcpResourceToContentでメッセージのコンテンツに、mcpResourceToFileでファイルアップロードの入力にそれぞれ変換できます。変換できない値(未対応のコンテンツ種別・MIMEタイプ、http/https以外のリソースリンクなど)を渡すとUnsupportedMCPValueErrorが送出されるので、try/catchで拾って原因を特定します。

Message Batchesとリトライ・タイムアウトの設定

client.messages.batches名前空間でMessage Batchesにも対応しています。基本の流れはbatches.create()でリクエストの配列を送り、processing_statusが"ended"になったらbatches.results()で結果を1件ずつ取り出すというものです。

const batch = await client.messages.batches.create({
  requests: [
    { custom_id: "my-first-request", params: { model: "claude-opus-5-5", max_tokens: 1024, messages: [{ role: "user", content: "Hello, world" }] } }
  ]
});
 
const results = await client.messages.batches.results(batch.id);
for await (const entry of results) {
  if (entry.result.type === "succeeded") {
    console.log(entry.result.message.content);
  }
}

接続失敗やAPIからの4xx/5xx応答はAPIErrorのサブクラスとして投げられます。接続エラー・408・409・429・500番台は指数バックオフ付きでデフォルト2回まで自動リトライされ、maxRetriesオプションでクライアント全体またはリクエスト単位に上書きできます。たとえば、タイムアウトして再送しても害のない読み取り系のリクエストは回数を増やし、二重に実行されると困る書き込み系のリクエストはmaxRetries: 0にする、という使い分けがリクエスト単位の上書きで可能です。

タイムアウトはデフォルト10分ですが、非ストリーミングかつmax_tokensが大きい場合は(60 * 60 * maxTokens) / 128000秒(最大60分)まで動的に延びます。長時間かかる非ストリーミングリクエストは、この見積もりで10分を超えると判断された時点でSDK側がエラーを出します。ストリーミングに切り替えるか、timeoutオプションを明示的に上書きすると回避できます。

ステータスエラー型
400エラー型BadRequestError
401エラー型AuthenticationError
403エラー型PermissionDeniedError
404エラー型NotFoundError
409エラー型ConflictError
422エラー型UnprocessableEntityError
429エラー型RateLimitError
500以上エラー型InternalServerError
N/Aエラー型APIConnectionError(接続自体が確立できない場合)

デフォルトではSDKがanthropic-versionヘッダーを2023-06-01に固定して送信します。独自の値に上書きすると、型や挙動がSDKの想定とずれることがあります。

Auto-paginationとBeta機能へのアクセス

一覧系エンドポイントはページネーションされています。for await ... of構文を使えば、次ページの有無を自分で判定せずに全件を辿れます。

async function fetchAllMessageBatches() {
  const allMessageBatches = [];
  for await (const messageBatch of client.messages.batches.list({ limit: 20 })) {
    allMessageBatches.push(messageBatch);
  }
  return allMessageBatches;
}

1ページずつ手動で制御したい場合はpage.hasNextPage()とpage.getNextPage()を使います。全件を一括で辿らず、1ページ分を処理してユーザーの操作を待ってから次ページを取得する画面や、途中で打ち切り条件を判定してから続きを取るバッチ処理など、for awaitが向かない場面で使います。

ベータ機能はほとんどがclient.beta配下から使えます。有効化にはbetasフィールドへベータヘッダーの識別子を渡します。たとえばcontext editingを使う場合は次のようになります。

const client = new Anthropic();
const response = await client.beta.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Hello, Claude" }],
  betas: ["context-management-2025-06-27"]
});

ベータ機能は一般提供前の早期フィードバック用なので、利用可能な機能一覧は随時変わります。

Bedrock・Agent Platform・Foundryのクライアントを使い分ける

Amazon Bedrock・Google CloudのAgent Platform・Claude Platform on AWS・Microsoft Foundryへは、それぞれ専用パッケージのクライアントで接続します。

パッケージ

接続先ごとのパッケージとクライアント

  • Agent Platform

    @anthropic-ai/vertex-sdkのAnthropicVertexです。

  • Bedrock

    @anthropic-ai/bedrock-sdkのAnthropicBedrockMantleは新規プロジェクト向けです。既存のInvokeModel API利用アプリはAnthropicBedrockを使います。

  • Claude Platform on AWS

    @anthropic-ai/aws-sdkのAnthropicAwsです。ベータで、workspaceIdをコンストラクタか環境変数ANTHROPIC_AWS_WORKSPACE_IDで指定します。

  • Foundry

    @anthropic-ai/foundry-sdkのAnthropicFoundryです。

パッケージは基本的にSemVerに従いますが、静的型だけへの変更・ドキュメント化されていない内部実装への変更・大多数の利用者に影響しない変更はマイナーバージョンとしてリリースされることがあります。破壊的変更の判定基準を把握しておくと、アップグレード時の差分確認が楽になります。

まとめ

サンプルを自分のプロジェクトへ移すときは、モデルID・importパス・モジュール設定の3点を型検査で確かめるのが近道です。dangerouslyAllowBrowserは、APIキーがクライアントコードに露出するため、プロダクション環境では有効にしません。

Ruby SDKとの実装上の違いはClaudeのRuby SDKでツール実行ループとページネーションを実装するで比べられます。料金やモデル選択を含むAPI全体の見取り図はAnthropic API完全ガイド、送信前のトークン見積もりはcount_tokensで送信前にトークン数を数える方法にあります。

この記事を共有:XはてブLinkedIn