コマンドパレット

ページ・記事・アクションを検索できます。

MCPツールに足りない引数をどう補うか

2026/4/5

はじめに

LLM ベースのエージェントが MCP ツールを呼び出すとき、ユーザーの指示が曖昧でパラメータが足りないケースは日常的に発生します。

「KR ダッシュボードのデータ見せて」

このリクエストを受けたエージェントは kr_dashboard_get_records ツールを呼び出したいが、どの KR データかが指定されていない。売上 KR? 採用 KR? 品質 KR?

この「足りない引数」を補う手段は、現在 3 つ存在します。

アプローチ誰が聞くかいつ聞くか
LLM が聞くLLM 自身ツール呼び出し
リッチフォーム(Generative UI)LLM がフォーム UI を生成ツール呼び出し
MCP ElicitationMCP サーバー(コード)ツール実行

本記事では、MCP 仕様 2025-06-18 で追加された Elicitation の仕組みを解説し、既存のリッチフォーム手法との使い分けを整理します。さらに、実プロジェクトで実装を試みた際に発覚した AgentCore Gateway の構造的制約 を記録します。

: 本記事は MCP 仕様 2025-06-18 を基準に整理しています。2025-11-25 では URL mode の追加など Elicitation の拡張が行われていますが、AgentCore Gateway がサポートする仕様バージョンに合わせて 2025-06-18 を採用しました。最新の仕様は MCP Elicitation Specification (2025-11-25) を参照してください。

MCP Elicitation とは

MCP Elicitation は、MCP 仕様 2025-06-18 で追加されたプロトコル機能です。ツール実行中に MCP サーバーがクライアントを介してユーザーに質問し、回答を受けて処理を続行できます。厳密には「サーバー → クライアント → ユーザー」の経路で、クライアントが UI 表示と承認制御を握ります。

Claude Code を使ったことがある方なら AskUserQuestion を想像してください。近い概念ですが、決定的な違いがあります。

AskUserQuestion との対比

AskUserQuestion — LLM が聞く:

ユーザー: 「KRデータ見せて」
LLM: 「どのKRですか?売上、採用、品質がありますが」  ← LLMが文脈から推測
ユーザー: 「売上」
LLM → ツール呼び出し

MCP Elicitation — サーバーが聞く:

ユーザー: 「KRデータ見せて」
LLM → ツール呼び出し(パラメータ不足のまま)
       → MCPサーバーが実行中に「パラメータ不足」と判断
       → elicitation/create でクライアント経由でユーザーに質問
       → ユーザーが回答
       → MCPサーバーが処理続行 → 結果返却
LLM ← ツール結果を受け取り表示

MCP Elicitation のデータフロー
MCP Elicitation のデータフロー

注目すべきは、Elicitation のやり取りが LLM をバイパスしている点です。MCP サーバーがクライアントを介してユーザーに質問し、回答を受け取る。LLM のトークン消費もラウンドトリップも発生しません。

質問を決めるのはサーバーコード

ここが直感に反するポイントです。Elicitation の質問文も選択肢も、LLM は一切関与しない。すべて MCP サーバー側の開発者が書いたコードが決めます。

// MCPサーバー側のツール実装(開発者が書くコード)
server.tool("get-kr-dashboard", async ({ params }, { sendElicitation }) => {
  // DBから利用可能なKRを動的に取得
  const availableKRs = await fetchAvailableKRs(params.orgId);

  // サーバーが質問文とスキーマを決める。LLMは関与しない
  // 仕様上、requestedSchema のトップレベルは type: "object" 固定
  const result = await sendElicitation({
    message: "どのデータを表示しますか?",
    requestedSchema: {
      type: "object",
      properties: {
        selected: {
          type: "string",
          enum: availableKRs.map((kr) => kr.name),
          description: "表示するKRデータ",
        },
      },
      required: ["selected"],
    },
  });

  if (result.action === "accept") {
    // content は requestedSchema と同じ shape の object
    return fetchDashboardData(result.content.selected);
  }
});

LLM は「ツールを呼ぶ」だけ。質問文も選択肢もサーバーのロジック次第です。これが AskUserQuestion と根本的に異なる点であり、サーバーだけが知っている情報 — DB の中身、ユーザー権限で見えるリソース、API の現在の状態 — を選択肢にできるのが強みです。

LLM に利用可能な KR の一覧を教える必要がない。MCP サーバーが DB を引いて「この組織で今見れるのはこの 3 つ」と動的に出せます。

AI SDK のサポート状況

@ai-sdk/mcp v1.0.15 / @modelcontextprotocol/sdk v1.27.1 で完全サポートされています。

import { createMCPClient } from "@ai-sdk/mcp";

