AIトレンド

AIエージェントが「手を止めて確認する」仕組み——MCP Elicitationが変えるヒューマン・イン・ザ・ループの形

MCPのElicitation機能は、AIエージェントが処理の途中で人間に構造化された質問を投げかける仕組みだ。「全自動か全手動か」の二択を超えた設計について解説する。

公開: 2026年3月24日約11分

AIエージェントが最も危ないのは、暴走する瞬間ではなく、「何をすべきかわからないのに黙って進む」瞬間だ。

MCPのElicitationは、その問題に対処するために設計された機能だ。AIエージェントが処理の途中で人間に構造化された確認を求め、必要な情報を得てから次のステップに進む仕組みを提供する。


「全自動か全手動か」という誤った二択

AIエージェントに仕事を任せようとすると、二つの極端な設計に行き着きがちだ。

一方は全自動——AIに全てを委ねて、途中で何も確認しない。作業は速いが、誤った前提で進んでいても誰も止められない。もう一方は全手動——ステップごとに人間が確認する。安全だが、エージェントを使う意味が薄れる。

本番環境へのデプロイ、データベースの削除、ファイルの一括変更。こういった「取り返しのつかない操作」を前にしたとき、「自動で進む」設計は怖い。かといって「全て人間が判断する」設計は、エージェントの価値を消してしまう。

MCP Elicitationは、このどちらでもない第三の設計を可能にする。「必要な時だけ人間に聞く」というパターンだ。


一発勝負のツール呼び出しと、その限界

従来のMCPツール呼び出しは一発勝負だ。AIが判断してツールを呼び出し、結果が返ってくる。ブランチ名が不明ならデフォルト値を使うか、エラーを返すかの二択しかない。

# ❌ 旧来: 欠損パラメータはエラーかサイレントなデフォルト値
@mcp.tool()
async def deploy_to_production(branch: str = "main") -> str:
    # 本番デプロイ、取り消せない。
    # branchが本当に正しいか確認する手段がない
    return f"Deployed {branch} to production"

このコードには問題がある。branch が指定されなければ "main" をデフォルトにするが、AIが「main以外のブランチのつもりで指示していた」可能性がある。エラーにしても、AIは「情報が足りないからタスク失敗」と判断するだけで、ユーザーに聞き返す手段がない。

Elicitationを使えば、ツール実行の途中でユーザーに構造化フォームを表示し、必要な情報を取得してから処理を続けられる。

# ✅ Elicitation あり: 必要な時だけ人間に確認
from pydantic import BaseModel
from fastmcp import Context

class DeployConfirm(BaseModel):
    branch: str
    confirmed: bool

@mcp.tool()
async def deploy_to_production(ctx: Context) -> str:
    result = await ctx.elicit(
        message="本番環境にデプロイします。ブランチと操作を確認してください",
        schema=DeployConfirm,
    )
    if result.action != "accept" or not result.data.confirmed:
        return "デプロイをキャンセルしました"
    return f"Deployed {result.data.branch} to production"

ユーザーの画面にはフォームが表示される。ブランチ名を入力し、確認チェックボックスをオンにしてから「実行」を押す。それを受け取ったエージェントが処理を続ける。


elicitation/create の仕組み

ElicitationはMCPの仕様バージョン2025-06-18で導入されたRPCメソッドだ。通常のMCPはクライアント(AIアプリ)からサーバー(ツール)へのリクエストが基本だが、Elicitationはサーバーからクライアントへの逆方向リクエストになっている。

MCPサーバーが elicitation/create を発行すると、クライアントはUIにフォームを表示してユーザーの入力を待つ。入力が完了するとクライアントからサーバーへ結果が返り、処理が再開する。

リクエストのJSON構造はこうなる:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "elicitation/create",
  "params": {
    "message": "デプロイ先の環境を選択してください",
    "requestedSchema": {
      "type": "object",
      "properties": {
        "environment": {
          "type": "string",
          "title": "環境",
          "enum": ["staging", "production"],
          "enumNames": ["ステージング(テスト)", "本番環境"]
        },
        "confirmed": {
          "type": "boolean",
          "title": "確認",
          "description": "本番への変更は取り消せません"
        }
      },
      "required": ["environment", "confirmed"]
    }
  }
}

requestedSchema に使える型はシンプルに限定されている——string(format: email/uri/date/date-time対応)、number/integer(minimum/maximum指定可)、boolean(default指定可)、enum の4種類だ。ネストしたオブジェクトは使えない。クライアント側のUI生成を単純にするための意図的な設計だ。

クライアントはMCPの初期化ハンドシェイク時にElicitation対応を宣言する必要がある:

{ "capabilities": { "elicitation": {} } }

この宣言がないクライアントに対しては、MCPサーバーはElicitationを使うべきでない。


フォームモードとURLモード

Elicitationには2つのモードがある。

フォームモード(インバンド)は基本のモードで、MCPプロトコル経由でデータがやり取りされる。ユーザーが入力した情報はMCPクライアントを通ってサーバーに届く。名前や日付、選択肢のような一般的な入力に適している。

URLモードは仕様バージョン2025-11-25で追加された。クライアントがブラウザでURLを開き、ユーザーは外部のWebページで認証などの操作を完了する。操作結果はMCPプロトコルを通過しない。つまり、認証情報がLLMのコンテキストやMCPクライアントを経由しない設計になっている。

