Claude Agent SDK(TypeScript)実践ガイド — CLI spawn から SDK ネイティブへ、自律エージェント構築の全手順
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.subtype で error_max_turns / error_max_budget_usd 等を型判別 |
CLI spawn パターンは fire-and-forget には手軽だが、構造化された制御が必要になった時点で SDK への移行を検討する価値がある。
セットアップとインストール
インストールと前提条件
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行で動く:
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 パスの変更のみ:
npm uninstall @anthropic-ai/claude-code
npm install @anthropic-ai/claude-agent-sdk
- 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 を渡す:
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。マルチターンが圧倒的に楽になる:
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() がさらに簡潔:
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);
選定フローチャート
マルチターン会話が必要?
├─ 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 を読ませるには明示的な指定が必須:
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 | 高 |
プログラムで直接指定した allowedTools や systemPrompt は常に最優先される。
permissionMode × allowedTools × disallowedTools の評価順序
権限の評価は以下の5段階で行われる:
1. Hooks(PreToolUse) → ブロック可。最優先
2. disallowedTools → bypassPermissions でも有効。絶対拒否
3. permissionMode → default / acceptEdits / bypassPermissions / dontAsk
4. allowedTools → 事前承認リスト(制限リストではない)
5. canUseTool コールバック → プログラム的な動的判定
よくある罠: allowedTools は「これだけ許可する」リストではなく「これは事前に承認済み」リストだ。allowedTools: ["Read"] と指定しても、permissionMode: "bypassPermissions" であれば Read 以外のツールも実行される。
ツールを本当に制限したい場合は disallowedTools と permissionMode: "dontAsk" を組み合わせる:
// Read 以外のツールを拒否する場合の正しい設定
const options = {
permissionMode: "dontAsk",
allowedTools: ["Read", "Glob", "Grep"],
disallowedTools: ["Edit", "Write", "Bash", "NotebookEdit"],
};
本番向け権限設計パターン
// パターン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 サーバーを宣言的に接続できる:
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 ツールとして公開できる:
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 で実装する:
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_usd と num_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 に移行する段階的アプローチが現実的だ:
判断フロー:
┌─ マルチターン会話が必要? ──── 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 のテキスト出力をパースしていた時間は何だったのか」と感じるはずだ。
