メインコンテンツへスキップ
ブログ一覧

Claude Agent SDK(TypeScript)実践ガイド — CLI spawn から SDK ネイティブへ、自律エージェント構築の全手順

(更新: 2026年04月07日)
Claude Agent SDKAIエージェントTypeScriptMCP

Claude Code CLI を spawn で叩いて stdout をパースする — このパターンで自律エージェントを動かしている開発者は少なくないだろう。筆者自身、24時間稼働の自動化デーモンでまさにこの方式を使い続けてきた。だが、マルチターン会話の状態管理、ツール権限のきめ細かい制御、コスト追跡をすべて文字列ベースで実装し続けるのは、正直そろそろ限界だ。

2025年9月に「Claude Code SDK」から「Claude Agent SDK」へリブランドされた公式 SDK は、型付きメッセージ・宣言的な権限制御・セッション管理・コスト追跡をネイティブで提供する。本記事では V1(async generator)と V2(send/stream)の両 API を実コードで比較しながら、MCP カスタムツール統合・settingSources によるプロジェクト設定制御・本番運用で効く権限設計まで、CLI spawn では得られない SDK の実力を一気に解説する。

Claude Agent SDK とは — CLI spawn 方式との決定的な違い

Claude Code SDK → Claude Agent SDK へのリブランド経緯

元々 @anthropic-ai/claude-code パッケージの一部として提供されていた SDK 機能が、2025年9月に @anthropic-ai/claude-agent-sdk として独立した。背景には「コーディング専用ではなく、金融・カスタマーサポート・リサーチなど汎用的なエージェント構築に使える SDK」という位置づけへの転換がある。CLI 本体(@anthropic-ai/claude-code)は引き続きメンテされており、SDK はその上に型安全なインターフェースを被せるレイヤーだ。

SDK vs CLI spawn:何が変わるのか

SDK の内部実装は claude -p を子プロセスとして起動し JSON-lines で通信する仕組みで、パフォーマンス差はほぼない。ただし利用者が得られる制御レベルが根本的に違う。

観点 CLI spawn(従来) Agent SDK
型安全性 stdout は string。自前パース 15種以上の型付き SDKMessage
権限制御 --dangerously-skip-permissions か手動承認 allowedTools / disallowedTools / permissionMode の宣言的制御
コスト追跡 なし(自前で概算) result.total_cost_usd でリアルタイム取得
セッション管理 sessionId を自前で保持 createSession / resumeSession でネイティブ管理
MCP 統合 .mcp.json に依存 mcpServers オプション + インプロセス MCP サーバー
エラー型 stderr の文字列マッチ result.subtypeerror_max_turns / error_max_budget_usd 等を型判別

CLI spawn パターンは fire-and-forget には手軽だが、構造化された制御が必要になった時点で SDK への移行を検討する価値がある。

セットアップとインストール

インストールと前提条件

bash
npm install @anthropic-ai/claude-agent-sdk

前提条件:

  • Node.js 18+
  • Claude Code CLI がインストール済みであること(SDK は内部で CLI を起動する)
  • 依存パッケージ: @anthropic-ai/sdk ^0.80.0@modelcontextprotocol/sdk ^1.27.1

最小構成は5行で動く:

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

for await (const message of query({ prompt: "Hello!" })) {
  if (message.type === "assistant") console.log(message.content);
}

旧パッケージからのマイグレーション

@anthropic-ai/claude-code SDK から移行する場合、作業は import パスの変更のみ:

bash
npm uninstall @anthropic-ai/claude-code
npm install @anthropic-ai/claude-agent-sdk
diff
- import { query } from "@anthropic-ai/claude-code";
+ import { query } from "@anthropic-ai/claude-agent-sdk";

API の互換性は維持されているため、コードの変更は基本的に不要だ。

V1 API(async generator)vs V2 API(send/stream)— どちらを選ぶか

V1: query() の async generator パターン

V1 の query()AsyncGenerator<SDKMessage> を返す安定 API。マルチターンは resume オプションで sessionId を渡す:

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

