SpyBara
Go Premium

agent-sdk/custom-tools.md 2026-09-27 23:59 UTC to 2026-09-28 22:01 UTC

This page contains 6 additions and 6 deletions.

2026
Wed 9 22:58 Sat 12 03:02 Fri 18 23:58 Mon 28 22:59

Claude にカスタムツールを提供する

Claude Agent SDK のインプロセス MCP サーバーでカスタムツールを定義し、Claude が関数を呼び出し、API にアクセスし、ドメイン固有の操作を実行できるようにします。

カスタムツールは Agent SDK を拡張し、Claude が会話中に呼び出せる独自の関数を定義できるようにします。SDK のインプロセス MCP サーバーを使用すると、Claude にデータベース、外部 API、ドメイン固有のロジック、またはアプリケーションが必要とするその他の機能へのアクセスを提供できます。

クイックリファレンス

実行したい操作 方法
ツールを定義する Python では @tool、TypeScript では tool() を使用して、名前、説明、スキーマ、ハンドラーを指定します。カスタムツールを作成するを参照してください。
Claude にツールを登録する create_sdk_mcp_server / createSdkMcpServer でラップし、query() の mcpServers に渡します。カスタムツールを呼び出すを参照してください。
ツールを事前承認する 許可されたツールに追加します。許可されたツールを設定するを参照してください。
Claude のコンテキストから組み込みツールを削除する 必要な組み込みのみをリストする tools 配列を渡します。許可されたツールを設定するを参照してください。
Claude がツールを並列で呼び出せるようにする 副作用のないツールに readOnlyHint: true を設定します。ツール注釈を追加するを参照してください。
Claude が読むエラーメッセージを制御する isError: true を返して、生の例外をサーフェスする代わりにメッセージを作成します。エラーを処理するを参照してください。
画像またはファイルを返す コンテンツ配列で image または resource ブロックを使用します。画像とリソースを返すを参照してください。
マシン可読 JSON 結果を返す 結果に structuredContent を設定します。構造化データを返すを参照してください。
多くのツールにスケーリングする ツール検索を使用して、オンデマンドでツールを読み込みます。

カスタムツールを作成する

ツールは 4 つの部分で定義され、TypeScript の tool() ヘルパーまたは Python の @tool デコレーターに引数として渡されます。

  • 名前: Claude がツールを呼び出すために使用する一意の識別子。
  • 説明: ツールが何をするか。Claude はこれを読んで、ツールをいつ呼び出すかを決定します。
  • 入力スキーマ: Claude が提供する必要がある引数。TypeScript では常に Zod スキーマであり、ハンドラーの args は自動的に型付けされます。Python では、{"latitude": float} のような名前から型へのマッピングである dict であり、SDK が JSON Schema に変換します。Python デコレーターは、列挙型、範囲、オプションフィールド、またはネストされたオブジェクトが必要な場合、完全な JSON Schema dict も直接受け入れます。
  • ハンドラー: Claude がツールを呼び出すときに実行される非同期関数。検証された引数を受け取り、以下を含むオブジェクトを返す必要があります。
    • content(必須):結果ブロックの配列。各ブロックは "text"、"image"、"audio"、"resource"、または "resource_link" の type を持ちます。テキスト以外のブロックについては、画像とリソースを返すを参照してください。
    • structuredContent(オプション):結果を機械可読データとして保持する JSON オブジェクト。content と一緒に返されます。構造化データを返すを参照してください。
    • isError(オプション):ツール障害を通知するために true に設定して、Claude が対応できるようにします。エラーを処理するを参照してください。

ツールを定義した後、createSdkMcpServer(TypeScript)または create_sdk_mcp_server(Python)でサーバーにラップします。サーバーはアプリケーション内でインプロセスで実行され、別のプロセスとしては実行されません。

天気ツールの例

この例は get_temperature ツールを定義し、MCP サーバーにラップします。ツールのセットアップのみを行います。query に渡して実行するには、以下の カスタムツールを呼び出すを参照してください。

from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server


# Define a tool: name, description, input schema, handler
@tool(
"get_temperature",
"Get the current temperature at a location",
{"latitude": float, "longitude": float},
)
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 a content array - Claude sees this as the tool result
return {
"content": [
{
"type": "text",
"text": f"Temperature: {data['current']['temperature_2m']}°F",
}
]
}


# Wrap the tool in an in-process MCP server
weather_server = create_sdk_mcp_server(
name="weather",
version="1.0.0",
tools=[get_temperature],
)

