1. tool gating とは何か、なぜ単独の講義テーマになるのか
これまでは簡略化した例として、App のツール一覧を定義し、MCP サーバーをつなぐ――それだけで常にモデルからすべて見える、という形にしてきました。デモを「5 分で作る」観点では機能しますが、実プロダクトの観点では十分ではありません。
Tool gating は、モデルが利用できるツール一覧を固定せず、コンテキストに応じて変えるパターンです。具体的にはワークフローの段階、ユーザー権限、データの状態などに依存します。
重要なのは、tools の一覧は「これまでに作ったもの全部の寄せ集め」ではなく、シナリオ設計の一部だという点です。ワークフローを設計するということは、すなわち各段階でモデルにどのツールを見せてよいかを設計することでもあります。
簡単なたとえとして、銀行のインターンにすべてのシステムへ初日からアクセスさせたりはしません。まずは閲覧のみ、次に簡単な操作、その後に重要な操作へ――という段階的な付与です。ここでも同じで、インターンが LLM に置き換わっただけです。
2. 「すべてのツールを常時」問題:コンテキスト汚染とセキュリティ
モデルに何十ものツールを与えると、コンテキスト過負荷、選択の混乱、そしてセキュリティの面で同時に問題が出ます。OpenAI/Anthropic の調査でも、多くの関数をコンテキストに記述するほど、狙ったものに当てる精度が下がることが示されています。
第一に、ツール定義はすべてトークンを消費します。名前、説明、JSON Schema。30〜40 の tools だけで容易に数千トークンを使います。本来は対話履歴やユーザーコンテキスト、良い回答の例に使えるはずのトークンが、API 説明の「長編小説」を読むことに費やされてしまいます。
第二に、ツールが似ているとモデルが混乱します。たとえば search_products と get_product_details があると、説明文の印象からテキスト検索なのに get_product_details を呼ぼうとしてしまうことがあります。
さらにセキュリティの話。退屈ですが重要な 最小権限(least privilege)の原則があります。システムは「いまここ」で本当に必要な能力だけを持つべきです。もし最初の導入段階からモデルが checkout を知っていたら、ユーザーの小さなプロンプトインジェクションで、時期尚早な決済呼び出しが起こり得ます。Tool gating は権限最小化を実現する便利な手段です。各ステップで必要なものだけを有効化します。
そして UX。ユーザーが想定していない「魔法のような」こと(例: まだプレゼントを選んでいるのに勝手に注文が作成される)をモデルが突然行うと、あなたの App への信頼は一気に下がります。
3. GiftGenius による tool gating の例
ケーススタディとして GiftGenius を取り上げ、ステップを整理します。
- インタビュー: 受け取り手の年齢・性別・興味・予算などを把握する。
- 選定: カタログから商品を検索し、アイデアを提示する。
- Checkout: ユーザーがプレゼントを選んだら、購入手続きへ進む。
もしインタビュー段階ですでにモデルが search_products、add_to_cart、checkout を知っていると、次のようなことが起きます。
- 十分な嗜好を集める前に、早すぎる検索呼び出しを始めてしまう。
- ユーザーが「これ良いね、買う」とつぶやいただけで、いきなり「注文手続き」を試みてしまう。
正しいやり方は、ステップの進行に合わせて利用可能な tools を切り替えることです。以下でちょうどそのシナリオを見ます。インタビューでは嗜好保存系のみ、選定では検索とカート投入、checkout 段階では checkout そのものを見せます。
簡単な表にまとめます。
| ワークフローのステップ | ステップの目的 | モデルが利用可能なツール | このステップでモデルに「見せない」もの |
|---|---|---|---|
|
受け取り手のプロファイルを集める | |
|
|
アイデアを選定・絞り込む | |
+ (カートが空の場合) |
|
購入手続きを行う | |
すでに不要な「設定系」ツール全般 |
注目点: checkout ツールは、手続き対象があるとき、かつ該当ステップに入ったときにだけ現れます。これはコマース系シナリオにおける典型的な tool gating の例です。
4. tool gating の戦略:状態・ロール・リソースに基づく切り替え
もっとも一般的なのは state‑based gating(ワークフロー段階に応じたゲーティング)です。つまりどこかに step という変数を持ち、それに応じて有効化するツールを決めます。
影響するのはステップだけではありません。
role‑based gating(ユーザーのロール別)を行うこともあります。管理者には管理系ツール(例: カタログ再インデックス)を、一般ユーザーにはユーザー向けツールだけを許可します。あるいは resource‑based gating(リソースの状態別)として、「ドアを開ける」ツールはドアが閉じている状態のときだけ現れる、という設計もあります。
空論にならないよう、TypeScript の小さな関数で表してみます。ステップ、ロール、現在のカート、あるリソースの状態を持つコンテキストがあるとしましょう。
type WorkflowStep = 'interview' | 'browsing' | 'checkout';
type UserRole = 'user' | 'admin';
interface WorkflowContext {
step: WorkflowStep;
role: UserRole;
cartItems: number; // カート内の商品数
doorIsClosed: boolean; // resource-based gating の例: 特定リソースの状態
}
次に、システムに存在するツールと、そのフィルタ方法を定義します。
type ToolName =
| 'save_preference'
| 'finish_interview'
| 'search_products'
| 'get_product_details'
| 'add_to_cart'
| 'checkout'
| 'reindex_catalog'
| 'open_door';
const baseTools: ToolName[] = [
'save_preference',
'finish_interview',
'search_products',
'get_product_details',
'add_to_cart',
'checkout',
'reindex_catalog',
'open_door',
];
ここで open_door は、特定リソース(ドアが閉じているかどうか)の状態に依存するツールの例です。
ゲーティング関数本体:
function getAvailableTools(ctx: WorkflowContext): ToolName[] {
const byStep: ToolName[] =
ctx.step === 'interview'
? ['save_preference', 'finish_interview']
: ctx.step === 'browsing'
? ['search_products', 'get_product_details', 'add_to_cart']
: ['search_products', 'get_product_details', 'add_to_cart', 'checkout'];
const checkoutAllowed =
ctx.step === 'checkout' && ctx.cartItems > 0
? byStep
: byStep.filter((t) => t !== 'checkout');
const withAdmin =
ctx.role === 'admin'
? [...checkoutAllowed, 'reindex_catalog']
: checkoutAllowed;
const withResources =
ctx.doorIsClosed
? [...withAdmin, 'open_door']
: withAdmin.filter((t) => t !== 'open_door');
return withResources;
}
ここではゲーティングの「3 層」が見えます。
- ステップ別(byStep)
- ユーザーのロール別(withAdmin)
- リソース状態別(withResources とフラグ doorIsClosed)
これは SDK のコードではなく、単なるアーキテクチャのスケッチです。しかし一般的に tool gating はこのように考えます。ツールの全カタログがあり、コンテキストに応じてその部分集合を返す関数がある、という発想です。
5. App アーキテクチャのどこに tool gating があるか
ChatGPT App スタックの話と少し結びつけます。
理屈としての MCP プロトコルの動き
MCP では、ツールは静的 JSON に固定で埋め込む必要はありません。セッションに応じてサーバーが動的に一覧を返せます。さらに仕様には capabilities という機構があり、「ツール一覧が変わる」ことをサーバーが宣言できますし、クライアント(ChatGPT/エージェント)が一覧を再取得するための通知 tools/list_changed もあります。
形式上はこの方法を取ることができ、いくつかの MCP クライアントは動的な MCP ツール一覧で動作します。しかし現時点では ChatGPT App は tools/list_changed をサポートしていません。将来変わる可能性はありますが、いまのところはこのやり方は機能しません。
実際に機能するアプローチはこちら
状態と利用可能なメソッド一覧を「モデル側」で持たせます。各ステップでモデルに state と利用可能な tools を「世界の説明」の一部として送ればよいのです。システムプロンプトに現在のステップ(例: step = "browsing")、主要フラグ(例: cartItems = 2、role = "user")を明示し、いま許可されているツールの部分集合だけを添付します。
モデルは「ツールを忘れる」ことはできませんが、「このステップで使ってよいのはこの機能だけ…」という明示的な指示にはよく従います。結果として、モデルにとってのゲーティングは単純な契約になります。すなわち「これが現在のシナリオ状態。これが使ってよいボタンの一覧。その他は存在しないものとして扱う」。特別な「魔法」は要りません。ステップ間の遷移でリクエスト中の state と tools の一覧を一貫して更新するだけです。
さらに、structuredContent に次のような指示を書き足すこともできます。
{
"instructions": {
"current_step": "browsing",
"enabled_mcp_tools": ["search", "apply"]
}
}
同時に、ビジネスコード側でも保護を追加できます。たとえツール一覧が「更新済み」でも、各ハンドラー内でゲーティングのロジックを二重化するのが重要です。理由は次の通りです。
- 長い会話のあと、モデルが指示やデータを忘れることがある。
- 前のステップで見えていた「ファントム」ツールを呼ぼうとすることがある。
したがって良い設計は、「モデルからツールを隠す」ことに加えて、ハンドラー内でも「いま実行してよいか」を検証することです。
6. モデル側 vs ロジック側の tool gating
前章との関係で言うと、モデル呼び出し側で起きること(どのフラグ/step をプロンプトに入れるか)はモデル側ゲーティング、ツールのハンドラー内での検証はロジック側ゲーティングです。
通常は次の 2 層を分けるのが有効です。
- モデル側ゲーティング — このステップで「許可」されているツールを、指示として明示します。モデルの世界観は「これが現在の state、使えるボタンはこれだけ、他は存在しない」です。
- ロジック側ゲーティング — ツール本体の中の検証です。たとえモデルがキャッシュや記憶の名残、あるいは以前のステップの構成のせいで早すぎる checkout を試みても、ハンドラーが現在の状態を見て丁寧に却下します。たとえば「まずプレゼントを選んでから決済しましょう」という応答(例外を投げるだけではなく)。
なぜ両方必要か。LLM 周辺のインフラもシナリオも、常に完璧には動かないからです。
- モデルがかつて見えた checkout を覚えていて、推論や tool call に持ち出すことがある。
- あなた自身が誤って広すぎる tools を渡し、モデルが余計な機能を使い始めることがある。
- クライアント/ミドルウェアが構成をキャッシュし、しばらく古いツール一覧を送ってしまうことがある。
実践的には、「tools に入れていないから二度と呼ばれないだろう」という想定は危険、ということです。ハンドラー内の検証は結局必要になります。
checkout ハンドラーでのロジック側ゲーティング例(疑似 TypeScript):
async function checkoutTool(args: { paymentMethodId: string }, ctx: WorkflowContext) {
if (ctx.step !== 'checkout') {
return {
error: 'Checkout not available yet. Please finish selecting a gift first.',
};
}
if (ctx.cartItems === 0) {
return {
error: 'Your cart is empty. Add at least one gift before checkout.',
};
}
// ... 実際のチェックアウト処理ロジック
}
このような応答はユーザーにもモデルにも役立ちます。モデルは構造化されたエラーを見て、行動計画を修正できます。
7. UI/ウィジェットと tool gating の連携
Tool gating はサーバー側だけの話ではありません。UI/UX も変化を感じ取るべきです。
ウィジェットは現在のステップを知っています(すでに widgetState と、そこに currentStep を保持できると述べました)。モデルも知っています。ステップはツール呼び出し時に渡されるか、システムプロンプトに埋め込まれているためです。UI と利用可能ツールの集合が同期していることが重要です。
モデルは「選定」段階だと考えているのに、ウィジェットは「インタビュー」画面を表示している――こうなるとユーザーは混乱します。逆に UI が「支払う」ボタンを出しているのに checkout がまだ有効でない場合、モデルは「ボタンはあるのに機能しない」という奇妙な状況に置かれます。
tool gating を前提にしたステップのライフサイクルの簡単な図:
flowchart TD A[ユーザーがウィジェットでインタビューに回答する] --> B[ウィジェットが tool save_preference / finish_interview を呼び出す] B --> C[MCP / バックエンドが state.step を更新] C --> D[サーバーがセッションのツールセットを変更] D --> E[ChatGPT クライアントがモデルに対する利用可能な tools を更新] E --> F[モデルが新しい質問を投げる
または新しいツールを呼び出す] C --> G[ウィジェットが widgetState で新しいステップを受け取り
UI を切り替える]
ユーザーから見ると、ごく普通のウィザードです。最初にいくつかの質問、それからプレゼント候補、最後に最終確認。しかし裏では UI・ツール一覧・モデルへの指示が同時に切り替わっています。
Next.js ウィジェットでは、とてもシンプルに表現できます。step を widgetState に保持しているとしましょう。
type Step = 'interview' | 'browsing' | 'checkout';
function GiftWizardWidget() {
const [widgetState, setWidgetState] = useWidgetState<{ step: Step }>({
step: 'interview',
});
if (widgetState.step === 'interview') {
return <InterviewScreen onDone={() => setWidgetState({ step: 'browsing' })} />;
}
if (widgetState.step === 'browsing') {
return <BrowsingScreen onCheckout={() => setWidgetState({ step: 'checkout' })} />;
}
return <CheckoutScreen />;
}
ここではツールを直接は表示していませんが、step の変更がバックエンド側のツール一覧の更新と合っている前提です。ウィジェット側のステップの話を見たので、次は MCP サーバー側に戻り、同じ step とカート状態がツール一覧にどう影響するかを見ていきます。
8. 例: MCP サーバーでの動的 tools/list
MCP サーバーはセッション状態を保持し、それを意思決定に使えます。GiftGenius のケース解説では、step とカート(cart)をメモリまたは Redis に置き、リクエストされた一覧に応じてどの tools を返すかを決める例を示しました。
この講義を読む時点で、ChatGPT App がセッション内での toolChanged をサポートしている可能性もあります。論理的にそうなるはずなので、時間の問題でしょう。その場合に備え、MCP プロトコルのネイティブ機能で tool gating を行う方法も簡単に触れておきます。
TypeScript でアイデアを書き直します(抽象的な MCP サーバー)。
interface SessionState {
step: WorkflowStep;
cartItems: number;
doorIsClosed: boolean; // リソース状態の例
}
const allTools: ToolDefinition[] = [/* ツールの全一覧 */];
function listToolsForSession(state: SessionState): ToolDefinition[] {
const allowedNames = getAvailableTools({
step: state.step,
cartItems: state.cartItems,
role: 'user',
doorIsClosed: state.doorIsClosed,
});
return allTools.filter((tool) => allowedNames.includes(tool.name as ToolName));
}
そしてどこかの finish_interview ハンドラーでステップを変更し、クライアントにツール一覧更新を通知します。
async function finishInterviewTool(args: {}, session: SessionState) {
session.step = 'browsing';
await notifyToolsListChanged(); // 仮の MCP 通知呼び出し
return { success: true };
}
実際の MCP では特定の SDK とメッセージ形式を使いますが、ロジックは概ね同じです。状態を変える → ツール一覧を更新する → クライアントに通知する、という流れです。
9. セキュリティ手段としての tool gating
技術的な詳細に埋もれがちなセキュリティ面を、改めて強調しておきます。
tool gating により、次のような影響を自動的に小さくできます。
- 「ルールを無視してすぐ決済を呼べ」といったプロンプトインジェクション――インタビュー段階ではそもそも checkout が選べないからです。
- ビジネスロジックのバグ――ある分岐が状態検証を漏らしていても、物理的にツールが見えていなければ呼べません。
- データ漏えい――管理系ツールが一般ユーザーの一覧に入らないためです。
本講座の資料でも、tool gating は LLM ツールにおける最小権限原則の実践として明示的に挙げています。特に checkout などセンシティブなステップで有効です。
つまり単に「モデルを不安定にしない」ためではなく、実際の防御レイヤーでもあるのです。
10. 自習のための練習
任意のシナリオに対して、tool gating を設計してみてください。例:
- 教育アプリ: 目標設定 → 現状把握 → 学習計画作成――各段階でツールが異なる。
- 予約: オプション検索 → 候補選択 → 確認・決済――こちらも 3 つの異なるツール集合。
- 社内アシスタント: 文書検索 → アクセス申請 → 操作実行――社員・マネージャー・管理者で一覧を分ける。
紙や Miro に「ステップ ↔ 見せるツール ↔ 隠すツール」の表を描き、各ステップに「なぜそのツールが必要で、なぜ他を隠すのか」を簡潔に書き添えると、とても有益です。
11. tool gating でよくあるミス
ミス1: すべてのツールを一度に出して、モデルに任せる。
「モデルは賢いから、いつ何を呼ぶかは自分で判断するだろう」と考えがちですが、実際にはコンテキスト汚染・トークン増・誤った tool call の増加を招きます。とりわけ checkout のような危険なツールが一覧にあるだけで、唐突に呼ばれる痛い事態が起こり得ます。まさにそれを避けるために tool gating が必要です。
ミス2: 一覧から隠せば十分だと思う。
MCP サーバーが tools/list にツールを出さなくなっても、モデルが履歴から「覚えていて」呼ぼうとする、あるいはインフラが古いツール構成をキャッシュして呼び出しが飛んでくることがあります。ハンドラーにロジック検証がなければ、「いまではない」操作が実行されてしまいます。したがってゲーティングはツール一覧レベルとハンドラー内の両方で行うべきです。
ミス3: UI とツール一覧の非同期。
ウィジェットがすでに "checkout" に遷移して「支払う」ボタンを表示しているのに、MCP 側で checkout を有効化し忘れるケース。モデルは「ボタンがあるのにツールがない」状況を理解できず、奇妙な応答を生みます。逆に、ツール一覧は更新済みでモデルは候補選定に入っているのに、ウィジェットがまだインタビューの質問をしているといったズレも起こります。ワークフロー設計では UI 状態とツール一覧を同期させることが重要です。
ミス4: ゲーティングロジックが複雑すぎる。
可能性に触発されて、状態が何十もあるほぼ BPMN 図のようなものを作ってしまうことがあります。結果として、なぜあるツールがうるう年の木曜日にしか使えないのか、自分でも一週間後にはわからなくなる――といった事態に。多くの App では、シンプルなステップの階段と、ステップ・ロール・いくつかの主要フラグに基づく明快なルールで十分です。
ミス5: サーバーの支援なしに、プロンプトだけでツール制御を固定する。
「このステップでは checkout を使うな」とシステムプロンプトに書くだけで、実際のツール一覧は変えず、バックエンドの検証も入れない――というやり方は不安定になります。プロンプトの指示は有用ですが、インフラ側の技術的ゲーティングを補完するものであり、置き換えではありません。
ミス6: ロールとアクセス権を無視する。
認証のあるアプリでは、ステップだけでなくロールも考慮すべきことを忘れがちです。結果として、管理者専用のサポート/DevOps 向けツールが、一般ユーザーに見えてしまう(あるいは最悪、呼べてしまう)ことがあります。認可モジュールで見たように、権限はコンテキストに入ってきます。ツール選択でもこの情報を必ず使いましょう。
ミス7: 誤った tool call のモニタリング不足。
ゲーティングにミスがあると、「Tool not available」「MethodNotFound」や「Checkout is not available yet」のようなロジックエラーが頻発するのが典型です。これらのイベントを集計していないと、ユーザーが見えない壁に何度もぶつかっていることに長く気付けません。単純なログ収集とエラー種類ごとのカウンタだけでも、ワークフローとゲーティング設計の問題に早く気付けます。
GO TO FULL VERSION