MCPサーバー(自前でも他社製でも)をClaude Codeに繋いでいて、サーバー側がツール実行の途中で追加質問フォームを出してくる場面に、回答を自動化したり、ユーザーが入力した値を送信前に書き換えたりしたい人向け
自前で書いた calendar MCP に毎回「どの日に登録?」と聞かれて手で答えるのが面倒で、明日固定で自動回答させたい時は Elicitation で accept を返す。社外ベンダー製のMCPに社員IDの生値を渡したくない時や、ユーザー入力をJSTからUTCに直してから送りたい時は ElicitationResult で content を上書きする。MCPサーバーが elicitation 機能を実際に呼ばないなら一切発火しないので、まず使ってる MCPサーバーが elicitation を出すかを確認してから設定する
MCPサーバーに「ツールを動かしてる途中で、ユーザーに追加質問したい」という機能があります。MCP の世界ではこれを elicitation と呼びます。
たとえば自前で書いた calendar MCP サーバーが「予定登録ツール」を動かしている途中で、「どの日に登録しますか?」と短いフォームを Claude 経由でユーザーに出してくる。この問い合わせが走るたびに、Claude Code 側のフック2種が前後で割り込めるようになっています。それが Elicitation と ElicitationResult です。
2つはペアで動きます。Elicitation が「ユーザーに出す前」に介入する側、ElicitationResult が「ユーザーが答えた後、MCPサーバーに返す前」に介入する側。順番で言うとこうなります。
MCPサーバーが追加質問を出す
↓
【Elicitation フックがここで割り込める】
↓
ユーザーがフォームに入力 ※ Elicitation 側で代行も可能
↓
【ElicitationResult フックがここで割り込める】
↓
入力内容が MCPサーバーに送られる
噛み砕くと
レストランの「店員が席まで来てオーダー追加を聞く」場面が一番近いです。MCPサーバーが店員で、ユーザーが客。普段はそのやり取りに第三者は入りません。
このフック2種はキッチンとホールの間に立つマネージャーみたいな役割で、「今日は店長判断で全員ドリンクこれにしときます」と店員のオーダー取りを肩代わりすることもできるし、「客が頼んだのはアイスだったけど、室温的にホットに差し替えて伝票回します」と注文内容を裏で書き換えることもできる。前者が Elicitation、後者が ElicitationResult です。
客側は店員と直接話してるつもりですが、間にマネージャーがいる、という構図です。
大事な前提:このフックが効くのは MCP サーバーが elicitation を出してくる時だけ
普通のツール呼び出し、たとえば Read / Write / Bash といった Claude Code 内蔵ツールの呼び出しでは一切走りません。MCPサーバー側が「ユーザーに追加質問させたい」と判断して elicitation の依頼を出してきた瞬間にだけ発火します。
もう1つ大事なのが Matcher の指定単位です。Matcher には「どの MCP サーバーから来た elicitation に反応するか」を、MCPサーバーの名前で書きます。ツール名ではない。
「予定登録ツール」を例に、実際の手順を見る
自前で書いた calendar MCP サーバーが、予定登録ツール mcp__calendar__add_event を動かしている途中、Claude にこう質問を出してくるとします。「どの日に登録しますか? date は文字列、YYYY-MM-DD 形式」。
普段は「明日」で固定したい。同時に、ユーザーが手動で日付を入力した場合、それを MCP サーバーに送る前に JST から UTC に補正したい。この2要件を Elicitation と ElicitationResult の2枚で組みます。
ステップ1: フック設定の場所を開く
Claude Code のフック定義はプロジェクト直下の .claude/settings.json に書きます。なければ作ります。
{
"hooks": {
"Elicitation": [],
"ElicitationResult": []
}
}
ステップ2: Elicitation 側を書く(自動回答で人間スキップ)
Matcher に MCPサーバーの名前 calendar を指定し、bashスクリプトを呼び出します。スクリプトは入力としてフック情報のJSONを受け取り、出力に「明日の日付で accept した」というJSONを返します。
{
"hooks": {
"Elicitation": [
{
"matcher": "calendar",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/calendar-auto-date.sh"
}
]
}
]
}
}
ステップ3: 中身のスクリプトを置く
bashで明日の日付を作って accept を返します。これでユーザー操作は走らず、フォームに「明日の日付」が自動入力された状態でフローが進みます。
#!/bin/bash
TOMORROW=$(date -v+1d '+%Y-%m-%d')
cat <<JSON
{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {"date": "${TOMORROW}"}
}
}
JSON
※ date -v+1d は macOS の BSD date 専用です。Ubuntu / Debian など Linux 環境では TOMORROW=$(date -d '+1 day' '+%Y-%m-%d') に置き換えてください。
action は3種類あります。accept はユーザーに代わって回答完了、decline はこのフォーム自体を断る、cancel はフロー丸ごと中断。今回は accept。
ステップ4: ここで初心者がやりがちな勘違いがある
「Elicitation で accept してるんだから、もう ElicitationResult は呼ばれないのでは?」と思いがちですが、呼ばれます。Elicitation の accept は「ユーザーの代わりに答えた」という意味なので、その後の「答えを MCPサーバーに送る前の最終チェック」として ElicitationResult が同じように発火します。
つまり Elicitation でユーザーをスキップしても、ElicitationResult は通る。2段構造はそのまま維持されます。
ステップ5: ElicitationResult 側を書く(タイムゾーン補正)
ユーザーが手動で日付を入れた場合、JST想定の文字列を UTC に直して MCP サーバーに送りたい。ElicitationResult で content を上書きします。
{
"hooks": {
"ElicitationResult": [
{
"matcher": "calendar",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/calendar-tz-shift.sh"
}
]
}
]
}
}
ステップ6: 補正スクリプトを書く
入力JSONから content.date を取り出して、JSTのまま MCP サーバーに送ると9時間前倒しでズレるので、UTC方向に補正した値で content を返します。jq が入ってる前提です。
#!/bin/bash
INPUT=$(cat)
DATE=$(echo "$INPUT" | jq -r '.content.date')
UTC_DATE=$(date -j -f '%Y-%m-%d %H:%M' "${DATE} 00:00" '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)
cat <<JSON
{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "accept",
"content": {"date": "${UTC_DATE}"}
}
}
JSON
※ date -j -f は macOS の BSD date 専用です。Linux では UTC_DATE=$(date -d "${DATE}T00:00:00" '+%Y-%m-%dT%H:%M:%SZ') に置き換えてください。
これで MCPサーバーには UTC で補正された日時が届く。ユーザーは普通にJSTで入力するだけで済みます。
つまり Elicitation / ElicitationResult は何をしてくれるのか
- やってくれる: MCPサーバーが出してくる追加質問フォームを、Claude経由でユーザーに見せる「前後」で介入できる。前で介入すれば自動回答してユーザー操作を省ける。後で介入すれば回答内容を書き換えて MCP に渡せる
- やってくれる: Matcher を MCPサーバー名で絞れるので、calendar には介入するけど slack-bridge には介入しない、みたいな選り分けができる
- やってくれない: Claude Code 内蔵ツールの通常のツール呼び出しには反応しない。あくまで MCPサーバーが elicitation を出してきた時だけ動く
- やってくれない: フォーム項目そのものを増やす/減らすことはできない。あくまで「フォームに対する回答」を肩代わり・書き換えするだけ
- 意味が薄い場面: 使ってる MCPサーバーがそもそも elicitation 機能を呼んでこない場合は一切発火しない。ツール呼び出しのたびに何か挟みたいだけなら PreToolUse の方が筋がいい
使いどころ3シナリオ(具体題材で再現)
シナリオ1: カレンダーMCPで毎回「明日」と答えてユーザー操作を省く
毎週の振り返り運用で「明日のタスクを calendar MCP に登録」を回す時、毎回「どの日?」と聞かれて手で入力するのは面倒です。Elicitation で action: "accept" と明日の日付を返す bashスクリプトを噛ませると、フォームが出ず無音で「明日」で登録されます。週次運用の摩擦が消える。
ただし、たまに「明後日」に登録したい日もあるので、スクリプト側で曜日を見て「金曜なら月曜の日付」「それ以外は明日」みたいに分岐させると、もっと使えるようになります。
シナリオ2: 社内DB MCP で社員IDを送る前にマスクする
社内DB を叩く MCPサーバーが「対象社員のIDを入れてください」とフォームを出してくるとします。普段はユーザーが手で社員IDを打つけど、社外ベンダー製の MCPサーバーに生のIDを渡したくないケースがある。
ElicitationResult でユーザー入力済の content から社員IDを取り出し、社内ハッシュに変換した値で content を上書きします。ハッシュというのは元の値を別の文字列に置き換えて、見ても元データが分からなくする仕組みのことです。MCP側には ID の代わりにハッシュが届くので、生データが社外に出ない。フックで「答えを送る前にすり替える」のは ElicitationResult の本領発揮ポイントです。
シナリオ3: タイポ多発のフォームを正規化
Slackブリッジ系の MCPサーバーが「投稿先チャンネル名を入れてください」というフォームを出してくる。ユーザーは #general と書いたり general と書いたり、全角シャープで #general と書いたりして安定しません。
ElicitationResult で content.channel を取り出し、ルールで # マークの有無を統一してから MCP に渡すと、Slack側で「そんなチャンネルない」と弾かれる事故が消えます。ユーザーには「入力の自由度」、MCPには「整った形」を両立できる。
初心者が踏みやすい落とし穴
- Matcher にツール名を書いてしまう。Matcher は MCPサーバーの名前で、
mcp__calendar__add_eventのようなツール名ではありません。calendar や slack-bridge のような、設定で自分が付けた識別名を書く。ツール名を書くと一切発火せず、半日くらい原因が分からなくて溶ける - form_schema を無視した content を返す。MCPサーバーは form_schema で「date は文字列、YYYY-MM-DD」のような形を指定してきます。それを無視した値を Elicitation 側で accept して返すと、MCP 側で reject される。form_schema は入力JSONに含まれるので、まず読んで形を合わせる
- Elicitation で accept したから ElicitationResult はもう来ないと思い込む。来ます。2段構造はスキップされない。前段で自動回答しても後段で再加工できる、という設計だと理解しておく
- cancel と decline を混同する。decline はこのフォーム自体を断る種別で、MCPサーバーには decline 状態が返ります。cancel は elicitation のやり取りを中断する種別。MCPサーバー側の挙動はサーバー実装次第なので、自前で MCP を書いてるなら両方の戻り値を実装側でハンドリングしておくこと
- Notification フックの elicitation 系 Matcher と混同する。Notification フックには
elicitation_dialog/elicitation_complete/elicitation_responseの3つの Matcher があるけど、これは「画面上でフォームが開いた/閉じた」という通知イベントで、回答を介入する用途には使えない。介入したいなら今回の2フック - 返すJSONが壊れる。bashでまとめて文字列を書き出す書き方、いわゆるヒアドキュメント
<<JSON ... JSONを使う時、変数の中身に"や改行が混ざるとJSONが壊れて Claude Code 側でパース失敗します。jq で組み立てる方が安全。たとえばjq -n --arg d "$TOMORROW" '{hookSpecificOutput: {hookEventName: "Elicitation", action: "accept", content: {date: $d}}}'のように書く - 毎回 accept しすぎてユーザーが置き去りになる。Elicitation を全部自動回答にすると、ユーザーが「いつ何が登録されたか」を把握できなくなる。最初は ElicitationResult 側でログだけ取って手動運用、慣れてから Elicitation で自動化、の順がおすすめ
- スクリプトが exit 2 で終わると自動的に decline 扱いになる。Elicitation / ElicitationResult ともに、フックスクリプトが終了コード2で終わると、JSONを返さなくても Claude Code 側が decline として処理します。jq のパース失敗・コマンド未インストール・ヒアドキュメントの文法ミスで「スクリプトが途中で死ぬ」と発生する。なぜか毎回 decline されると感じたら、まずスクリプト単体を手で叩いて
echo $?で終了コードを確認する
書き方
.claude/settings.json に書く形:
{
"hooks": {
"Elicitation": [
{
"matcher": "<MCPサーバー名>",
"hooks": [{"type": "command", "command": "<実行コマンド>"}]
}
],
"ElicitationResult": [
{
"matcher": "<MCPサーバー名>",
"hooks": [{"type": "command", "command": "<実行コマンド>"}]
}
]
}
}
各フックは標準入力でフック情報のJSONを受け取り、標準出力で hookSpecificOutput を返す。action は accept / decline / cancel の3種類
やってみるとこうなる
入力
Elicitation フックに標準入力で渡ってくるJSON:
{
"session_id": "abc123",
"hook_event_name": "Elicitation",
"mcp_server": "calendar",
"tool_name": "mcp__calendar__add_event",
"form_schema": {
"type": "object",
"properties": {
"date": {"type": "string", "description": "登録日(YYYY-MM-DD)"}
},
"required": ["date"]
}
}
出力例
Elicitation で「明日の日付」を自動回答して人間スキップ:
{
"hookSpecificOutput": {
"hookEventName": "Elicitation",
"action": "accept",
"content": {"date": "2026-05-20"}
}
}
ElicitationResult でユーザー入力をUTCに補正してから送る:
{
"hookSpecificOutput": {
"hookEventName": "ElicitationResult",
"action": "accept",
"content": {"date": "2026-05-20T00:00:00Z"}
}
}
このページに出てきた言葉
- MCPサーバー
- Claude Codeに繋いでツール群を増やすための外部プログラム。自前でもサードパーティ製でも入れられる
- elicitation(追加質問フロー)
- MCPサーバーがツール実行の途中で「ユーザーに追加で質問したい」と短いフォームを出してくるMCP公式の機能
- フック
- Claude Codeの特定タイミングで自動的に走らせる、ユーザー側が用意した小さなプログラム
- Matcher
- そのフックが「何に反応するか」を絞る指定。Elicitation / ElicitationResult では MCPサーバーの名前で書く
- form_schema
- MCPサーバーが「こういう項目を聞きたい」と提示してくるフォーム定義。JSON Schema 形式で書かれる
- action(accept / decline / cancel)
- elicitation への対応種別。accept は答えを返す、decline はフォーム要求自体を断る、cancel はやり取りを中断
- content
- フォームへの回答中身。form_schema で定義された項目名と値の組み合わせ(例: <code>{"date": "2026-05-20"}</code>)
- hookSpecificOutput
- フックがClaude Codeに返すJSONの最上位キー。hookEventName と action と content を入れる
- jq
- コマンドラインでJSONを切り出したり組み立てたりするための小さな道具。フックのJSON処理で定番