// 1ターン目
let sessionId: string | undefined;
for await (const msg of query({
  prompt: "src/index.ts を読んで問題を指摘して",
  allowedTools: ["Read", "Glob", "Grep"],
  permissionMode: "bypassPermissions",
})) {
  if (msg.type === "system" && "sessionId" in msg) sessionId = msg.sessionId;
  if (msg.type === "result") console.log(`Cost: $${msg.total_cost_usd}`);
}

// 2ターン目(セッション継続)
for await (const msg of query({
  prompt: "指摘した問題を修正して",
  resume: { sessionId: sessionId!, mode: "continue" },
  allowedTools: ["Read", "Edit", "Bash"],
  permissionMode: "bypassPermissions",
})) {
  if (msg.type === "assistant") console.log(msg.content);
}

V1 は安定版であり、forkSession() によるセッション分岐など高度な機能もサポートしている。

V2: unstablev2createSession の send/stream パターン

V2 は unstable_ プレフィックス付きのプレビュー API。マルチターンが圧倒的に楽になる:

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

// TypeScript 5.2+ の await using で自動クリーンアップ
await using session = unstable_v2_createSession({
  allowedTools: ["Read", "Edit", "Bash", "Glob"],
  permissionMode: "bypassPermissions",
});

// 1ターン目
await session.send("src/index.ts を読んで問題を指摘して");
for await (const msg of session.stream()) {
  if (msg.type === "assistant") console.log(msg.content);
}

// 2ターン目 — セッションID管理不要
await session.send("指摘した問題を修正して");
for await (const msg of session.stream()) {
  if (msg.type === "result") console.log(`Cost: $${msg.total_cost_usd}`);
}

ワンショットだけなら unstable_v2_prompt() がさらに簡潔:

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

const result = await unstable_v2_prompt("package.json の依存関係を一覧して", {
  allowedTools: ["Read"],
  permissionMode: "bypassPermissions",
});
console.log(result.total_cost_usd);

選定フローチャート

code
マルチターン会話が必要?
├─ No → unstable_v2_prompt()(ワンショット)
└─ Yes → セッションフォーク(分岐)が必要?
    ├─ Yes → V1 query() + forkSession()(V1 一択)
    └─ No → 本番の安定性を最優先?
        ├─ Yes → V1 query() + resume
        └─ No → V2 createSession()(開発体験が最良)

個人的には、V2 のマルチターン体験は一度使うと V1 に戻れなくなるレベルだ。ただし unstable_ プレフィックスが外れるまでは、本番環境では V1 を推奨する。

settingSources と権限設計 — SDK アプリの安全な構成

settingSources で何が制御されるか

v0.1.0 での破壊的変更: SDK はデフォルトで settingSources: [](空配列)となり、ファイルシステム上の設定を一切読まない。これは CLAUDE.md、Skills、.mcp.json の MCP 設定がすべて無視されることを意味する。

CLAUDE.md や .mcp.json を読ませるには明示的な指定が必須:

typescript
const options = {
  prompt: "...",
  settingSources: ["project"],  // .claude/settings.json + CLAUDE.md を読み込む
};

3つの値とその優先度:

読み込み対象 優先度
'user' ~/.claude/settings.json
'project' .claude/settings.json + CLAUDE.md
'local' .claude/settings.local.json

プログラムで直接指定した allowedToolssystemPrompt は常に最優先される。

permissionMode × allowedTools × disallowedTools の評価順序

権限の評価は以下の5段階で行われる:

code
1. Hooks(PreToolUse)       → ブロック可。最優先
2. disallowedTools           → bypassPermissions でも有効。絶対拒否
3. permissionMode            → default / acceptEdits / bypassPermissions / dontAsk
4. allowedTools              → 事前承認リスト(制限リストではない)
5. canUseTool コールバック    → プログラム的な動的判定

よくある罠: allowedTools は「これだけ許可する」リストではなく「これは事前に承認済み」リストだ。allowedTools: ["Read"] と指定しても、permissionMode: "bypassPermissions" であれば Read 以外のツールも実行される。

