本文へスキップ
Claude Media
Agent SDK入門 — Python / TypeScriptで最小エージェントを組む

Agent SDK入門 — Python / TypeScriptで最小エージェントを組む

Claude Agent SDKのPython / TypeScript版で最小カスタムエージェントを動かす入門。query・Custom Tool・Permission・Sessionの最短経路と言語別の使い分け早見表。

読了目安 約35

要点

Claude Agent SDKは、Claude Codeの裏側で動くエージェントループを自前アプリに組み込むための公式SDKです。Pythonの claude-agent-sdk とTypeScriptの @anthropic-ai/claude-agent-sdk が提供され、どちらも query() 一発で最小エージェントが動きます。以下、インストールからCustom Toolの定義、Permission Mode、Sessionの継続までを両言語で通しで組み、最後にPythonとTypeScriptの使い分け早見表を置きます。

この記事で学べること

  • claude-agent-sdk / @anthropic-ai/claude-agent-sdk のインストールと最小実行
  • @tool / tool() でのカスタムツール定義と createSdkMcpServer / create_sdk_mcp_server でのバンドル
  • permissionModeallowedTools / disallowedTools による安全側のデフォルト設計
  • ClaudeSDKClient(Python)と continue: true(TypeScript)によるセッション継続
  • どちらを選ぶべきかの用途別早見表

前段の設定レイヤに当たるCLAUDE.mdの書き方や、実行時フックによる自動化を担うHooksの設定方法と組み合わせると、作ったエージェントの挙動をプロジェクト単位で縛れます。

前提条件

  • Node.js 20以上、またはPython 3.10以上
  • ANTHROPIC_API_KEY 環境変数が設定済み、またはClaudeサブスクリプション経由の認証が通っている
  • TypeScript側は zod が必要(スキーマ定義に使用)
  • エディタから実行する場合は、プロジェクトのカレントディレクトリが重要(セッションはこのパス配下に保存される)

インストールはそれぞれ1コマンドです。

install.sh
# Python
pip install claude-agent-sdk
 
# TypeScript
npm install @anthropic-ai/claude-agent-sdk zod

Python版の最小実装

まずは query()async for で回すだけの最小例です。query() は1回限りのやり取りに向き、戻り値はメッセージのストリームになります。

agent_minimal.py
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import AssistantMessage, TextBlock, ResultMessage
 
async def main() -> None:
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Glob", "Grep"],
        permission_mode="default",
    )
 
    async for message in query(
        prompt="このディレクトリの TypeScript ファイルを 5 つまで挙げて",
        options=options,
    ):
        if isinstance(message, AssistantMessage):
            for block in message.content:
                if isinstance(block, TextBlock):
                    print(block.text)
        elif isinstance(message, ResultMessage) and message.subtype == "success":
            print(f"[done] cost={message.total_cost_usd}")
 
asyncio.run(main())

ポイントは3つです。

  • ClaudeAgentOptions でツール許可とPermission Modeをまとめて渡す
  • メッセージは型で分岐(AssistantMessage / ResultMessage など)
  • ResultMessage が来たら1ターンが完全に終わった合図

TypeScript版の最小実装

TypeScript側もAPIの形はほぼ同じです。query({ prompt, options })for await で回します。

agent-minimal.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
 
async function main() {
  for await (const message of query({
    prompt: "このディレクトリの TypeScript ファイルを 5 つまで挙げて",
    options: {
      model: "claude-opus-4-1",
      maxTurns: 5,
      permissionMode: "default",
      allowedTools: ["Read", "Glob", "Grep"],
    },
  })) {
    if (message.type === "assistant") {
      for (const block of message.message.content) {
        if (block.type === "text") console.log(block.text);
      }
    } else if (message.type === "result" && message.subtype === "success") {
      console.log(`[done] cost=${message.total_cost_usd}`);
    }
  }
}
 
main();