GitHubやGoogleへのOAuthフローはURLモードに向いている。アクセストークンがAIのコンテキストに混入するリスクを避けられる。

URLモードは非同期で動く。ユーザーがブラウザで操作を完了するまで数分かかることもある。サーバーは完了時に notifications/elicitation/complete でクライアントに通知する。


3つのアクション: accept / decline / cancel

Elicitationへのレスポンスには3種類のアクションがある。仕様書WorkOSのElicitation解説が詳しく説明している。

Loading diagram...

accept はユーザーが明示的に承認した状態だ。content フィールドに入力データが含まれ、サーバーはそのデータを使って処理を続ける。

decline はユーザーが明示的に拒否した状態だ。「このタスクはやらない」という意図的な判断なので、サーバーはリトライせず別の対応を取るべきだ。

cancel はユーザーが判断を保留した状態だ。Escapeキーを押した、画面を閉じたなどが該当する。「後でやる」の可能性があるのでリトライが許容される。

この3つの区別は重要だ。サーバー側が declinecancel を同じに扱ってしまうと、ユーザーが「やらない」と言っているのに同じ確認ダイアログが繰り返し表示される、不快な体験が生まれる。


パスワードを聞いてはいけない理由

フォームモードでMCPサーバーがパスワードやAPIキーを要求することを、仕様書は禁じている(MUST NOT)。

なぜか。フォームモードのデータはMCPプロトコルを経由してLLMのコンテキストに入る可能性がある。一度LLMのコンテキストに機密情報が入れば、ログに残ったり、別のツール呼び出しで露出したりするリスクがある。

機密情報が必要な場合はURLモードを使い、ブラウザの外部フローで処理する設計が正しい。

クライアント側にも実装上の義務がある。仕様書はクライアントがレート制限を実装すべき(SHOULD)と定めている。悪意あるMCPサーバーがElicitationを使ってユーザーを誘導したり、フィッシング的なフォームを大量表示したりすることを防ぐためだ。


Python SDKでの実装パターン

FastMCP(v2.10.0以降)を使うと、Elicitationをシンプルなコードで実装できる。MCP Python SDK公式のelicitation.pyを参考にした実装例を示す。

レストランの予約ツールで、満席だった場合に代替日を聞く例だ:

from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel
from fastmcp.server.elicitation import AcceptedElicitation

mcp = FastMCP("Restaurant MCP")

class AlternativeBooking(BaseModel):
    try_another_date: bool
    preferred_date: str  # 例: "2024-12-26"

@mcp.tool()
async def book_table(date: str, party_size: int, ctx: Context) -> str:
    """指定日にテーブルを予約する"""
    if is_fully_booked(date):
        result = await ctx.elicit(
            message=f"{date}は満席です。別の日を試しますか?",
            schema=AlternativeBooking,
        )
        if isinstance(result, AcceptedElicitation) and result.data.try_another_date:
            return await book_table(result.data.preferred_date, party_size, ctx)
        return "予約をキャンセルしました"
    return f"{date}に{party_size}名でご予約しました"

「12月25日は満席」→ フォームが表示される → ユーザーが「12月26日にする」と回答 → 予約完了。この一連の流れがエージェントとユーザーの対話として実現する。


対応済みクライアント

Elicitation対応のクライアントはすでに複数登場している。

GitHub Copilot in VS Code v1.102(2025年6月リリース)は、Elicitation対応した最初の主要クライアントの一つだ。GitHub Blogの解説記事では、チックタックトーゲームのMCPサーバーが「難易度」「プレイヤー名」「先攻後攻」をElicitationで問い合わせ、VS Code上にモーダルが表示される様子が示されている。

Claude Codeはバージョン2.1.76(2026年3月14日リリース)でElicitationに対応した。Claude Code Changelogによると、フォームモードとURLモードの両方に対応しており、Elicitation フックと ElicitationResult フックも追加されている。フックを使うことで、elicitationリクエストをインターセプトして独自の処理を挟むことも可能だ。

Building smarter interactions with MCP elicitation: From clunky tool calls to seamless user experiencesExplore how MCP elicitation transforms AI tool interactions by gathering missing information upfront, plus practical tips.github.blog

対話型ワークフローという設計の変化

Elicitationが示しているのは、AIエージェントの設計に関する考え方の変化だ。

これまでのエージェントは一方通行だった。入力を受け取り、ツールを呼び出し、結果を返す。処理の途中でユーザーに確認を取る仕組みがなかった。

Elicitationで双方向になる。エージェントが処理の途中で立ち止まり、「ここは私が判断するより聞いたほうがいい」と判断して問い合わせる。ユーザーが答えたら処理を再開する。

空港の搭乗ゲートに近いイメージだ。全員を一度に止めて全行程をチェックするのではなく、「こちらの便のお客様だけこちらへ」と必要な時だけ呼び止める。それ以外は進める。

「暴走するAI」への不安と「毎回手動でチェックしなければならない」面倒さ。その両方に対して、Elicitationは一つの設計上の回答を出している。全自動でも全手動でもなく、「判断が必要な場所だけ人間を巻き込む」という設計だ。

MCPを使ったツール開発をするなら、Elicitationの活用を設計の選択肢に入れておく価値がある。

Elicitation - Model Context Protocolmodelcontextprotocol.io