ツールを本当に制限したい場合は disallowedToolspermissionMode: "dontAsk" を組み合わせる:

typescript
// Read 以外のツールを拒否する場合の正しい設定
const options = {
  permissionMode: "dontAsk",
  allowedTools: ["Read", "Glob", "Grep"],
  disallowedTools: ["Edit", "Write", "Bash", "NotebookEdit"],
};

本番向け権限設計パターン

typescript
// パターン1: 読み取り専用エージェント(コードレビュー等)
const readOnly = {
  permissionMode: "dontAsk" as const,
  allowedTools: ["Read", "Glob", "Grep"],
  disallowedTools: ["Edit", "Write", "Bash"],
};

// パターン2: 編集許可(ビルド・テスト付き)
const editAllowed = {
  permissionMode: "bypassPermissions" as const,
  allowedTools: ["Read", "Edit", "Write", "Glob", "Grep", "Bash"],
  disallowedTools: ["NotebookEdit"],
};

// パターン3: フル権限 + コスト上限
const fullAccess = {
  permissionMode: "bypassPermissions" as const,
  maxBudgetUsd: 5.0,
  maxTurns: 20,
};

MCP ツール統合 — 外部ツールとカスタムツールの接続

stdio / HTTP / SSE の3トランスポート

mcpServers オプションで MCP サーバーを宣言的に接続できる:

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

for await (const msg of query({
  prompt: "最新のissueを確認して",
  mcpServers: {
    github: {
      type: "stdio",
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-github"],
      env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! },
    },
    "remote-search": {
      type: "http",
      url: "https://search.example.com/mcp",
    },
  },
  allowedTools: ["mcp__github__*"],  // ワイルドカードで一括許可
  permissionMode: "bypassPermissions",
})) {
  if (msg.type === "assistant") console.log(msg.content);
}

ツール名は mcp__<server-name>__<tool-name> の命名規則に従う。allowedTools ではワイルドカード(mcp__github__*)が使える。

なお、.mcp.json からの自動読み込みは settingSources: ["project"] 設定時のみ有効だ。SDK デフォルトの settingSources: [] では読み込まれない。

SDK MCP Server でインプロセスのカスタムツールを作る

createSdkMcpServer()tool() を使えば、TypeScript の関数をそのまま MCP ツールとして公開できる:

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

const searchTool = tool(
  "search_docs",
  "社内ドキュメントを検索する",
  { query: z.string().describe("検索クエリ") },
  async ({ query }) => {
    const results = await searchInternalDocs(query);
    return { content: [{ type: "text", text: JSON.stringify(results) }] };
  },
  { annotations: { readOnlyHint: true } }
);

const mcpServer = createSdkMcpServer({
  name: "internal-tools",
  version: "1.0.0",
  tools: [searchTool],
});

for await (const msg of query({
  prompt: "認証フローのドキュメントを検索して要約して",
  mcpServers: { "internal-tools": mcpServer },
  allowedTools: ["mcp__internal-tools__*"],
  permissionMode: "bypassPermissions",
})) {
  if (msg.type === "assistant") console.log(msg.content);
}

zod でスキーマを定義し、annotations でツールの特性(読み取り専用など)をヒントとして付与できる。これは地味に便利で、エージェントがツールの副作用を判断する材料になる。

実践: ファイル編集 + Bash 実行を行う自律エージェント

エージェントの設計と実装

指定ディレクトリのコードを読み取り → 問題を特定 → 修正 → ビルド確認まで自動で行うエージェントを V2 API で実装する:

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

