エージェントにやらせない操作は、ツールinputSchema/descriptionではなくサーバーのデコード面で禁止する
MCP/WebMCPのようにLLMエージェントが呼ぶツールを公開する設計で、「このフィールドはエージェントには使わせたくない(人間のページ操作専用など)」という制約を、ツールのinputSchemaからそのフィールドを抗き、descriptionに「この操作はしない」と書くだけでは不十分である。
理由: inputSchemaとdescriptionはあくまでモデルへのヒントであり、強制力を持たない。(1) モデルがスキーマを無視して余分なフィールドを送ってくる可能性がある、(2) ツールは実体としては通常のHTTP APIの薄いラッパーに過ぎず、同じAPIをツール層を経由せず直接叫ぶ経路(人間のUIや他のクライアント)が別に存在することが多い。その場合、ツール定義だけを制限してもサーバー側のAPIハンドラがそのフィールドを受け付ければ、実際の制約にならない。
適用条件: 「このアクター(エージェント)には絶対にこの能力を持たせたくない」という能力境界(authorization boundary)に限って重要。単なる使い勝手の推奨(例: この引数は普段使わない)なら、schema/descriptionだけで十分。
対処方法: エージェント向けと人間向けでエンドポイントを分け、エージェント側のエンドポイント(ツールが叫ぶHTTPハンドラ)のデコード対象構造体自体から禁止したいフィールドを完全に削除する。人間専用の操作(例: 並べ替え・削除)は別のエンドポイントに切り出し、そちらにはツールを一切定義しない。これにより、モデルがスキーマを無視して余分なフィールドを送ってもサーバーがそもそも解釈しないため、制約がスキーマの完全性やモデルの従順に依存しない。さらにスキーマにadditionalProperties: falseを付与してツール呼び出し側(クライアントランタイム)でも早期に拒否させれば二重の防御になるが、サーバー側のデコード制限が本質的な境界であり、スキーマ側はUX上のヒントに過ぎない。
確認方法: ツールのinputSchemaを無視して、エンドポイントに直接禁止フィールド(例: 並べ替え用order、削除用delete)を含むリクエストを送ってみて、そのフィールドが実際に無視される(状態が変化しない)ことを自動テストで検証する。これを常設の識別反証テストとして残すことで、将来のリファクタが制限を崩さずに継続することも検証できる。
教訓: 「ツールの入力スキーマを狭くすれば安全」という直感は、ツールとAPIハンドラが1対1で密結合していない限り成り立たない。能力境界は常に「実際に実行されるコードが何を受け付けるか」で引く。