const client = await createMCPClient({
  transport: { type: "sse", url, headers },
  capabilities: {
    elicitation: {}, // Elicitation サポートを宣言
  },
});

client.onElicitationRequest(ElicitationRequestSchema, async (request) => {
  // request.params.message — サーバーからの質問
  // request.params.requestedSchema — 期待されるレスポンスのJSON Schema
  return {
    action: "accept", // "accept" | "decline" | "cancel"
    content: {
      /* ユーザーの回答 */
    },
  };
});

応答の action は 3 つ。accept(回答する)、decline(質問を拒否)、cancel(操作をキャンセル)。JSON Schema で応答形式を制約できるため、自由テキストではなく選択肢やフォーマット付きの入力を強制できます。

セキュリティ上の注意: 仕様上、サーバーは Elicitation でパスワードやトークンなどの機密情報を要求してはなりません。Elicitation の経路はそうした情報の安全な伝送を想定しておらず、2025-11-25 では機密情報の収集には別途 URL mode が追加されています。

Generative UIとの比較

ここで問題になるのが、「足りない引数を補う」という同じ課題を解決する既存手法との重なりです。

筆者が担当しているプロジェクトでは、LLM がフォーム UI を JSON-L 形式で生成し、クライアントが DatePicker や Select などのリッチコンポーネントとして描画する仕組み(Generative UI)を既に構築していました。休暇申請フォーム、ユーザー検索フィルタなど、複数フィールドの一括入力に使っています。

これも「MCP ツールに足りない引数を補う」手段です。Elicitation と役割が被っている。

なお、Generative UI はアプリケーション層の実装であり、Elicitation は MCP プロトコルの機能です。レイヤーが異なるものの比較ですが、「足りない引数を補う」という同じ課題に対するアプローチとして並べています。

比較表

Generative UIMCP Elicitation
誰が判断LLMMCP サーバー(コード)
いつ聞くツール呼び出しツール実行
UI の表現力高い(DatePicker, Table 等)flat primitive schema に制限、リッチ UI はクライアント実装依存
一度に聞ける量複数フィールド一括flat object で複数フィールド可能だが、ネストや複雑な構造は不可
LLM 再呼び出し必要(フォーム送信 → 新リクエスト)不要(ツール内で完結)
トークンコストLLM 往復が 1 回多いツール内完結で節約
選択肢の情報源LLM の知識 or 事前のツール呼び出しサーバーが DB/API から動的生成

判断フレームワーク(私見)

以下は筆者のプロジェクトで実際に運用してみた上での整理であり、公式なベストプラクティスではありません。プロダクトの規模やアーキテクチャによって最適解は変わるはずです。

判断軸は大きく 2 つあります。誰が質問を決めるか(LLM か サーバーコードか)と、UI にどこまでの表現力が必要かです。

  • Generative UI を選ぶ: 複数フィールドの一括入力、DatePicker や Table などの入力支援、リッチな UI 表現が必要なとき。LLM が考える UI。
  • Elicitation を選ぶ: ツール実行中に不足引数が判明する、候補をサーバーが DB/API から動的生成する、LLM を介さず確定的に聞きたいとき。サーバーコードが決める追問。プロトコル上は構造化された追問だが、UI の表現力は Generative UI より狭い。
  • LLM の聞き返しを選ぶ: 曖昧さの解消自体に会話の文脈理解が必要なとき。
  • HITL 承認を選ぶ: 破壊的操作の最終確認。既存の仕組みで十分。

具体例で整理すると:

ユースケース推奨手段理由
複数フィールドの一括入力(休暇申請: 期間+理由+種別)Generative UIリッチ UI、DatePicker 等の表現力が必要
リソース選択(どの KR? どの組織?)Elicitationサーバーだけが正しい選択肢を知っている(権限付き org 一覧、現在有効な候補など)
曖昧な意図の確認(削除?更新?)LLM の聞き返し文脈理解が必要
破壊的操作の最終確認HITL 承認既存の仕組みで十分

なお、インフラやクライアントが Elicitation を素直に扱えない環境なら、Generative UI やアプリ層の独自実装で同等の UX を提供する判断も自然です。手段はあくまで手段であり、ユーザー体験を優先すべきです。

AgentCore Gateway の壁

現在のアーキテクチャ

筆者が担当しているプロジェクトでは、AWS Bedrock AgentCore Gateway + Lambda インターセプターでMCPサーバーを構成しています。

クライアント → AgentCore Gateway → Lambda ハンドラー → 結果返却
                                   (ステートレス)

Lambda ハンドラーは「引数を受け取り → 処理 → 結果を返す」一方通行のリクエスト-レスポンスモデルです。