完全なパラメーター詳細(JSON Schema 入力形式と戻り値の構造を含む)については、tool() TypeScript リファレンスまたは @tool Python リファレンスを参照してください。

カスタムツールを呼び出す

mcpServers オプション経由で query に作成した MCP サーバーを渡します。mcpServers のキーは各ツールの完全修飾名 mcp__{server_name}__{tool_name} の {server_name} セグメントになります。その名前を allowedTools にリストして、ツールが権限プロンプトなしで実行されるようにします。

これらのスニペットは、天気ツールの例の weatherServer を再利用して、特定の場所の天気について Claude に尋ねます。

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
options = ClaudeAgentOptions(
mcp_servers={"weather": weather_server},
allowed_tools=["mcp__weather__get_temperature"],
)

async for message in query(
prompt="What's the temperature in San Francisco?",
options=options,
):
# ResultMessage is the final message after all tool calls complete
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)


asyncio.run(main())

このスニペットを 天気ツールの例のツールとサーバー定義と 1 つのファイルに組み合わせ、Python の場合は python weather.py で、TypeScript の場合は npx tsx weather.ts で実行します。Claude は get_temperature を呼び出し、スクリプトはサンフランシスコの現在の気温を含む 1 行の回答を出力します。

さらにツールを追加する

サーバーは tools 配列にリストされているのと同じ数のツールを保持します。サーバーに複数のツールがある場合、allowedTools で各ツールを個別にリストするか、ワイルドカード mcp__weather__* を使用してサーバーが公開するすべてのツールをカバーできます。

以下の例は 2 番目のツール get_precipitation_chance を定義し、天気ツールの例の weatherServer 定義を、配列内の両方のツールをリストするものに置き換えます。

# Define a second tool for the same server
@tool(
"get_precipitation_chance",
"Get the hourly precipitation probability for a location. "
"Optionally pass 'hours' (1-24) to control how many hours to return.",
{"latitude": float, "longitude": float},
)
async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]:
# 'hours' isn't in the schema - read it with .get() to make it optional
hours = args.get("hours", 12)
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.open-meteo.com/v1/forecast",
params={
"latitude": args["latitude"],
"longitude": args["longitude"],
"hourly": "precipitation_probability",
"forecast_days": 1,
},
)
data = response.json()
chances = data["hourly"]["precipitation_probability"][:hours]

return {
"content": [
{
"type": "text",
"text": f"Next {hours} hours: {'%, '.join(map(str, chances))}%",
}
]
}


# Rebuild the server with both tools in the array
weather_server = create_sdk_mcp_server(
name="weather",
version="1.0.0",
tools=[get_temperature, get_precipitation_chance],
)

ツール検索はデフォルトで有効になっており、SDK MCP ツールを遅延させます。Claude は各ツールの名前をコンパクトなリストで表示し、必要に応じてその完全なスキーマを読み込みます。ツール検索が無効になっている場合、この配列内のすべてのツールは毎ターン、コンテキストウィンドウスペースを消費します。TypeScript では、tool() の extras 引数または createSdkMcpServer() のオプションで alwaysLoad: true を渡して、ツールの完全なスキーマを初期プロンプトに保持します。

ツール注釈を追加する

ツール注釈は、ツールの動作を説明するオプションのメタデータです。TypeScript の tool() ヘルパーの 5 番目の引数として、または Python の @tool デコレーターの annotations キーワード引数経由で渡します。すべてのヒントフィールドはブール値です。

フィールド デフォルト 意味
readOnlyHint false ツールは環境を変更しません。ツールを他の読み取り専用ツールと並列で呼び出せるかどうかを制御します。
destructiveHint true ツールは破壊的な更新を実行する可能性があります。情報提供のみ。
idempotentHint false 同じ引数での繰り返し呼び出しは追加の効果がありません。情報提供のみ。
openWorldHint true ツールはプロセス外のシステムに到達します。情報提供のみ。

注釈はメタデータであり、強制ではありません。readOnlyHint: true とマークされたツールでも、ハンドラーがそれを行う場合はディスクに書き込むことができます。注釈をハンドラーに正確に保つようにしてください。

この例は、天気ツールの例の get_temperature ツールに readOnlyHint を追加します。

from claude_agent_sdk import tool, ToolAnnotations


@tool(
"get_temperature",
"Get the current temperature at a location",
{"latitude": float, "longitude": float},
annotations=ToolAnnotations(
readOnlyHint=True
),  # Lets Claude batch this with other read-only calls
)
async def get_temperature(args):
return {"content": [{"type": "text", "text": "..."}]}

