Claude CodeのMCPツールフック
type: mcp_toolを使ってClaude CodeのフックからMCPサーバーのツールを直接呼び出す方法。スキーマ、変数展開の構文、ユースケース、本番パターンまで解説します。
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。
問題: フックはシェルスクリプトで動いています。MCPサーバーを呼び出すたびにサブプロセスを立ち上げ、トランスポートの配線、認証処理、レスポンスのパース、JSONのstdout出力と、毎回同じ作業が発生します。ファイル書き込みのたびに動くフォーマッターやセキュリティチェックだと、そのオーバーヘッドが積み重なります。
解決策: v2.1.118からフックに新しいtypeが追加されました。MCPツールをサブプロセスなしで直接呼び出せます。.claude/settings.jsonにこれを追加するだけで、ファイル書き込みのたびにセキュリティスキャンが走ります:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "semgrep",
"tool": "scan_file",
"input": { "path": "${tool_input.file_path}" }
}
]
}
]
}
}MCPサーバーはすでに起動しています。フックはシェルをスキップして、サーバーのRPC接続に直接呼び出しをかけます。ツールのテキスト出力は、commandフックと同じJSONデシジョンパーサーに渡されます。
type: "mcp_tool" とは何か
v2.1.118より前、フックにはcommand、http、prompt、agentの4つのハンドラータイプがありました。今は5つです:
| Type | 何が実行されるか |
|---|---|
command | シェルのサブプロセス(stdin/stdout) |
http | URLエンドポイントへのPOST |
mcp_tool | 接続済みMCPサーバーへの直接RPC呼び出し |
prompt | シングルターンのLLM評価(デフォルトはHaiku) |
agent | Read/Grep/Globアクセスを持つマルチターンのサブエージェント |
mcp_toolタイプはcommandやhttpと同様に、すべてのフックイベントに対応しています。注意点は一つ:SessionStartとSetupはサーバー接続中に発火するため、初回実行時に「サーバー未接続」エラーが出ることがあります。2回目以降は問題ありません。
フルスキーマ
mcp_toolフック固有のフィールドは3つです。残りはすべてのフックタイプで共通です:
{
"type": "mcp_tool",
"server": "my-mcp-server",
"tool": "tool_name",
"input": {
"arg1": "${tool_input.file_path}",
"arg2": "${session_id}"
},
"timeout": 30,
"statusMessage": "Checking...",
"if": "Edit(*.ts|*.tsx)"
}| フィールド | 必須 | 説明 |
|---|---|---|
server | YES | settingsで設定したMCPサーバーの正確な名前 |
tool | YES | そのサーバー上のツール名 |
input | no | ツールに渡す引数。${path}変数展開に対応 |
timeout | no | フックがキャンセルされるまでの秒数 |
statusMessage | no | フック実行中に表示されるスピナーのテキスト |
if | no | パーミッションルール構文のフィルター。完全な呼び出しが一致したときだけフックが発火 |
重要: serverはMCP設定のサーバー名と完全一致している必要があります。1文字でも違うと、フックはエラーなく静かに失敗します。
変数展開(Input Substitution)
inputの文字列値は${field.path}というドット記法でフックのイベントJSONを参照できます。Write呼び出しのPostToolUseフックでは、イベントJSONはこうなります:
{
"session_id": "abc123",
"cwd": "/your/project",
"hook_event_name": "PostToolUse",
"tool_name": "Write",
"tool_use_id": "toolu_01...",
"tool_input": {
"file_path": "/your/project/src/api.ts",
"content": "..."
},
"tool_response": { "filePath": "/your/project/src/api.ts", "success": true },
"duration_ms": 142
}なので"${tool_input.file_path}"は/your/project/src/api.tsに解決されます。このオブジェクト内のフィールドはどれでも参照できます。duration_msフィールドはmcp_toolの1リリース後、v2.1.119で追加されました。
出力の処理方法
MCPツールのテキストコンテンツは、commandフックのstdoutとまったく同じように扱われます。有効なJSONとしてパースできればClaudeはデシジョンフィールドに従って動作し、そうでなければテキストはClaudeへのコンテキストになります。
デシジョンフィールドは他のフックと同じです:
{
"decision": "block",
"reason": "Security issue found in src/api.ts: SQL injection risk on line 42."
}PostToolUseのMCPツールフックでこれを返すと、Claudeはメッセージを受け取ってファイルを修正します。ツールはすでに実行済みなので、これは予防ではなくアドバイスです。ツール実行前にブロックしたい場合はPreToolUseを使い、permissionDecision: "deny"を返してください。
mcp_toolフック(PostToolUse限定)に固有のフィールドが一つあります:updatedMCPToolOutputです。これにより、Claudeが会話に読み込む前に、別のツールの出力結果を書き換えられます。起動中のMCPサーバーが、他のツールの結果をClaude読み込み前に後処理できるということです。
シェルコマンドフックとの違い
スピードだけじゃありません。2つの具体的な違いがあります。
ステートフルなサーバー。 シェルのサブプロセスは毎回ゼロから起動します。MCPサーバーは独自の状態を持つ生きたプロセスです:ロード済みの設定、オープンな接続、キャッシュ、蓄積されたセッションコンテキスト。起動時にtsconfig.jsonをパース済みのリンティングMCPは、ファイル書き込みのたびに再パースしません。commandフックは毎回します。
シェル環境への依存がない。 commandフックはPATHが間違っていると、jqがインストールされていないと、~/.zshrcが非インタラクティブシェルで何かをstdoutに出力すると、静かに失敗します。MCPツールフックはそれをすべて回避します。呼び出しはClaude Codeから既存のRPC接続経由でサーバーに直接届きます。
ifフィールド:フックのスコープを絞る
ifがないと、フックはmatcherに合致するすべてのイベントで発火します。ifがあると、完全なツール呼び出し(名前と引数)がパーミッションルール構文に一致した場合だけフックが起動します:
{
"type": "mcp_tool",
"server": "semgrep",
"tool": "scan_file",
"if": "Edit(*.py|*.ts|*.js)",
"input": { "path": "${tool_input.file_path}" }
}このフックは.mdや.jsonファイルでは絶対に実行されません。ドキュメント編集が多いプロジェクトでは、体感できるパフォーマンス差が出ます。
パターン1:書き込みのたびにセキュリティスキャン
ファイルパスを受け取って検出結果を返すセキュリティMCPサーバー。何か見つかったらClaudeをブロックします:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "semgrep",
"tool": "scan_file",
"if": "Write(*.ts|*.py|*.js|*.go)",
"input": { "path": "${tool_input.file_path}" },
"statusMessage": "Scanning..."
}
]
}
]
}
}MCPツールが検出結果を返す場合、レスポンスをこう組み立てます:
{
"decision": "block",
"reason": "Semgrep finding: [description of issue at line N]"
}Claudeはブロックメッセージを受け取り、ファイルを修正します。スキャンはサーバーのキャッシュ済みルールセットで実行されるため、毎回サブプロセスをパースする必要がありません。
パターン2:外部検証を使ったStopフック
Claudeが完了を宣言する前に、関連チケットが本当にクローズされているか確認するStopフック。LinearやJiraのMCPを呼び出します:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "mcp_tool",
"server": "linear",
"tool": "get_issue_status",
"input": { "issue_id": "${tool_input.issue_id}" }
}
]
}
]
}
}MCPツールがチケットのステータスを返します。In Progressだった場合、レスポンスJSONにdecision: "block"と理由を含めます。Claudeは作業を続けます。
Stopフックのロジックでは必ずstop_hook_activeを確認してください。 イベントJSONには、Claudeが前回のStopフック発火から継続中のときに"true"がセットされるこのフィールドが含まれています。チェックしないサーバーは無限ループを作ります。MCPツール側にガードを組み込んでください:inputのstop_hook_activeが"true"なら、空の出力を返してきれいに終了する、という処理です。
パターン3:停止前に本番エラーをチェック
Claudeが機能を作り終えたあと、セッション完了とマークする前にステージングで何か壊れていないか確認します。SentryのMCPが調べてくれます:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "mcp_tool",
"server": "sentry",
"tool": "get_new_errors_since",
"input": { "minutes": "5", "skip_if_active": "${stop_hook_active}" }
}
]
}
]
}
}直近5分間に新しいエラーが発生していれば、MCPツールはそれをdecision: "block"とともに返します。Claudeはエラーの詳細を読んで、停止前にリグレッションを修正します。
パターン4:プロンプトのたびにドキュメントを自動注入
UserPromptSubmitフックにContext7のMCPを組み合わせて、プロンプトで言及されているライブラリのドキュメントをClaudeが処理する前に取得します:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "mcp_tool",
"server": "context7",
"tool": "get_library_docs",
"input": { "prompt": "${prompt}" },
"timeout": 15
}
]
}
]
}
}以前はClaudeが明示的にMCPツールを呼び出す必要がありました。今は毎回のプロンプトで自動的に行われます。Claudeは学習データではなく、最新のドキュメントからスタートします。
パターン5:エージェントチームへのポリシー適用
マルチエージェントワークフローで、共有ポリシーMCPサーバーを使ってどのエージェントがどのディレクトリに書き込めるかを管理できます。CLAUDE_AGENT_NAME環境変数で現在のエージェントを識別します。PreToolUseフックがポリシーサーバーを呼び出します:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "mcp_tool",
"server": "policy-server",
"tool": "check_write_permission",
"input": {
"agent": "${agent_name}",
"path": "${tool_input.file_path}"
}
}
]
}
]
}
}ポリシーサーバーが完全な認可マップを持っています。サーバーを一度更新すれば、すべてのプロジェクトのすべてのエージェントが新しいルールを引き継ぎます。settings.jsonを一つも触らずに。
パターン6:エージェントフロントマターのMCPツールフック
フックはsettings.jsonに置く必要はありません。エージェントのYAMLフロントマターに書いて、そのエージェントのライフサイクルにスコープを絞ることもできます:
---
name: backend-developer
description: Builds API endpoints and database logic
hooks:
PostToolUse:
- matcher: "Write"
hooks:
- type: mcp_tool
server: semgrep
tool: scan_file
input: { "path": "${tool_input.file_path}" }
Stop:
- hooks:
- type: agent
prompt: "Verify all API endpoints have corresponding tests. Block if any are missing."
---オーケストレーションされたチームの各専門エージェントが、自分のバリデーションロジックを持ちます。バックエンドエージェントはセキュリティ問題をスキャンし、フロントエンドエージェントはアクセシビリティをチェックします。全員に適用されるグローバルフックは不要です。
Elicitation制御
Elicitationイベントは、MCPサーバーがタスク中にユーザー入力を求めると発火します。mcp_toolフックでシークレットマネージャーを呼び出して、既知のプロンプトに自動応答できます:
{
"hooks": {
"Elicitation": [
{
"matcher": "my-db-server",
"hooks": [
{
"type": "mcp_tool",
"server": "secrets-manager",
"tool": "get_credential",
"input": { "key": "${elicitation.field_name}" }
}
]
}
]
}
}予測可能なクレデンシャルの問い合わせは自動で解決されます。タスクは中断なく進みます。
フックと相性のいいMCPサーバー
すべてのMCPサーバーがフックのターゲットとして有効なわけではありません。ユーザー介入なしに特定のイベントで発火させたいツールが、最も相性がいいです:
| サーバー | イベント | 何をするか |
|---|---|---|
| Semgrep | PostToolUse: Write | 書き込みのたびにセキュリティスキャン |
| Sentry | Stop | 完了前にステージングの新しいエラーを確認 |
| Linear / Jira | Stop, TaskCompleted | チケットステータスの確認、完了時に更新 |
| Context7 | UserPromptSubmit | 言及されたライブラリのドキュメントを自動取得 |
| ElevenLabs | Stop, Notification | タスク完了時にTTSで音声通知 |
| Slack | Notification, Stop | curlボイラープレートなしでチームにアラート |
| E2B | Stop | 完了マーク前に生成されたスクリプトをサンドボックスで実行 |
| claude-mem | PostCompact, SessionStart | コンパクション後にセッションコンテキストを復元 |
| n8n | TaskCompleted | 完了時に外部ワークフローをトリガー |
既知の問題:PostToolUse + MCPイベント + additionalContext
MCPツール呼び出しがトリガーとなったイベントでadditionalContextが静かに破棄されるバグがあります(GitHub issue #24788)。これはmcp_toolフック自体ではなく、MCPツールイベントに反応するtype: "command"フックに影響します。
区別が重要です:MCPの呼び出しであるフックは正常に動作します。MCPツール呼び出しに反応してadditionalContextを返すフックは動作しません。回避策は、MCPツール呼び出しをターゲットにしたPostToolUseフックでの重要なメッセージに、exit 2とstderrを使うことです。ブロッキングパターンは動作します。アドバイザリーな注入は動作しません。
MCPツールフックはフックシステムの最後のピース
以前、フックはセーフティネットでした。危険なものをブロックしたり、フォーマッターを実行したりするシェルコマンド。ステートレスで、プロセスローカルで、MCPサーバーが持つ情報とは切り離されていました。
今は違います。フックは決定論的なオーケストレーション層です。どんなイベントにも、どんなMCPツールにも対応し、完全なデシジョン制御が可能で、呼び出しをまたいで状態が維持され、サブプロセスのオーバーヘッドもありません。
パイプラインはこれで完成しました。PreToolUseが検証し、PostToolUseがフォーマットとスキャンを行い、PostToolBatchがテストを実行し、Stopが実際の外部データで検証します。すべてのステップをMCPツールの呼び出しにできる。シェルスクリプトはもう必要ありません。
設定をやめて、構築を始めよう。
AIオーケストレーション付きSaaSビルダーテンプレート。