async function autoFixAgent(projectDir: string, task: string) {
  await using session = unstable_v2_createSession({
    cwd: projectDir,
    systemPrompt: `あなたはシニアエンジニアです。与えられたタスクに対して、
コードの読み取り→問題特定→修正→ビルド確認を自律的に行ってください。
修正後は必ず npm run build で動作確認すること。`,
    allowedTools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
    permissionMode: "bypassPermissions",
    maxTurns: 15,
    maxBudgetUsd: 2.0,
  });

  await session.send(task);

  for await (const msg of session.stream()) {
    switch (msg.type) {
      case "assistant":
        // アシスタントの応答をストリーム出力
        if (typeof msg.content === "string") process.stdout.write(msg.content);
        break;
      case "result":
        // 完了 or エラー
        console.log("\n--- 実行完了 ---");
        console.log(`ステータス: ${msg.subtype}`);
        console.log(`コスト: $${msg.total_cost_usd}`);
        console.log(`ターン数: ${msg.num_turns}`);
        console.log(`所要時間: ${msg.duration_ms}ms`);

        if (msg.subtype === "error_max_turns") {
          console.warn("最大ターン数に到達。タスクが複雑すぎる可能性あり。");
        }
        if (msg.subtype === "error_max_budget_usd") {
          console.warn("コスト上限に到達。maxBudgetUsd の引き上げを検討。");
        }
        break;
    }
  }
}

// 使用例
await autoFixAgent(
  "/path/to/project",
  "TypeScript のビルドエラーを修正して、全テストが通るようにして"
);

SDKMessage のハンドリングポイント

result メッセージの subtype で実行結果を正確に判別できる:

subtype 意味
success 正常完了
error_max_turns maxTurns に到達(ループ暴走の防止)
error_max_budget_usd コスト上限に到達
error_during_execution 実行中にエラー発生

total_cost_usdnum_turns を組み合わせれば、エージェントの効率性をモニタリングできる。CLI spawn で stderr を正規表現マッチしていた時代と比べると、構造化されたエラーハンドリングのありがたみを実感する。

CLI spawn と SDK、どちらを使うべきか — 判断フローチャート

CLI spawn が適するケース

  • 既存のシェルスクリプトやバッチ処理との統合
  • 単発のテキスト生成(ワンショット、結果だけ欲しい)
  • --dangerously-skip-permissions の手軽さを活かした自動化デーモン
  • Node.js 以外の言語(Python、Go 等)からの呼び出し

SDK が適するケース

  • マルチターン会話でセッション状態を維持したい
  • ツール権限をプログラム的にきめ細かく制御したい
  • total_cost_usd でコストを追跡・制限したい
  • MCP カスタムツールをインプロセスで統合したい
  • 型安全なメッセージハンドリングで保守コストを下げたい

ハイブリッド構成という選択肢

既存の CLI spawn パイプラインを一度にすべて書き換える必要はない。メインのジョブキューは CLI spawn を維持しつつ、権限制御やマルチターンが必要な新パイプラインだけ SDK に移行する段階的アプローチが現実的だ:

code
判断フロー:
┌─ マルチターン会話が必要? ──── Yes ──→ SDK
├─ ツール権限の細かい制御が必要? ─ Yes ──→ SDK
├─ コスト追跡・上限設定が必要? ── Yes ──→ SDK
├─ MCP カスタムツールを使う? ─── Yes ──→ SDK
└─ すべて No ──────────────────────────→ CLI spawn で十分

SDK は内部で CLI を spawn しているため、パフォーマンスの差はほぼない。選定基準は「構造化された制御が必要かどうか」の一点に集約される。

まとめ — SDK で広がるエージェント構築の可能性

Claude Agent SDK は、CLI の spawn ラッパーを書く時代の次のステップだ。型安全なメッセージング・宣言的な権限制御・ネイティブなコスト追跡・セッション管理を、追加のパース処理なしで手に入れられる。

V2 API はまだ unstable_ プレフィックスが付いているが、マルチターンの開発体験は V1 と比較にならないほど改善される。本番環境では V1 で堅実に始めつつ、V2 の安定化を見据えて設計しておくのが現実的だろう。

まずは既存の CLI spawn パイプラインの1つを SDK に置き換えてみてほしい。allowedTools / disallowedTools / settingSources の3つの設定軸を実際に触ることで、「CLI のテキスト出力をパースしていた時間は何だったのか」と感じるはずだ。

もっと読む他の技術記事も読む