TypeScript または Python リファレンスで ToolAnnotations を参照してください。

ツールアクセスの制御

天気ツールの例は、サーバーを登録し、allowedTools にツールをリストしました。このセクションでは、複数のツールがある場合や組み込みツールを制限したい場合のアクセス範囲の設定方法について説明します。ツール名の構成方法については、カスタムツールの呼び出しを参照してください。

許可されたツールの設定

tools オプションと許可/禁止リストは、2 つのレイヤーに影響します。可用性はツールが Claude のコンテキストに表示されるかどうかを制御し、権限は Claude がツールを試みた後に呼び出しが承認されるかどうかを制御します。tools と単純名の disallowedTools エントリは可用性を変更します。allowedTools とスコープ付き disallowedTools ルールは権限を変更します。タスク追跡ツールの 1 つを allowedTools に名前を付けた場合、Claude Code もセッションをオプトインします。

オプション レイヤー 効果
tools: ["Read", "Grep"] 可用性 リストされた組み込みツールのみが Claude のコンテキストに含まれます。リストされていない組み込みツールは削除されます。MCP ツールは影響を受けません。
tools: [] 可用性 すべての組み込みツールが削除されます。Claude は MCP ツールのみを使用できます。
許可されたツール 権限 リストされたツールは権限プロンプトなしで実行されます。その他のリストされていないツールは利用可能なままです。呼び出しは権限フローを通じて行われます。
禁止されたツール 両方 "Bash" などの単純なツール名は、tools から省略するのと同じように、ツールを Claude のコンテキストから削除します。"Bash(rm *)" などのスコープ付きルールは、ツールをコンテキストに残し、記述されたとおり一致する呼び出しのみを拒否します。

組み込みツールを完全に削除するには、tools から省略するか、disallowedTools(Python: disallowed_tools)に単純名をリストします。どちらもツールをコンテキストから除外するため、Claude はそれを試みることはありません。スコープ付き disallowedTools ルールは一致する呼び出しをブロックしますが、ツールを表示したままにするため、Claude はそれを試みるターンを無駄にする可能性があります。評価順序の詳細については、権限の設定を参照してください。

エラーを処理する

ハンドラーエラーはエージェントループを停止しません。SDK のインプロセス MCP サーバーはキャッチされない例外をキャッチし、エラー結果として返すため、エラーをどのように報告するかが Claude が読む内容を決定します。クエリが失敗するかどうかではありません。

何が起こるか 結果
ハンドラーがキャッチされない例外をスロー MCP サーバーはそれをエラー結果に変換し、生の例外メッセージを含めます。Claude はそのメッセージを見て、エージェントループは続行します。
ハンドラーがエラーをキャッチして isError: true(TS)/ "is_error": True(Python)を返す Claude はあなたが作成したメッセージを見ます。生の例外が欠いているコンテキスト(どのリクエストが失敗したか、代わりに何を試すかなど)を追加できます。

どちらの場合でも Claude は再試行したり、別のツールを試したり、失敗を説明したりできます。生の例外メッセージが Claude が対応するのに十分でない場合は、自分でエラーをキャッチしてください。

以下の例は、ハンドラー内で 2 種類の失敗をキャッチし、Claude が読むエラーメッセージを作成します。200 以外の HTTP ステータスはレスポンスからキャッチされ、エラー結果として返されます。ネットワークエラーまたは無効な JSON は、周囲の try/except(Python)または try/catch(TypeScript)によってキャッチされ、エラー結果として返されます。どちらの場合でも Claude は、生の例外文字列の代わりに失敗を説明するメッセージを受け取ります。

import json
import httpx
from typing import Any
from claude_agent_sdk import tool


@tool(
"fetch_data",
"Fetch data from an API",
{"endpoint": str},  # Simple schema
)
async def fetch_data(args: dict[str, Any]) -> dict[str, Any]:
try:
async with httpx.AsyncClient() as client:
response = await client.get(args["endpoint"])
if response.status_code != 200:
# Return the failure as a tool result so Claude can react to it.
# is_error marks this as a failed call rather than odd-looking data.
return {
"content": [
{
"type": "text",
"text": f"API error: {response.status_code} {response.reason_phrase}",
}
],
"is_error": True,
}

data = response.json()
return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]}
except Exception as e:
# Composes the message Claude reads. An uncaught exception would
# reach Claude as the raw str(e) with no context.
return {
"content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}],
"is_error": True,
}

画像とリソースを返す