なぜ Elicitation が動かないか

MCP Elicitation はツール実行中の双方向通信を前提としています。

MCPサーバー: ツール実行開始
  → 「パラメータ足りない」
  → elicitation/create を送信(実行を一時停止)
  → ユーザーの回答を待つ        ← ここで Lambda は死んでいる
  → 回答を受けて実行を再開
  → 結果を返却

この「一時停止して応答を待つ」がステートレスな Lambda では構造的に不可能です。Lambda はリクエストを受けたら結果を返して終了。途中で外部に質問を送って応答を待つ、という対話的なフローを挟む仕組みがありません。

AgentCore Gateway vs Runtime
AgentCore Gateway vs Runtime

AgentCore Runtime という選択肢

AWS Bedrock AgentCore には Gateway とは別に Runtime が存在します。

AgentCore GatewayAgentCore Runtime
実行モデルLambda(ステートレス)サーバーレスランタイム(stateful session 対応)
Elicitation現行サポート範囲では扱えないstateful MCP session で対応
ツール実行中の対話不可(tools/list / tools/call のみ)可(stateful mode 有効時)
インフラLambda + インターセプターRuntime + MCP サーバー
変更コストTerraform / インフラ全面変更

Runtime は stateful MCP session をサポートしており、ツール実行中に「一時停止 → 追問 → 再開」の双方向フローが成立します。MCP の Elicitation をプロトコル準拠で動かすなら、Runtime が最有力な選択肢です。

なぜ Runtime が必要なのか:stateful session の有無

Gateway の公式ドキュメントが明示しているサポート範囲は tools/listtools/call です。AWS が「Gateway は Elicitation 非対応」と明示的に書いているわけではありませんが、現行のサポート範囲から elicitation/create が通らないと読むのが自然です。

一方、Runtime は MCP の stateful mode をサポートしています。デフォルトは stateless な streamable HTTP ですが、Elicitation や sampling を使う場合は stateful mode を有効にできます。stateful session 内では、ツール実行中に sendElicitation() で一時停止し、ユーザーの回答を待ち、そのコンテキストを保持したまま処理を再開できる。

これは MCP Elicitation だけの話ではありません。MCP 仕様が拡張していく双方向機能 — サーバーからの通知、プログレス報告など — は、stateful session を前提としています。Gateway のアーキテクチャでは、仕様の進化に追随することが難しくなる可能性があります。

今回の判断:Runtime への移行

この制約を受けて、プロジェクトとしては AgentCore Runtime への移行を検討しています。まず LangFuse 分析エージェントをパイロットとして Runtime に移行し、うまくいけば他のエージェントも順次切り替えていく想定です。

移行の動機は Elicitation だけではありません。実際に移行を進める中で、Runtime でなければ実現が難しい要件が複数見えてきました。

  • Elicitation: ツール実行中にユーザーへ追加質問し、回答を受けて処理を続行する
  • Progress 通知: エージェント実行中の進捗(どのツールを呼んでいるか、どの段階か)をリアルタイムにチャット画面へ表示する
  • HITL(Human-in-the-Loop): 破壊的操作の承認フローを MCP ネイティブで実現する
  • 長時間実行: LangGraph ベースの Python エージェントは複数ツールを連鎖的に呼び出すため、Lambda のタイムアウト制約(最大 15 分)では厳しいケースがある

これらはいずれも「ツール実行中に状態を保持したまま外部とやり取りする」という共通の要件を持っており、Gateway のステートレスモデルでは構造的に対応しきれません。

クライアント側は先行して Elicitation を受信・表示・応答する仕組みを実装済みなので、サーバー側が Runtime に切り替わり MCP ネイティブの elicitation/create を送るようになれば、そのまま繋がります。

まとめ

MCP ツールに「足りない引数」を補う手段は 1 つではありません。

  • LLM の聞き返し — 文脈理解が必要な曖昧さの解消に
  • Generative UI — 複数フィールドの一括入力に
  • MCP Elicitation — サーバーが動的に選択肢を生成するリソース選択に
  • HITL 承認 — 破壊的操作の最終確認に

どれか一つに統一するのではなく、それぞれの強みを活かして共存させるのが現実的な設計です。

そしてインフラ面では、同等の UX はアプリケーション層の独自実装でも実現できます。ただし、MCP ネイティブ準拠で Elicitation を動かしたいなら、stateful session をサポートする AgentCore Runtime が最有力な選択肢です。Gateway のステートレスアーキテクチャでは現行のサポート範囲に制約があり、MCP 仕様の進化に追随していくなら Runtime への移行を検討する価値があるでしょう。

前の記事Code Modeとは何か