Tools - ツールの定義と実行
MCP の中心機能である Tools について、定義・実行・エラー処理・メタデータの仕様を解説します。ツールの設計指針 (粒度、命名、description の書き方) は ツール設計パターンとアンチパターン で扱い、ここではプロトコルが定める形式と振る舞いに絞ります。サーバー側の機能全体の位置づけは MCP入門、サーバーが処理の途中でユーザー入力を求める仕組みは ElicitationとMulti Round-Trip Requests を参照してください。
1. このドキュメントで扱うこと
- tools/list と tools/call
- ツール定義の構成要素
- ツール名の形式
- JSON Schema 2020-12
- 結果の種類
- エラーの二層構造
- アノテーション
- 人間による確認
- list_changed と動的なツール
tools/list と tools/call
Tools はモデル主導 (model-controlled) の機能です。サーバーがツールの一覧を公開し、モデルがユーザーの依頼と文脈に応じて呼び出すツールを選びます。プロトコルはユーザーインターフェースの形を強制しませんが、人間が拒否できる余地を残すことを求めています (後述)。
ツールを公開するサーバーは、tools capability を宣言します。listChanged はツール一覧の変更通知を送るかどうかを示します。
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
クライアントは tools/list で利用可能なツールを取得し、tools/call で実行します。
tools/list には、次の決まりがあります。
- ページネーション:
cursorとnextCursorによるカーソルベースのページネーションに対応します。ページサイズはサーバーが決めます (詳細は Resources・Prompts・共通ユーティリティ) - キャッシュ: 結果に
ttlMs(ミリ秒) とcacheScope(public/private) を含めなければなりません。クライアントはこの間、一覧を再取得せずに使えます - 決定的な順序: 基になるツールの集合が変わっていない限り、同じ順序で返すことが推奨されます。クライアントがキャッシュしやすくなり、ツール定義をモデルのコンテキストに入れる場合のプロンプトキャッシュも効きやすくなります
- 接続ごとに変えない: 一覧は、接続や他のリクエストの副作用で変わってはいけません。変えてよいのは、リクエストに付いた認可情報 (たとえば付与されたスコープ) に応じたものだけです。2026-07-28 版はセッションを持たないため、認証情報は接続の状態ではなく、リクエストごとの入力として扱われます
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"tools": [
{
"name": "get_weather",
"title": "Weather Information Provider",
"description": "Get current weather information for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": { "type": "string", "description": "City name or zip code" }
},
"required": ["location"]
}
}
],
"nextCursor": "next-page-cursor",
"ttlMs": 300000,
"cacheScope": "public"
}
}
tools/call は name と arguments を送り、結果を受け取ります。結果には resultType: "complete" が付きます。サーバーは、処理の途中でユーザー入力が必要になった場合に限り、結果の代わりに resultType: "input_required" を返せます (Multi Round-Trip Requests。詳細は ElicitationとMulti Round-Trip Requests)。このとき、クライアントが再試行するリクエストの JSON-RPC id は、最初のリクエストとは別の値にします。
ツール定義の構成要素
ツール定義は、次のフィールドで構成されます。
| フィールド | 必須 | 内容 |
|---|---|---|
name | はい | ツールの一意な識別子 |
title | いいえ | 表示用の人間向けの名前 |
description | いいえ | 機能の説明。モデルがツールを選ぶ主な手がかり |
icons | いいえ | UI に表示するアイコンの配列 |
inputSchema | はい | 引数を定める JSON Schema。type: "object" が必要 |
outputSchema | いいえ | 構造化された結果を定める JSON Schema |
annotations | いいえ | ツールの振る舞いに関するヒント |
_meta | いいえ | 拡張などが使うメタデータ |
引数がないツールの inputSchema は、{ "type": "object", "additionalProperties": false } が推奨されています。空のオブジェクトだけを受け付けることを明示できるためです。{ "type": "object" } は任意のオブジェクトを受け付けます。null は使えません。
name はプログラムが使う識別子、title は画面に出す名前です。description はモデルのコンテキストに入る、モデル向けの文章です。何をするか、いつ使うか、使ってはいけない場面を書きます。
inputSchema のプロパティには、x-mcp-header という拡張キーを付けられます。Streamable HTTP で、その引数の値を Mcp-Param-{name} という HTTP ヘッダーにも複製させ、ロードバランサーや WAF がリクエストの本文を解析せずに振り分けられるようにするものです。ヘッダーは中継するネットワーク機器から見えるため、パスワード、API キー、トークン、個人情報を示す引数には付けません。詳細は トランスポート - stdioとStreamable HTTP を参照してください。
ツール名の形式
ツール名には、次の推奨があります (SEP-986 を取り込んだ現行の仕様)。
- 長さは 1 文字以上 128 文字以下
- 大文字と小文字を区別する
- 使える文字は、英字 (A-Z、a-z)、数字 (0-9)、アンダースコア (
_)、ハイフン (-)、ドット (.) - スペース、カンマ、その他の特殊文字は使わない
- 1 つのサーバーの中で一意にする
有効な名前の例は getUser、DATA_EXPORT_v2、admin.tools.list です。ドットで名前空間を表現できます。
一意性が保証されるのは、1 つのサーバーの中だけです。複数のサーバーのツールをまとめるクライアントやプロキシでは、たとえば 2 つのサーバーが search を公開すると衝突します。その場合は、サーバー識別子を接頭辞に付けるなどの方法で区別します。serverInfo の name は、サーバー間で一意とは限らないため、区別の根拠にしてはいけません。
SEP-986 の初期の提案には、スラッシュ (/) の許可と 64 文字の上限が含まれていました。ここでは、現行の 2026-07-28 版の Tools ページの記述 (128 文字、/ なし) に従っています。
JSON Schema 2020-12
inputSchema と outputSchema は、$schema がなければ JSON Schema 2020-12 として扱います (SEP-2106)。他の方言を使うときは $schema で明示します。
inputSchema: ルートはtype: "object"が必要ですが、anyOf、oneOf、allOf、not、if/then/else、$ref、$defsなど、2020-12 のキーワードを使えますoutputSchema: オブジェクトに限らず、配列やプリミティブなど任意の JSON Schema を書けます
たとえば「ID または名前のどちらか」を受け付けるツールは、oneOf で表現できます。
{
"type": "object",
"oneOf": [
{ "properties": { "id": { "type": "string" } }, "required": ["id"] },
{ "properties": { "name": { "type": "string" } }, "required": ["name"] }
]
}
表現力が増えた分、スキーマを処理する側に防御が求められます。仕様と SEP-2106 は、次のことを求めています。
$refを勝手にネットワーク取得しない:#/$defs/Fooのようなドキュメント内の参照以外 (外部 URI) を、通常の検証の一部として自動的に解決してはいけません。悪意あるツール定義が、サーバーサイドリクエストフォージェリー (SSRF) や取得の増幅に使われるためです。解決できない外部参照を含むスキーマは、緩い扱いにせず拒否します- 合成キーワードの上限:
anyOf、oneOf、allOf、$defsの組み合わせは、検証の計算量を爆発させ得ます。スキーマの深さ、サブスキーマの総数、1 回の検証の時間に上限を設けます
結果の種類
ツールの結果は、構造化されていない内容 (content) と、構造化された内容 (structuredContent) で返せます。
content は、次の種類の項目の配列です。
type | 内容 |
|---|---|
text | テキスト |
image | base64 の画像データと mimeType |
audio | base64 の音声データと mimeType |
resource_link | リソースへのリンク (uri、name など)。クライアントが取得や購読に使える |
resource | リソースの内容そのものの埋め込み |
resource_link が指すリソースは、resources/list の結果に現れるとは限りません。どの項目にも、対象者 (audience)、優先度 (priority)、更新日時 (lastModified) の annotations を付けられます。
structuredContent は、任意の JSON 値 (オブジェクト、配列、文字列、数値、真偽値、null) です。outputSchema があれば、サーバーはそれに適合する結果を返さなければならず、クライアントは検証することが推奨されます。後方互換のため、構造化された結果を返すツールは、同じ JSON を文字列にして text の項目にも入れることが推奨されています。
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"resultType": "complete",
"content": [
{ "type": "text", "text": "{\"temperature\": 22.5, \"conditions\": \"Partly cloudy\", \"humidity\": 65}" }
],
"structuredContent": { "temperature": 22.5, "conditions": "Partly cloudy", "humidity": 65 }
}
}
structuredContent は、LLM の「構造化出力」(スキーマで制約したモデルの生成) とは無関係で、サーバーが作る結果のデータです。
エラーの二層構造
エラーの返し方は、2 つの層に分かれます。詳細は次の「エラーの種類と返し方」を参照してください。
アノテーション
annotations は、ツールの振る舞いに関するヒントです。
| フィールド | 既定値 | 意味 |
|---|---|---|
title | なし | 人間向けのタイトル |
readOnlyHint | false | true なら、環境を変更しない |
destructiveHint | true | false なら、追加のみの更新 (破壊的でない)。readOnlyHint が false のときだけ意味を持つ |
idempotentHint | false | true なら、同じ引数での繰り返しの呼び出しが、追加の効果を持たない。readOnlyHint が false のときだけ意味を持つ |
openWorldHint | true | true なら、外部の開かれた対象とやり取りする。false なら、閉じた領域だけを扱う |
既定値が「書き込む、破壊的、外部とやり取りする」側に倒れている点に注意してください。ヒントを付けない限り、クライアントはツールを危険なものと見なすのが安全な既定です。
仕様は、クライアントは、信頼できるサーバーから得たもの以外のアノテーションを信頼してはならない (MUST) としています。アノテーションは、サーバーの自己申告にすぎません。書き込むツールに readOnlyHint: true を付ける偽装の危険は AIエージェント特有の脅威 - プロンプトインジェクションとツール汚染 で扱っています。アノテーションは、UI の見せ方や確認ダイアログの文言を決める参考値として使い、確認の省略の根拠にはしません。
人間による確認
仕様は、信頼性と安全性のため、常に人間が介在し、ツールの実行を拒否できる状態にすることが推奨される (SHOULD) としています。アプリケーションには次のことが推奨されます。
- どのツールがモデルに公開されているかを、UI で分かるようにする
- ツールが呼ばれたときに、明確な表示を出す
- 人間が介在することを担保するため、操作の確認を求める
クライアントは、機微な操作の前に確認を求め、サーバーへ送る前にツールの入力を利用者に見せます。意図しない、あるいは悪意あるデータの持ち出しを避けるためです。サーバー側は、入力の検証、アクセス制御、呼び出し回数の制限 (レート制限)、出力のサニタイズが必須です。破壊的な操作 (削除、送信、デプロイ、支払い) は、アノテーションの値にかかわらず、利用者の確認を挟むのが基本です。
list_changed と動的なツール
ツールの集合は時間とともに変わってかまいません。listChanged: true を宣言したサーバーは、変更を notifications/tools/list_changed で通知することが推奨されます。2026-07-28 版は、通知の受け取りを subscriptions/listen に集約しました。クライアントが toolsListChanged: true を指定して subscriptions/listen を開いたときに、サーバーはその通知を送ります。
通知を受けたクライアントは、キャッシュしていた一覧を直ちに古いものとして扱い、tools/list を取得し直します。通知は ttlMs と併用できます。TTL で不要な再取得を避けつつ、通知で即座に無効化する、という使い分けです。
プロトコルにセッションはないため、サーバーは「この接続だけにこのツールを出す」ことができません。状態を持つツール (買い物かご、開いたブラウザーのコンテキスト、データベースのトランザクション) は、作成用のツールが明示的なハンドルを返し、後続の呼び出しがそれを引数で受け取る形にします。仕様の非規範的な指針では、次の点が挙げられています。
- 認可: 認証されたサーバーでは、ハンドルは「名前」であって権限ではない。呼び出しのたびに、呼び出し元がそのハンドルを使ってよいかを検証する。認証のないサーバーでは、ハンドルがベアラートークンの役割を担うため、十分なエントロピーで生成し、有効期間を設ける
- 不透明性: 内部構造を埋め込んだハンドルは、解析や推測を誘う
- 有効期間: 保持期間 (例: 「かごは 24 時間操作がなければ失効します」) を、作成用ツールの description に書く
- 失効時のエラー: 失効した、あるいは未知のハンドルへの呼び出しには、モデルが新しく作り直せるよう、そう伝えるツール実行エラーを返す
2. エラーの種類と返し方
| 種類 | 例 | 返し方 |
|---|---|---|
| プロトコルエラー | 未知のメソッド、不正な JSON-RPC リクエスト | JSON-RPC の error オブジェクト |
| ツール実行エラー | API 呼び出しの失敗、ビジネスルール違反、入力値の検証エラー | 成功レスポンスの中で isError: true。LLM が自己修正できる説明を含める |
| 認可エラー | トークンなし、スコープ不足 | HTTP 401 / 403 と WWW-Authenticate |
プロトコルエラーは、モデルが直せない、リクエストの構造の問題です。未知のツール名、CallToolRequest のスキーマを満たさない不正なリクエスト、サーバーの内部エラーがこれに当たり、JSON-RPC のエラーとして返します。未知のツールには -32602 (Invalid params) を使います。
{
"jsonrpc": "2.0",
"id": 3,
"error": { "code": -32602, "message": "Unknown tool: invalid_tool_name" }
}
ツール実行エラーは、モデルが読んで自己修正し、再試行できる、実行時の失敗です。結果の中で isError: true を付けて返します。
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"resultType": "complete",
"content": [
{ "type": "text", "text": "Invalid departure date: must be in the future. Current date is 08/08/2025." }
],
"isError": true
}
}
入力値の検証エラー (日付の形式の誤り、範囲外の値) は、プロトコルエラーではなく、ツール実行エラーとして返します (SEP-1303)。モデルが理由を読んで引数を直せるためです。クライアントは、ツール実行エラーをモデルに渡すことが推奨され、プロトコルエラーは渡してもよい、という扱いです。プロトコルエラーは、モデルが回復できる見込みが低いためです。
認可のエラーは、JSON-RPC ではなく HTTP のレイヤーで返します。詳細は MCPの認可 を参照してください。
3. 開発者がつまずきやすいポイント
3.1 入力検証のエラーを JSON-RPC エラーで返してしまう
引数の検証に失敗したときに、-32602 で返す実装が多く見られます。この場合、モデルにはエラーの内容が渡らず、同じ誤りを繰り返しがちです。値の誤りは isError: true で、何がなぜ誤りで、どう直すかを書いて返します。-32602 は、未知のツール名のように、モデルが直しようのない問題に使います。
3.2 outputSchema を宣言して、適合しない結果を返す
outputSchema を宣言したら、サーバーは必ず適合する structuredContent を返さなければなりません。エラー時 (isError: true) に、スキーマに合わない内容を返して検証に失敗する実装があります。型の違うフィールドや欠落したフィールドは、クライアントの検証エラーになります。SDK の自動検証があれば、テストで早期に気づけます。
3.3 structuredContent だけを返して content を空にする
古いクライアントは、structuredContent が配列やプリミティブだと扱えない場合があります。また、モデルに渡されるのは通常 content です。構造化された結果を返すときは、同じ JSON を文字列にして text にも入れます。
3.4 ツール一覧を接続や状態ごとに変える
2026-07-28 版では、接続単位のセッションがありません。「ログイン後にツールが増える」ような接続状態に依存した一覧は、仕様違反です。リクエストの認可情報に応じて変えるのは許容されますが、その場合は cacheScope を private にし、認可コンテキストをまたいだキャッシュの共有を防ぎます。cacheScope: "public" の一覧は、異なるアクセストークンの間でも共有され得ます。また cacheScope は、ツールを呼べるかどうかの認可の代わりにはなりません。呼び出しのたびにアクセス制御をかけます。
3.5 アノテーションを信頼する
readOnlyHint: true を、確認なしの自動実行の根拠にする実装があります。信頼できるサーバー以外のアノテーションは、自己申告です。また、アノテーションの既定値は「破壊的、外部とやり取りする」側にあることを忘れて、付け忘れたまま公開してもよいと考えないでください。
3.6 ツール名の衝突と文字の制限
複数のサーバーをまとめるクライアントでは、search や create_issue といった一般的な名前が衝突します。サーバーの名前空間を意識した名前 (github.search_issues など) を付けます。スペースや日本語を含む名前は、クライアントやモデルプロバイダーの制約で拒否されることがあります。
3.7 状態をセッションに持たせようとする
プロトコルにセッションはないので、ツール呼び出しの間の状態を、接続に暗黙に持たせることはできません。ハンドルを明示的に返し、引数で受け取る形にし、失効のエラーを分かりやすく返します。
4. コード例
ツールの定義と、入力検証エラーの返し方
TypeScript SDK (@modelcontextprotocol/sdk) で、outputSchema 付きのツールを定義する例です。API は SDK のバージョンで変わります。ここでは考え方を示すもので、実際の呼び出しは使用する SDK のドキュメントで確認してください。
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({ name: "flight-server", version: "1.0.0" });
server.registerTool(
"search_flights",
{
title: "Search Flights",
description:
"Search flights by route and date. Use for questions about availability. Does not book.",
inputSchema: {
from: z.string().length(3).describe("IATA code, e.g. HND"),
to: z.string().length(3).describe("IATA code, e.g. ITM"),
date: z.string().describe("Departure date, YYYY-MM-DD"),
},
outputSchema: {
flights: z.array(
z.object({ id: z.string(), departs: z.string(), priceJpy: z.number() }),
),
},
annotations: { readOnlyHint: true, openWorldHint: true },
},
async ({ from, to, date }) => {
// 値の誤りは、プロトコルエラーではなくツール実行エラーとして返す
if (new Date(date) < new Date()) {
return {
isError: true,
content: [
{
type: "text",
text: `Invalid date ${date}: must be in the future. Use YYYY-MM-DD.`,
},
],
};
}
const flights = await findFlights(from, to, date);
return {
content: [{ type: "text", text: JSON.stringify({ flights }) }],
structuredContent: { flights },
};
},
);
引数のない読み取り専用ツール
引数のないツールは、空のオブジェクトだけを受け付けると明示します。
{
"name": "get_current_time",
"description": "Returns the current server time",
"inputSchema": { "type": "object", "additionalProperties": false },
"annotations": { "readOnlyHint": true, "openWorldHint": false }
}
状態を持つツールのハンドル
{
"name": "create_basket",
"description": "Create a shopping basket. Baskets expire after 24 hours of inactivity.",
"inputSchema": { "type": "object", "additionalProperties": false }
}
結果で basket_id を返し、後続のツールが引数で受け取ります。
{
"name": "add_item",
"arguments": { "basket_id": "bsk_a1b2c3", "sku": "SKU-1001" }
}
サーバーは、呼び出しのたびに basket_id の持ち主を認可情報で検証し、失効していれば「失効しました。create_basket で作り直してください」というツール実行エラーを返します。
5. まとめ
tools/listで発見し、tools/callで実行する。一覧は決定的な順序で返し、ttlMsとcacheScopeを付け、接続の状態では変えないinputSchema/outputSchemaは JSON Schema 2020-12。外部$refを自動で取得せず、合成キーワードには上限を設ける- 結果は
contentとstructuredContent。構造化した結果には、同じ JSON のtextも添える - エラーは二層。入力値の誤りは、モデルが直せるようツール実行エラー (
isError: true) で返す - アノテーションは自己申告。信頼できるサーバーのもの以外を、確認の省略の根拠にしない
- 実行前に人間が拒否できる余地を残す。破壊的な操作は確認を挟む
- ツール一覧の変更は
subscriptions/listenとnotifications/tools/list_changedで伝える。状態は、明示的なハンドルで持たせる
関連ドキュメント
- ツール設計パターンとアンチパターン
- Resources・Prompts・共通ユーティリティ
- ElicitationとMulti Round-Trip Requests
- AIエージェント特有の脅威 - プロンプトインジェクションとツール汚染