MCPツールに「アノテーション」を付けて、AIクライアントの権限管理を読み書きで分類する
MCPサーバーのツール定義に readOnlyHint / destructiveHint といった「アノテーション」を付けると、Claudeなどのクライアント側で「読み取り専用ツール」「書き込み/削除ツール」として自動的にグループ分けされ、権限管理がぐっと扱いやすくなります。意外と知られていないこの仕組みのメリットと、実際の付け方を解説します。
目次
MCP(Model Context Protocol)サーバーを自作してClaudeなどのAIクライアントに接続したとき、ツールの権限管理画面の見え方がサーバーによって違う、と気づいたことはないでしょうか。あるサーバーは「読み取り専用ツール」「書き込み/削除ツール」ときれいにグループ分けされているのに、自作サーバーは全ツールが「その他のツール」としてフラットに並ぶだけ。

この差を生んでいるのが、MCPの仕様にあるツールアノテーション(tool annotations)です。あまり知られていませんが、ツール定義にいくつかのヒントを添えるだけで、クライアント側の権限UIが読み書きを自動分類してくれるようになります。この記事では、アノテーションのメリットと具体的な付け方をまとめます。
ツールアノテーションとは
MCPの各ツール定義には annotations というメタデータを添付できます。これはツールの「振る舞いのヒント」をクライアントに伝えるためのもので、次の4つのフィールドがあります。
| アノテーション | 型 | 意味 |
|---|---|---|
readOnlyHint |
boolean | true なら環境を変更しない読み取り専用ツール |
destructiveHint |
boolean | true なら破壊的(削除・上書きなど)な操作。readOnlyHint が false のときに意味を持つ |
idempotentHint |
boolean | 同じ引数で繰り返し呼んでも結果(状態)が変わらないか |
openWorldHint |
boolean | 外部エンティティ(外部API・Webなど)と相互作用するか |
重要なのは、これらはあくまでヒント(hint)であって保証ではない、という点です。クライアントはこの情報を使ってUIの出し分けや確認ダイアログの要否を判断しますが、サーバー側の実際の挙動を強制するものではありません。とはいえ、正しく付けておくことでユーザー体験が大きく変わります。
アノテーションを付けるメリット
- 権限UIがカテゴリ単位で扱える:Claudeの権限設定では、アノテーションがあると「読み取り専用ツール(◯個)」「書き込み/削除ツール(◯個)」のようにグループ化されます。「読み取り系はまとめて常に許可、書き込み系は都度承認」といった設定が1クリックで可能になります。
- アノテーションが無いとフラットな一覧になる:ヒントを返さないサーバーのツールは、クライアントが読み書きを判別できず「その他のツール」として全部が1つの塊で並びます。ツール数が多いほど設定が面倒になります。
- 破壊的操作の事故を減らせる:
destructiveHint: trueのツールに対してクライアントが追加確認を出すなど、削除系オペレーションのガードレールとして機能します。 - 自律エージェントの安全性が上がる:エージェントが自動でツールを選ぶとき、「読み取り専用」を優先させる、破壊的ツールは人間の承認を挟む、といった制御の材料になります。
付け方その1:registerTool のconfigに渡す
TypeScript版MCP SDK(@modelcontextprotocol/sdk)の registerTool を使う場合、ツール定義のconfigオブジェクトに annotations を足すだけです。これが最も素直な書き方です。
server.registerTool(
"delete_post",
{
title: "Delete Post",
description: "指定したIDの投稿を削除します。",
inputSchema: { id: z.number() },
annotations: {
readOnlyHint: false,
destructiveHint: true,
idempotentHint: true, // 同じIDを2回消しても最終状態は同じ
openWorldHint: true, // 外部API(WordPress)を叩く
},
},
async ({ id }) => {
// ...実際の削除処理
},
);
読み取り系のツールなら次のようになります。
server.registerTool(
"list_posts",
{
title: "List Posts",
description: "投稿の一覧を取得します。",
inputSchema: {},
annotations: {
readOnlyHint: true,
destructiveHint: false,
openWorldHint: true,
},
},
async () => {
// ...一覧取得処理
},
);
付け方その2:server.tool() のオーバーロードで渡す
古めの server.tool() API を使っている場合は、引数の並びでアノテーションを渡せるオーバーロードがあります。name, description, paramsSchema, annotations, callback の順です。
server.tool(
tool.name,
tool.description,
tool.schema,
tool.annotations, // ← 4番目にアノテーション
async (args) => handler(args),
);
ツールをレジストリ(配列)で一元管理しているプロジェクトなら、ツール定義の型に annotations フィールドを追加し、登録ループでそのまま渡すのがきれいです。「全ツールにアノテーション必須」を型レベルで強制したいなら、フィールドをオプショナル(?)ではなく必須にしておくと、新しいツールを追加するときに付け忘れがコンパイルエラーで弾けます。
どう分類するか:判断のヒューリスティック
個々のツールをどのカテゴリに割り当てるかは、操作のセマンティクスで決めます。ツール名の動詞から機械的に判断できることが多いです。
| 操作の種類 | readOnlyHint | destructiveHint | idempotentHint |
|---|---|---|---|
| get / list / search / read(取得・検索) | true | false | — |
| create / post / add(新規作成) | false | false | — |
| update / patch / set(更新) | false | false | true |
| delete / clear / remove(削除) | false | true | true |
- updateやdeleteは
idempotentHint: trueにしやすい:「同じ更新を2回適用しても最終状態は同じ」「同じものを2回消しても結果は同じ」だからです。一方、createは呼ぶたびに新しいリソースが増えるので冪等ではありません。 openWorldHintは外部依存の有無で決める:外部APIやWebを叩くツールはtrue。純粋にローカルで完結する計算ツール(足し算など)や、自前のストレージだけを触るツールはfalseにできます。- 「読み取りだが重い処理」も読み取り扱いでよい:データを取得してレポートを生成するだけのツールは、状態を変えないなら
readOnlyHint: trueです。処理の重さは関係ありません。
テストで「付け忘れ」を防ぐ
アノテーションは付け忘れても動いてしまうため、テストで担保しておくと安心です。ツールをレジストリで管理しているなら「全ツールに annotations が存在すること」を、代表的な読み取り/作成/削除ツールについては「期待するヒント値になっていること」をアサートするテストを書いておきます。
import { tools } from "./tools";
test("すべてのツールにアノテーションがある", () => {
for (const tool of tools) {
expect(tool.annotations).toBeDefined();
}
});
test("削除ツールは破壊的としてマークされている", () => {
const del = tools.find((t) => t.name === "delete_post")!;
expect(del.annotations.readOnlyHint).toBe(false);
expect(del.annotations.destructiveHint).toBe(true);
});
ツールを命令的に registerTool で登録しているプロジェクトなら、registerTool をモックして「記録された各ツールのconfigにアノテーションが含まれているか」を検証する形にすると、同じことができます。
まとめ
- 権限UIで読み書きがグループ化されるかどうかは、クライアントの挙動差ではなくサーバーがアノテーションを返しているかどうかの差。
readOnlyHint/destructiveHintを中心に、ツール定義へannotationsを足すだけで、Claudeなどのクライアントが「読み取り専用」「書き込み/削除」を自動分類してくれる。- 分類はツール名の動詞ベースのヒューリスティックでほぼ機械的に決められる。テストで付け忘れを防ぐとなお良い。
自作MCPサーバーを公開・運用するなら、ぜひ全ツールにアノテーションを付けておきましょう。利用者の権限管理の手間と、破壊的操作の事故リスクを同時に下げられる、コストの低い改善です。