ツール結果の content 配列は、text、image、audio、resource、および resource_link ブロックを受け入れます。同じレスポンス内でこれらを混在させることができます。TypeScript では、SDK はオーディオブロックをディスクに保存し、Claude は保存されたファイルパスを含むテキストブロックを受け取ります。Python では、SDK はツール結果からオーディオブロックを削除し、警告をログに記録します。

Claude は各リソースリンクブロックをテキストブロックとして受け取ります。このテキストブロックには、リンクの名前、URI、および説明が含まれます。TypeScript では、アプリケーションはユーザーメッセージの tool_use_result 上で resourceLinks としてリンク自体も受け取ります。Python では、SDK はそれらを CLI が結果を見る前にテキストに平坦化するため、Python の resourceLinks キー はインプロセスツールに対して生成されることはありません。

画像

画像ブロックは、画像バイトをインラインで、base64 としてエンコードされた形式で保持します。URL フィールドはありません。URL に存在する画像を返すには、ハンドラー内でそれをフェッチし、レスポンスバイトを読み取り、返す前に base64 エンコードしてください。結果は視覚入力として処理されます。

フィールド 型 注記
type "image"
data string Base64 エンコードされたバイト。生の base64 のみ、data:image/...;base64, プレフィックスなし
mimeType string 必須。例えば image/png、image/jpeg、image/webp、image/gif
import base64
import httpx
from claude_agent_sdk import tool


# Define a tool that fetches an image from a URL and returns it to Claude
@tool("fetch_image", "Fetch an image from a URL and return it to Claude", {"url": str})
async def fetch_image(args):
async with httpx.AsyncClient() as client:  # Fetch the image bytes
response = await client.get(args["url"])

return {
"content": [
{
"type": "image",
"data": base64.b64encode(response.content).decode(
"ascii"
),  # Base64-encode the raw bytes
"mimeType": response.headers.get(
"content-type", "image/png"
),  # Read MIME type from the response
}
]
}

リソース

リソースブロックは、URI で識別されるコンテンツを埋め込みます。URI はコンテンツの参照用ラベルです。実際のコンテンツはブロックの text または blob フィールドに含まれます。これは、ツールが後で名前で参照することが理にかなったもの(生成されたファイルや外部システムのレコードなど)を生成する場合に使用します。

フィールド 型 注記
type "resource"
resource.uri string コンテンツの識別子。任意の URI スキーム
resource.text string テキストの場合のコンテンツ。これまたは blob を提供します。両方ではなく
resource.blob string バイナリの場合、base64 エンコードされたコンテンツ。TypeScript のみ:Python SDK はバイナリリソースをツール結果から削除し、警告をログに記録します
resource.mimeType string オプション

この例は、ツールハンドラー内から返されるリソースブロックを示しています。URI file:///tmp/report.md は Claude が後で参照できるラベルです。SDK はそのパスから読み取りません。

return {
content: [
{
type: "resource",
resource: {
uri: "file:///tmp/report.md", // Label for Claude to reference, not a path the SDK reads
mimeType: "text/markdown",
text: "# Report\n..." // The actual content, inline
}
}
]
};

これらのブロック形状は MCP CallToolResult 型から来ています。完全な定義については、MCP 仕様を参照してください。

構造化データを返す

structuredContent は結果のオプションの JSON オブジェクトで、content 配列とは別です。テキスト文字列または画像から解析する代わりに、Claude が正確なフィールドとして読み取ることができる生の値を返すために使用します。

structuredContent が設定されている場合、Claude は JSON と content からのすべての画像またはリソースブロックを受け取ります。content のテキストブロックは転送されません。これらは構造化データを複製していると想定されているためです。以下の例は、チャートを画像ブロックとしてレンダリングし、同じハンドラーから structuredContent でその背後にあるデータポイントを返します。スニペットでは、chartPngBuffer はレンダリングされた PNG バイトを保持する Buffer です。

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]
  }
};

例:単位変換ツール

このツールは、長さ、温度、重さの単位間で値を変換します。ユーザーは「100 キロメートルをマイルに変換して」または「72°F は摂氏温度で何度ですか」と尋ねることができ、Claude はリクエストから正しい単位タイプと単位を選択します。

2 つのパターンを示しています:

  • Enum スキーマ: unit_type は固定値のセットに制限されます。TypeScript では、z.enum() を使用します。Python では、dict スキーマは enum をサポートしていないため、完全な JSON Schema dict が必要です。
  • サポートされていない入力の処理: 変換ペアが見つからない場合、ハンドラーは isError: true を返すため、Claude は失敗を通常の結果として扱うのではなく、ユーザーに何が間違ったかを伝えることができます。
