Claude Agent SDK のカスタムツールは、あなたのアプリ内の関数を Claude が会話の途中で呼び出せるようにするしくみです。SDK に組み込まれたインプロセスの MCP サーバーを使うので、別プロセスのサーバーを立てずに、データベースの照会、社内 API の呼び出し、業務固有の計算などをツールとして渡せます。
この記事では、公式ドキュメント「Give Claude custom tools」に沿って、TypeScript の tool() と Python の @tool によるツール定義、createSdkMcpServer(Python は create_sdk_mcp_server)での登録、allowedTools による事前承認、エラー・画像・構造化データの返し方までを整理します。外部の MCP サーバー(GitHub や Slack など)につなぐ方法は「Claude Code を MCP で外部ツールに接続する」も参考にしてください。
カスタムツールの4つの要素
ツールは次の4つで定義し、TypeScript では tool() ヘルパーの引数、Python では @tool デコレータの引数として渡します。
| 要素 | 内容 |
|---|---|
| 名前 | Claude がツールを呼ぶときの一意な識別子(例 get_temperature)。 |
| 説明 | ツールが何をするか。Claude はこれを読んで、いつ呼ぶかを判断します。 |
| 入力スキーマ | TypeScript は Zod のスキーマ(.describe() で各フィールドの説明を付けられる)。Python は {"latitude": float} のような型の辞書で、SDK が JSON Schema に変換します。Annotated[float, "Latitude coordinate"] と書けば説明も付き、列挙値や範囲指定が要るときは JSON Schema の辞書をそのまま渡せます。 |
| ハンドラ | Claude が呼んだときに実行される非同期関数。検証済みの引数を受け取り、結果オブジェクトを返します。 |
ハンドラの返り値は、必須の content(text・image・audio・resource・resource_link のいずれかの type を持つブロックの配列)に、任意の structuredContent(機械可読な JSON)と isError(失敗を示すフラグ)を加えた形です。
公式の例は、Open-Meteo の API から現在の気温を取ってくる get_temperature ツールです(TypeScript 版を抜粋)。
import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
const getTemperature = tool(
"get_temperature",
"Get the current temperature at a location",
{
latitude: z.number().describe("Latitude coordinate"),
longitude: z.number().describe("Longitude coordinate")
},
async (args) => {
const response = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}¤t=temperature_2m&temperature_unit=fahrenheit`
);
const data: any = await response.json();
return {
content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }]
};
}
);
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [getTemperature]
});
Python では同じものを次のように書きます(HTTP 呼び出しには httpx を使っています)。
from typing import Annotated, Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server
@tool(
"get_temperature",
"Get the current temperature at a location",
{
"latitude": Annotated[float, "Latitude coordinate"],
"longitude": Annotated[float, "Longitude coordinate"],
},
)
async def get_temperature(args: dict[str, Any]) -> dict[str, Any]:
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": args["latitude"],
"longitude": args["longitude"],
"current": "temperature_2m",
"temperature_unit": "fahrenheit",
},
)
data = response.json()
return {"content": [{"type": "text", "text": f"Temperature: {data['current']['temperature_2m']}°F"}]}
weather_server = create_sdk_mcp_server(name="weather", version="1.0.0", tools=[get_temperature])
作ったサーバーはアプリと同じプロセスの中で動きます。別プロセスとして起動する必要はありません。
サーバーに登録して query() から呼ぶ
サーバーは query() の mcpServers(Python は mcp_servers)オプションに渡します。このときのキー名が、ツールの完全修飾名 mcp__{サーバー名}__{ツール名} のサーバー名部分になります。その名前を allowedTools に入れておくと、権限の確認なしでツールが実行されます。
import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({
prompt: "What's the temperature in San Francisco?",
options: {
mcpServers: { weather: weatherServer },
allowedTools: ["mcp__weather__get_temperature"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
Python では ClaudeAgentOptions(mcp_servers={"weather": weather_server}, allowed_tools=["mcp__weather__get_temperature"]) を渡し、ResultMessage の subtype が "success" のものを取り出します。ツール定義と同じファイルにまとめ、TypeScript なら npx tsx weather.ts、Python なら uv run weather.py(または仮想環境で python weather.py)で実行できます。
1つのサーバーに複数のツールを載せる
サーバーの tools 配列にはいくつでもツールを並べられます。allowedTools には1つずつ書くほか、mcp__weather__* のようなワイルドカードでそのサーバーの全ツールをまとめて許可できます。
なお、ツール検索は既定で有効で、SDK の MCP ツールは名前だけが一覧で見え、詳細なスキーマは必要になったときに読み込まれます。ツール検索を無効にすると、配列内の全ツールが毎ターンのコンテキストを消費します。TypeScript では tool() の5番目の引数(extras)や createSdkMcpServer() のオプションに alwaysLoad: true を渡すと、そのツールのスキーマを最初のプロンプトに常に載せておけます。
TypeScript の createSdkMcpServer() は、ほかに instructions(モデルに渡すサーバーの説明)と timeout(このサーバーのツール呼び出しのタイムアウト。ミリ秒・1000以上の整数。SDK v0.3.248 以降)も受け付けます。
省略可能な引数とアノテーション
引数を省略可能にする
省略可能な引数は、スキーマで省略可能と宣言し、既定値はハンドラ側で補います。
- TypeScript … Zod のフィールドに
.optional()を付け、ハンドラでargs.hours ?? 12のように既定値を入れます。 - Python … 型の辞書形式ではすべてのキーが必須になります。JSON Schema 形式で書いて
requiredから外し、ハンドラではargs.get("hours", 12)で読みます。
@tool(
"get_precipitation_chance",
"Get the hourly precipitation probability for a location",
{
"type": "object",
"properties": {
"latitude": {"type": "number"},
"longitude": {"type": "number"},
"hours": {"type": "integer", "minimum": 1, "maximum": 24,
"description": "How many hours of forecast to return"},
},
"required": ["latitude", "longitude"],
},
)
async def get_precipitation_chance(args):
hours = args.get("hours", 12)
...
列挙値(enum)も同じで、TypeScript は z.enum([...])、Python は型の辞書形式では書けないため JSON Schema の辞書を使います。
ツールアノテーション
MCP のツールアノテーションは、ツールの振る舞いを示す任意のメタデータです。TypeScript では tool() の5番目の引数に { annotations: { ... } }、Python では @tool の annotations=ToolAnnotations(...) で渡します。
| フィールド | 既定値 | 意味 |
|---|---|---|
readOnlyHint | false | 環境を変更しないツール。ほかの読み取り専用ツールと並列に呼べるかがこれで決まります。 |
destructiveHint | true | 破壊的な更新をし得る(情報のみ)。 |
idempotentHint | false | 同じ引数で繰り返し呼んでも追加の影響がない(情報のみ)。 |
openWorldHint | true | プロセス外のシステムに触れる(情報のみ)。 |
tool(
"get_temperature",
"Get the current temperature at a location",
{ latitude: z.number(), longitude: z.number() },
async (args) => ({ content: [{ type: "text", text: `...` }] }),
{ annotations: { readOnlyHint: true } }
);
アノテーションは宣言であって強制ではありません。readOnlyHint: true を付けても、ハンドラがディスクに書けば書き込みは起きます。ハンドラの実際の動きと一致させておきます。
ツールの利用範囲を制御する
tools オプションと許可・拒否リストは、2つの層に効きます。利用可能性(ツールが Claude のコンテキストに見えるか)と、権限(呼ばれたときに承認されるか)です。
| オプション | 層 | 効果 |
|---|---|---|
tools: ["Read", "Grep"] | 利用可能性 | 列挙した組み込みツールだけがコンテキストに残ります。MCP ツールには影響しません。 |
tools: [] | 利用可能性 | 組み込みツールをすべて外し、Claude は自作の MCP ツールだけを使えます。 |
allowedTools | 権限 | 列挙したツールは確認なしで実行。列挙していないツールも使えますが、通常の権限フローを通ります。 |
disallowedTools | 両方 | "Bash" のような名前だけの指定はツールをコンテキストから外します。"Bash(rm *)" のようなスコープ付きの指定はツールを残したまま一致する呼び出しだけを拒否します。 |
組み込みツールを完全に使わせたくないときは、tools から外すか、disallowedTools に名前だけで書きます。スコープ付きの拒否ルールだとツール自体は見えたままなので、Claude が試して1ターン無駄にすることがあります。評価順の全体は「Agent SDK の権限設定」で解説しています。
エラーの返し方
ハンドラでエラーが起きてもエージェントのループは止まりません。インプロセスの MCP サーバーが未処理の例外を捕まえてエラー結果に変換するためです。違いが出るのは「Claude が何を読むか」です。
| 起きること | 結果 |
|---|---|
| ハンドラが例外を投げたまま | 例外メッセージがそのままエラー結果になり、Claude はそれを読んでループが続きます。 |
ハンドラが捕まえて isError: true(Python は "is_error": True)を返す | Claude はあなたが組み立てたメッセージを読みます。どのリクエストが失敗したか、代わりに何を試すべきかといった文脈を足せます。 |
どちらの場合も、Claude は再試行したり、別のツールを使ったり、失敗を説明したりできます。生の例外メッセージだけでは Claude が次の行動を決めにくいときに、自分で捕まえて返します。
async (args) => {
try {
const response = await fetch(args.endpoint);
if (!response.ok) {
return {
content: [{ type: "text", text: `API error: ${response.status} ${response.statusText}` }],
isError: true
};
}
const data = await response.json();
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
} catch (error) {
return {
content: [{ type: "text", text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
}
公式の単位変換ツールの例でも、対応していない単位の組み合わせが来たときは isError: true で「Unsupported conversion」を返し、失敗を普通の結果として扱わせないようにしています。
画像・リソース・構造化データを返す
画像
content 配列には複数の種類のブロックを混ぜられます。画像ブロックはバイト列を base64 で埋め込む形で、URL を渡すフィールドはありません。URL の画像を返したいときは、ハンドラで取得して base64 に変換します。
| フィールド | 内容 |
|---|---|
type | "image" |
data | base64 文字列。data:image/...;base64, の接頭辞は付けない |
mimeType | 必須。image/png・image/jpeg・image/webp・image/gif など |
PNG・JPEG・GIF・WebP は画像として Claude に渡ります。それ以外の形式はディスクに保存され、Claude にはファイルパスがテキストで渡ります。
リソースとリソースリンク
リソースブロックは、URI で識別される内容を resource.text(テキスト)か resource.blob(base64 のバイナリ)に入れて返します。URI は Claude が参照するためのラベルで、SDK がそのパスを読みに行くわけではありません。blob は TypeScript のみで、Python SDK はバイナリのリソースを落として警告を出します。resource_link ブロックは、名前・URI・説明を含むテキストとして Claude に渡ります。
音声
音声ブロックは、TypeScript では SDK がディスクに保存して Claude にはそのパスがテキストで渡り、Python では結果から落とされて警告が出ます。
構造化データ(structuredContent)
structuredContent は content とは別の JSON オブジェクトで、Claude が文字列や画像から値を読み取らなくても、正確なフィールドとして受け取れるようにします。設定すると、Claude には JSON と content 内の画像・リソースブロックが渡り、content 内のテキストブロックは転送されません(構造化データと重複するとみなされるため)。
return {
content: [{ type: "image", data: chartPngBuffer.toString("base64"), mimeType: "image/png" }],
structuredContent: {
series: "temperature_2m",
unit: "fahrenheit",
points: [62.1, 63.4, 65.0, 64.2]
}
};
Python の @tool デコレータは、返り値のうち content と is_error しか転送しません。Python で structuredContent を返したい場合は、インプロセスの SDK サーバーではなく独立した MCP サーバーを使います。
ツールが数十個に増えたらツール検索で必要なときだけ読み込む、既存の外部 MCP サーバーを使うなら MCP 接続、自動実行と承認の線引きは権限設定、と公式は次の参照先を示しています。1つのサーバーにデータベース用・API 用・画像生成用のツールを混在させることもできます。
本ページは Give Claude custom tools の内容に基づく非公式の日本語解説で、tool() と createSdkMcpServer() の引数は Agent SDK reference - TypeScript と照合しています(確認日 2026-10-13)。関数名・フィールド名は公式表記のまま記載しています。最新の仕様は公式ドキュメントを確認してください。