TypeScript版は戻り値が Query 型で、interrupt()setPermissionMode() などのメソッドも生えています。ループ途中で許可方針を切り替えたい時はこのメソッドを使います。

Custom Toolの作り方

Agent SDKの強みは、自前関数をMCPツール互換でClaudeに渡せる点です。Pythonは @tool デコレータ、TypeScriptは tool() ヘルパーで定義し、それを create_sdk_mcp_server / createSdkMcpServer でまとめてから query に渡します。

Python版の例です。

weather_tool.py
from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server
 
@tool(
    "get_temperature",
    "指定した緯度経度の現在気温を取得する",
    {"latitude": float, "longitude": float},
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
    async with httpx.AsyncClient() as client:
        res = await client.get(
            "https://api.open-meteo.com/v1/forecast",
            params={
                "latitude": args["latitude"],
                "longitude": args["longitude"],
                "current": "temperature_2m",
            },
        )
    data = res.json()
    return {
        "content": [
            {"type": "text", "text": f"気温: {data['current']['temperature_2m']}°C"}
        ]
    }
 
weather_server = create_sdk_mcp_server(
    name="weather",
    version="1.0.0",
    tools=[get_temperature],
)

TypeScript版はスキーマをZodで書くので、ハンドラ側の引数型は自動で付きます。

weather-tool.ts
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
 
const getTemperature = tool(
  "get_temperature",
  "指定した緯度経度の現在気温を取得する",
  {
    latitude: z.number().describe("緯度"),
    longitude: z.number().describe("経度"),
  },
  async ({ latitude, longitude }) => {
    const res = await fetch(
      `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m`,
    );
    const data: any = await res.json();
    return {
      content: [{ type: "text", text: `気温: ${data.current.temperature_2m}°C` }],
    };
  },
  { annotations: { readOnlyHint: true } },
);
 
export const weatherServer = createSdkMcpServer({
  name: "weather",
  version: "1.0.0",
  tools: [getTemperature],
});

登録したツールを呼ぶときは mcpServers に名前付きで渡し、フルネーム mcp__weather__get_temperatureallowedTools に入れます。これで承認ダイアログ抜きで実行されます。readOnlyHint: true を付けておくと、他の読み取り専用ツールと並列実行できるようになります。MCPサーバーの作り方そのものを深掘りしたい場合はMCP実践ガイドに独立した手順があります。

ツール内でエラーを返したい時は、例外を投げるとループごと落ちるので、isError: true(Pythonは is_error: True)を付けた content を返すのが作法です。Claude側はエラーを「データ」として認識して、別のツールを試したり、ユーザーに説明を返したりします。

Permission / Session管理

Permissionは5段階のフローで評価されます。Hooks → Denyルール → Permission Mode → Allowルール → canUseTool コールバック、の順です。主要なモードは次の通りです。

モード挙動想定用途
default未承認ツールは canUseTool人間が近くにいるCLI
dontAsk未承認はすべて拒否(プロンプトなし)ヘッドレス・固定ツールセット
acceptEditsファイル編集と mkdir / rm / mv を自動承認信頼できる編集ループ
bypassPermissions全ツール自動承認(Denyルールだけは有効)隔離環境での無人実行
planツール実行せず計画のみ立てるレビュー前の設計フェーズ

注意点として、bypassPermissionsallowedTools で絞れません。未記載のツールも全部通ってしまうので、止めたいツールは disallowedTools に入れます。固定ツールセットのヘッドレス用途では allowedTools + permissionMode: "dontAsk" の組み合わせが安全側のデフォルトです。

Sessionは会話履歴のことで、~/.claude/projects/<エンコードされた cwd>/ 配下にJSONLで自動保存されます。同じプロセス内でマルチターンを続けたいだけなら、Pythonは ClaudeSDKClient を使うのが自然です。

session_python.py
import asyncio
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
 
async def main() -> None:
    options = ClaudeAgentOptions(allowed_tools=["Read", "Edit", "Glob", "Grep"])
    async with ClaudeSDKClient(options=options) as client:
        await client.query("auth モジュールを読んで設計上の問題点を挙げて")
        async for _ in client.receive_response():
            pass
        # ↑ と同じセッション
        await client.query("今指摘してくれた点のうち最優先のものを直して")
        async for _ in client.receive_response():
            pass
 
asyncio.run(main())

TypeScript版には同等のクライアントクラスが無いので、2回目以降の query()continue: true を渡します。SDKが同一 cwd の最新セッションを自動で引き継ぎます。

session-ts.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
 
await consume(query({ prompt: "auth モジュールを読んで問題点を挙げて" }));
await consume(
  query({
    prompt: "今指摘してくれた点のうち最優先のものを直して",
    options: { continue: true, allowedTools: ["Read", "Edit"] },
  }),
);
 
async function consume(q: AsyncIterable<unknown>) {
  for await (const _ of q) {
    // ドロップ
  }
}

特定セッションに戻りたい場合は ResultMessage.session_id を保存しておき、resume: sessionId で復帰します。並行して別案を試したい時は forkSession: true(Pythonは fork_session=True)で分岐コピーを作れます。

Python vs TypeScriptの使い分け

APIの形はほぼ揃っていますが、運用面で得意領域が分かれます。独自にまとめると次のようになります。

観点Pythonが向くケースTypeScriptが向くケース
実装スタイルデータ処理・ML / LLMパイプラインに組み込むWebフロント / Nodeバックエンド / エッジ関数
セッション管理ClaudeSDKClient でクライアント主導continue: true / resume で関数主導
型付けTypedDict + JSON Schema(enumはdict指定)Zodでコンパイル時に型生成
既存資産との接続httpx / pandas / LangChain / JupyterNext.js / tRPC / Vercel AI SDK / Hono
セッション永続化常にディスク保存persistSession: false でメモリのみも可
プレウォーム起動非対応(query は都度起動)startup() で事前ウォーム可能

迷ったら、バックエンドがTypeScript中心なら素直にTypeScript、ノートブック検証やデータ前処理が長い場合はPython、と割り切るのが早いです。両方を跨ぐ場合、カスタムツールのシグネチャはほぼ同形なので移植コストは低めです。

よくあるつまずき

resume したのに履歴が読まれない: 多くの場合、cwd が違います。セッションは ~/.claude/projects/<cwd をエンコードした名前>/*.jsonl に保存されるので、別ディレクトリから実行するとヒットしません。

allowedTools を指定しているのに全ツール動く: permissionMode: "bypassPermissions" になっていないか確認します。このモードでは allowedTools は無効で、disallowedTools だけが効きます。

Custom Toolの名前が見つからないと言われる: MCP経由の正式名は mcp__<server_name>__<tool_name> です。allowedTools に素の名前だけ書くと、ツールは存在しているのに承認されず無限ループに近い挙動になります。ワイルドカード mcp__weather__* も使えます。

ツール例外で query ごと落ちる: ハンドラ内の未捕捉例外はエージェントループを止めます。外側を try/except(TypeScriptは try/catch)で囲み、失敗時は is_error: True / isError: true を付けた結果を返します。

Pythonでenumスキーマが書けない: {"key": str} 形式はenumを表現できません。JSON Schemaをdictで直接渡すと {"type": "string", "enum": [...]} が書けます。

まとめ

Claude Agent SDKは「Claude Codeのエージェントループを関数として呼べるようにしたもの」と捉えると入りやすいです。最小構成は query() 一行、そこにCustom Toolを足して、Permission Modeで安全側に倒し、必要ならSessionを継続する、という順で積み上げれば、Python / TypeScriptどちらでも同じ設計で組めます。エージェントそのものの振る舞いを縛るときは、CLAUDE.mdの設計Hooksの挟み込みを組み合わせると、実行レイヤと設定レイヤの両側から制御できます。

この記事を共有:XLinkedIn