from typing import Any
from claude_agent_sdk import tool, create_sdk_mcp_server


# z.enum() in TypeScript becomes an "enum" constraint in JSON Schema.
# The dict schema has no equivalent, so full JSON Schema is required.
@tool(
"convert_units",
"Convert a value from one unit to another",
{
"type": "object",
"properties": {
"unit_type": {
"type": "string",
"enum": ["length", "temperature", "weight"],
"description": "Category of unit",
},
"from_unit": {
"type": "string",
"description": "Unit to convert from, e.g. kilometers, fahrenheit, pounds",
},
"to_unit": {"type": "string", "description": "Unit to convert to"},
"value": {"type": "number", "description": "Value to convert"},
},
"required": ["unit_type", "from_unit", "to_unit", "value"],
},
)
async def convert_units(args: dict[str, Any]) -> dict[str, Any]:
conversions = {
"length": {
"kilometers_to_miles": lambda v: v * 0.621371,
"miles_to_kilometers": lambda v: v * 1.60934,
"meters_to_feet": lambda v: v * 3.28084,
"feet_to_meters": lambda v: v * 0.3048,
},
"temperature": {
"celsius_to_fahrenheit": lambda v: (v * 9) / 5 + 32,
"fahrenheit_to_celsius": lambda v: (v - 32) * 5 / 9,
"celsius_to_kelvin": lambda v: v + 273.15,
"kelvin_to_celsius": lambda v: v - 273.15,
},
"weight": {
"kilograms_to_pounds": lambda v: v * 2.20462,
"pounds_to_kilograms": lambda v: v * 0.453592,
"grams_to_ounces": lambda v: v * 0.035274,
"ounces_to_grams": lambda v: v * 28.3495,
},
}

key = f"{args['from_unit']}_to_{args['to_unit']}"
fn = conversions.get(args["unit_type"], {}).get(key)

if not fn:
return {
"content": [
{
"type": "text",
"text": f"Unsupported conversion: {args['from_unit']} to {args['to_unit']}",
}
],
"is_error": True,
}

result = fn(args["value"])
return {
"content": [
{
"type": "text",
"text": f"{args['value']} {args['from_unit']} = {result:.4f} {args['to_unit']}",
}
]
}


converter_server = create_sdk_mcp_server(
name="converter",
version="1.0.0",
tools=[convert_units],
)

サーバーが定義されたら、天気の例と同じ方法で query に渡します。この例は、同じツールが異なる単位タイプを処理することを示すために、ループで 3 つの異なるプロンプトを送信します。各レスポンスについて、AssistantMessage オブジェクト(Claude がそのターン中に行ったツール呼び出しを含む)を検査し、各 ToolUseBlock を出力してから最終的な ResultMessage テキストを出力します。これにより、Claude がツールを使用している場合と独自の知識から回答している場合を確認できます。

ツール検索はデフォルトで有効になっているため、出力には Claude が遅延ツールスキーマを読み込む際の ToolSearch 呼び出しも含まれる場合があります。

import asyncio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
ResultMessage,
AssistantMessage,
ToolUseBlock,
)


async def main():
options = ClaudeAgentOptions(
mcp_servers={"converter": converter_server},
allowed_tools=["mcp__converter__convert_units"],
)

prompts = [
"Convert 100 kilometers to miles.",
"What is 72°F in Celsius?",
"How many pounds is 5 kilograms?",
]

for prompt in prompts:
try:
async for message in query(prompt=prompt, options=options):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
print(f"[tool call] {block.name}({block.input})")
elif isinstance(message, ResultMessage) and message.subtype == "success":
print(f"Q: {prompt}\nA: {message.result}\n")
except Exception as error:
# A single-shot query() raises after yielding an error result. Only success
# results are printed above, so handle the failure here and continue with
# the next prompt.
print(f"Call failed: {error}")


asyncio.run(main())

次のステップ

このページのパターンを同じサーバー内で組み合わせることができます。単一のサーバーは、データベースツール、API ゲートウェイツール、画像レンダラーを並行して保持できます。

ここから:

  • サーバーが数十個のツールに成長する場合は、ツール検索を参照して、Claude がそれらを必要とするまで読み込みを遅延させてください。
  • 独自に構築する代わりに外部 MCP サーバー(ファイルシステム、GitHub、Slack)に接続するには、MCP サーバーを接続を参照してください。
  • どのツールが自動的に実行されるか、または承認が必要かを制御するには、権限を設定を参照してください。