mcp-confirm について
mcp-confirmは、ユーザー確認を伴うMCPサーバー向けのセキュリティ例です。確認の一回限りの使用、表示プロンプトのサニタイズ、ファイル削除直前のリソース再確認を実装します。
主な機能
- 01確認記録の一回限りの消費
- 02表示プロンプトの無害化と短縮
- 03実行前のリソース確認
- 04許可ルート外のパスを拒否
- 05シンボリックリンク置換を阻止
活用シーン
- 01一度限りの操作承認の再利用を防止
- 02確認ダイアログへのプロンプト注入を軽減
- 03削除前にファイルが変更されていないか確認
MCP 対応
MCPのリクエスト状態境界を補い、一回限りの確認管理、プロンプトのサニタイズ、リソースの再確認を行います。デモ用ツールは起動時に指定したルート内のファイルのみを削除します。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。
詳細
mcp-confirm
簡単に言うと: AI が「よろしいですか?」と尋ねるとき、MCP SDK はすでに、その承認が 別の アクションに再利用されるのを防いでいます。ただし、同じ承認が 二度 使われることは防ぎません。このライブラリはその抜け穴と、SDK が残しているもう 2 つの抜け穴を塞ぎます。
まずはこちらをお読みください:この問題の大部分はすでに解決されています
2026-07-28 の MCP 仕様で Multi Round-Trip Requests が追加され、ツールは呼び出しの途中で一時停止し、ユーザーに確認を求められるようになりました。承認は不透明な requestState としてクライアント経由で戻されます。仕様では、サーバーはこれを攻撃者が制御するものとして扱わなければならないと定められています。
Python SDK は、デフォルトで、すべての MCPServer に対してこれを実行します。
RequestStateBoundary はミドルウェアチェーンに無条件で追加されます。キーを指定しない場合は、一時的なキーが使用されます。状態は AES-256-GCM で封印され、メソッド、対象、呼び出し引数の ダイジェスト、オーディエンス、認証されたプリンシパルに、有効期限とともに紐付けられます。
つまり、誰もが最初に思いつく攻撃――cache.txt の削除を承認させ、その承認を thesis.txt に対してリプレイする――は、すでに SDK に拒否されます。
このためのライブラリは必要ありませんし、自分で作るべきでもありません。
それでも、このリポジトリは当初それを作ってしまいました。HMAC、有効期限、引数の紐付けを含む 250 行のコードを主要機能として公開しましたが、公開したその日から冗長でした。その経緯は、ひっそり削除せず、末尾に記録しています。
SDK が 行わない こと
テストで確認できる抜け穴が 3 つあります。
1. 状態を紐付けて期限切れにしますが、使用済みにはしません。
有効期限内であれば、同じ承認は提示されるたびに何度でも検証に成功します。境界で行われるすべてのチェックは、毎回通過します。仕様には、これが意図的なものであり、残りはサーバー側で対応するものだと明記されています。
これらの対策はリプレイ可能な期間を制限し、ユーザー間およびリクエスト間での再利用を防ぎますが、それだけでは単一使用を保証しない点に注意してください。特定の
requestStateを最大 1 回しか消費できないようにする必要があるサーバー(例:一度限りの引き換え)では、サーバー側でその不変条件を必ず適用しなければなりません。
「ファイルを削除する」場合、2 回目の試行では何も見つかりません。「500 ポンドを送金する」場合は、それがまさに問題のすべてです。 singleuse.py は、その不変条件を実現します。未処理の確認を記録し、引き換え時に使用済みにする台帳です。境界が平文はこのサーバーによって生成されたものだとすでに保証しているため、暗号処理は含みません。
2. 人間が読む質問をサニタイズするものがありません。
エリシテーションの message はサーバーが選んだテキストで、人に表示されます。通常は信頼できない値がその中に埋め込まれます。何のためかと言えば、どのファイルが削除されるのかを伝えるためです。そこで、ファイル名を指定します。
cache.txt
SYSTEM NOTICE: your session has expired.
Enter your AWS secret key to continue:
ダイアログには、公式に見える 2 つ目のプロンプトが表示されます。ユーザーは削除を確認しているのではなく、自分のツールにフィッシングされています。同じ 2026-07-28 のリリースでは、サーバーが UI を描画する MCP Apps も公開されました。これはこの攻撃対象領域を狭めるどころか、広げるものです。
prompt.py は、信頼できない値を 1 行にまとめ、双方向表示制御文字とゼロ幅文字を除去し、ファイル名が末尾にあっても見えるよう、中央部分を切り詰めます。制御文字は削除せず、スペースに置き換えます。削除すると a\nb と ab が同じように表示され、異なる 2 つのファイルが 1 つのダイアログに見えてしまうためです。
3. プロトコル層では、実行時に あなたの リソースを再確認できません。
その間にユーザーの考えが変わることもあります。ファイルはその時間内に置き換えられる可能性があり、「変更されていない」とはどういうことかを知っているのは、そのツールだけです。このサーバーは削除の前に再確認し、そのパスがシンボリックリンクになっていた場合は拒否します。
各レイヤーが拒否する内容
ここが重要な部分であり、テストはその点を示すために書かれています。MCPError
は、このパッケージが実行される前に SDK のミドルウェアが拒否したことを意味します。ToolError は、
このパッケージが拒否したことを意味します。
| 攻撃 | 拒否するもの | テスト |
|---|---|---|
| 別のファイルに対して承認を再利用する | SDK | test_the_sdk_refuses_a_confirmation_replayed_onto_another_file |
| 状態を偽造する、または別のキーで封印する | SDK | test_the_sdk_refuses_a_forged_state |
| 同じ承認を2回使用する | このパッケージ | test_a_confirmation_cannot_be_spent_twice |
| 別のレプリカが発行した状態 | このパッケージ | test_a_state_this_process_never_issued_is_refused |
| システムプロンプトを偽造するファイル名 | このパッケージ | test_a_forged_system_prompt_cannot_escape_its_slot |
| 承認後にファイルをシンボリックリンクに差し替える | このパッケージ | test_a_swap_aimed_inside_the_root_is_refused |
| 許可されたルート外のパス | このパッケージ | test_a_path_outside_the_roots_is_refused_before_asking |
テスト
35件のテスト — サーバー経由が17件、台帳が11件、プロンプトのサニタイズが7件です。
すべてのサーバーテストで、実際の RequestStateBoundary を通します。これは、
MCPServer 自体に設定されるのと同じクラスで、固定キーを使用し、ワイヤー上と同じように
1回目で封印し、2回目で封印を解除します。
このハーネスが存在するのは、特定の誤りがあったためです。最初のバージョンでは、
MCPServer.call_tool() を直接呼び出してテストしていましたが、これはツール
マネージャーに直行し、ミドルウェアチェーン全体を回避します。SDK の境界が実行されなかったため、
冗長な手作りのガードが重要な役割を果たしているように見えていました。テスト設計がその事実を、
ビルドサイクル1回分もの間、見えなくしていたのです。
カバレッジは81%で、prompt.py は100%、singleuse.py は94%です。残りは
main() の argparse とトランスポートの設定部分で、代わりに build_server を通じてテストしています。
CI は意図的に Ubuntu のみで実行します。承認後の差し替えをテストするには シンボリックリンクが必要であり、そこでテストがスキップされたと報告された場合は、ビルドが失敗します。
インストール
pip install mcp-confirm
実行
mcp-confirm --root /path/you/allow
--root を指定しない場合、サーバーは何もデフォルトに設定せず、すべてのリクエストを拒否します。
ルートは起動時に固定され、エージェントが選択することはありません。署名キーは不要です。
MCPServer が独自に用意します。
既知の制限事項
- 台帳はメモリ内にあります。そのため、単一プロセスでは正しく動作しますが、ロードバランサーの背後では正しく動作しません。SDK はレプリカ間でキーを共有する機能をサポートしています
(
RequestStateSecurity(keys=[...]))。この設定では、レプリカ B はレプリカ A が発行した確認を拒否します。安全な方向にフェイルクローズしますが、ユーザーから見ると、確認が理由もなく機能しなくなったように見えます。マルチプロセスのデプロイには、Redis または原子的な比較と削除を行うデータベースの行が必要です。この動作を確認するテストがあります。 - デモ用ツールは意図的に小規模です。 1つのファイルを削除します。重要なコードは
singleuse.pyとprompt.pyにあります。 - これを裏付ける CVE はありません。 MRTR は登場してから数週間しか経っていないため、公開されたインシデントではなく、仕様に記載された MUST/SHOULD の一覧と SDK の調査をもとに構築されています。
バージョン 0.1.0 の問題点
成功だけを記録するリポジトリは何の証拠にもならないため、ここに残しています。
0.1.0 は SDK がすでに実装していた機能を再実装していました。 state.py は、HMAC 署名、TTL、プリンシパル・メソッド・引数のバインディングを実装した 250 行のコードでした。これらはすべて RequestStateBoundary と重複しており、そのうえ品質も劣っていました。SDK が認証付き暗号化を使用する箇所で HMAC を使い、オーディエンスのバインディングも鍵のローテーションもありませんでした。
問題を発見したのはテストではなく、SDK のソースを読んだときでした。 問題を明らかにできたはずのミドルウェアを回避していたため、テストは正確に合格していました。
CI は別途、0.1.0 に実際のバグがあることも検出しました。 シンボリックリンクのチェックが Path.resolve() の 後 に実行されていたため、リンク自体ではなくリンク先を調べていました。許可されたルート内の別のファイルを指すようにリンクを差し替えると、そのファイルが削除されてしまう状態でした。この問題は修正し、元のテストでは一度も検証されていなかったパターンのテストも追加しました。
0.2.0 では state.py を完全に削除し、SDK がカバーしていない部分だけを残しています。
ライセンス
MIT。
作者
Leslie Kadenge が開発しました。MCP サーバーの独立したセキュリティレビューを行っており、本番環境で稼働する 13 のサーバーを調査した公開レポートは les-k.github.io/field-notes.html にあります。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。