Dev Study
← AIを部品として組み込む仕組み
レッスン 12 / 12·約 9 分·第20期 AIを部品として組み込む仕組み

人を挟むのは、取り消せなくなる直前

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力
  2. DNS名前→IP
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理今ここ
  7. DB保存

このコースは、確率で答える部品の確かさを部品の外側のどこに書くかを見てきました。最終回は、その外側に人を置く場所を決めます。

まず結論

2026-09 時点の MCP 仕様(最新版 2026-07-28、前版 2025-11-25 も同文)は、ツール呼び出しを拒否できる人を挟むべき(SHOULD)とし、呼ぶ前にツールへの入力を利用者に見せるよう求めます。ツールに付く「破壊的でない」といった注釈は hint にすぎません。

承認が守れるのは、見せたものと実行されるものが一致しているときだけです。要約ではなく実引数と実際に起きることを見せ、承認は取り消せなくなる直前に絞ります。増やすほど読まれなくなるからです。

? 読む前に予測

エージェントがファイル削除ツールを呼ぶ直前の承認画面です。承認者が「頼んだ操作と違う」と気づくのに最も役立つ表示はどれでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

承認が守れるのは、見せたものが実行されるときだけ

このコースで見てきた部品は、呼ぶたびに分布から1つを引いてきます。ツールを呼ぶかどうかも、どんな引数で呼ぶかも、その1回の引きの結果です。最後の外側として、人を置く場所を考えます。

MCP 仕様のツールの章は、冒頭の警告でこう書きます。

For trust & safety and security, there SHOULD always be a human in the loop with the ability to deny tool invocations.

規範語は SHOULD で、MUST ではありません。同じ章は the protocol itself does not mandate any specific user interaction model とも書き、承認画面の形は決めていません。何を見せるかは、Security Considerations の節がクライアントに求めています。

Show tool inputs to the user before calling the server, to avoid malicious or accidental data exfiltration

OWASP LLM06:2025 の緩和策の6番目も require a human to approve high-impact actions before they are taken と、承認を実行の前に置きます。

ここから、承認が働く条件が2つ読めます。見せたものと実行されるものが一致していることと、まだ取り消せる時点であることです。

承認を、取り消せなくなる直前に置く
モデルが呼び出しを提案要約と実引数。要約は引数とずれうる
実行せずに計算する
dry-run実際に起きることの一覧。まだ何も変わっていない
一覧に対して判断する
人が承認 / 拒否要約ではなく一覧を見る
同じ計算をやり直し、一致したときだけ
commitここから先は取り消せない
  • ・承認の前は何度でも引き直せる。取り消せない一線の手前にだけ人を置く
  • ・承認した後に対象が変われば、承認は無効になる
具体実物

要約だけ読む承認者の前で、4件が消えた

標準ライブラリだけの模型で確かめます。削除ツール delete_files(pattern) を dry-run と commit の2段にし、承認画面に「モデルの要約」「実引数」「dry-run の結果」を並べました。モデルの要約は台本の文字列で、LLM の出力ではありません。 サーバの注釈はわざと destructiveHint=False と偽り、実行器は判定に使いません。

ケース2は、要約が「古いログ2件」なのに実引数が *.* です。一覧の全件が .log のときだけ承認する承認者は拒否しました。ケース3は同じ呼び出しを、要約だけを読む承認者に回しました(1回目の実行から抜粋。2回目も同じ結果、トークンの値だけ異なる)。

python3 l12-approve.py(ケース2・3、Python 3.14.6)コマンド出力
[2 summary does NOT match args]
┌ approval tool=delete_files (server annotation says destructiveHint=False; treated as hint only)
│ model's summary : delete the 2 old log files
│ actual arguments: {"pattern": "*.*"}
│ dry-run result : would delete 4 file(s): ['notes.txt', 'old-1.log', 'old-2.log', 'report.txt']
└ approval token : 2005cd7e78a4
human: DENY -> ['notes.txt', 'old-1.log', 'old-2.log', 'report.txt']
[3 summary-only reviewer on the same mismatched call]
┌ approval tool=delete_files (server annotation says destructiveHint=False; treated as hint only)
│ model's summary : delete the 2 old log files
│ actual arguments: {"pattern": "*.*"}
│ dry-run result : would delete 4 file(s): ['notes.txt', 'old-1.log', 'old-2.log', 'report.txt']
└ approval token : 3250a265e217
human: APPROVE
commit: deleted ['notes.txt', 'old-1.log', 'old-2.log', 'report.txt'] -> left: []

3〜5行目で、要約と実引数と一覧が食い違っています。ケース3の画面にも同じ一覧が出ていましたが、読まれずに最終行で4件すべてが消えました。見せるだけでは足りず、読まれて初めて承認が働きます。

ケース4では、承認のあと commit の前に old-3.log を足しました。

python3 l12-approve.py(ケース4、1回目の実行)コマンド出力
[4 approved, then a new file appears before commit]
┌ approval tool=delete_files (server annotation says destructiveHint=False; treated as hint only)
│ model's summary : delete the 2 old log files
│ actual arguments: {"pattern": "old-*.log"}
│ dry-run result : would delete 2 file(s): ['old-1.log', 'old-2.log']
└ approval token : ba437ac881e4
human: APPROVE
commit: REFUSED: state/args changed since approval (approved ba437ac881e4, now a1af88085209) -> left: ['notes.txt', 'old-1.log', 'old-2.log', 'old-3.log', 'report.txt']
✓ ここまでの確認

模型のケース4では、承認のあと commit の前に old-3.log が増え、commit は REFUSED になりました。拒否された理由はどれですか。

抽象原理

注釈は約束ではなく、承認は増やすほど薄まる

承認を省く手がかりとして、ツールの注釈が使えそうに見えます。MCP の ToolAnnotations には destructiveHint(既定 true)や idempotentHint(既定 false)があります。注釈が無いツールは「破壊的でありうる・冪等でない」と読むのが既定です。しかし schema.ts はこう書きます。

NOTE: all properties in ToolAnnotations are hints. They are not guaranteed to provide a faithful description of tool behavior

続けて、信頼できないサーバの注釈で使用を判断するなと書き、tools.mdx 側はそうした注釈を untrusted と扱うことを MUST としています。第12期「冪等は性質ではなく約束」で見た HTTP の冪等は、RFC 9110 が定める規定でした。MCP の hint は、記述の忠実さすら保証されません。

では全部の呼び出しに承認を付ければよいか。arXiv:2506.08837 は各コマンドの確認を検討し、This would be cumbersome for the user、確認する命令が opaque to non computer experts、攻撃がおそらく innocuous looking commands に紛れやすいと書きます。CaMeL の論文も、確認に慣れた利用者が may inadvertently approve malicious actions と書きます。

承認は数を増やすほど読まれなくなり、ケース3の承認者に近づきます。だから取り消せる操作には置かず、取り消せなくなる直前にだけ置きます。

具体実物

BEGIN と COMMIT には ROLLBACK があった

第17期「全部か無しか。境界は BEGIN と COMMIT で宣言する」で見たとおり、DB のトランザクションは複数の文を全部か無しかにまとめ、その範囲はアプリが BEGIN と COMMIT で宣言します。COMMIT の前なら ROLLBACK で取り消せます。確定の一線がどこかを、書く側が決めています。

ツール実行も、一線を書く側が決めるところは同じです。違うのは、削除や送信のような操作には一般に ROLLBACK が無いことです。そこでこの模型では、実行前に起きることを計算する dry-run が、BEGIN から COMMIT までの「まだ確定していない窓」の代わりを務めました。ケース4の REFUSED は、窓の中で見たものと確定させるものがずれたら確定しない、という線の引き方です。

第14期「ではどこに線を引くか」の結論どおり、判断に時間をかけるべきは引き返せない線だけです。承認も同じ場所に置きます。

✓ ここまでの確認

DB の BEGIN〜COMMIT と、ツール実行前の承認との類推は、どこまで成り立ちますか。

⇄ 2つを見比べる
具体例 A

DB のトランザクション

アプリが BEGIN で開き COMMIT で確定します。COMMIT の前なら ROLLBACK で全部を取り消せるので、確定前の窓は DB が用意しています。

具体例 B

破壊的なツールの承認

削除や送信には一般に取り消しがありません。模型では dry-run で起きることを計算して見せ、承認後に同じ計算をやり直して一致したときだけ実行しました。窓は自分で作ります。

承認を置くべき場所は、どちらの例のどの時点にあたるでしょうか。

「ここを過ぎたら戻せない」点がどこにあるかを両方で探してください。

この章のまとめ

  • MCP 仕様は人を挟むことを SHOULD とし、呼ぶ前にツールへの入力を見せるよう求める。承認画面の形は定めない。
  • 承認は要約ではなく 実引数と実際に起きること に対して行う。模型では要約だけの承認で4件が消えた。
  • 注釈は hint。destructiveHint の既定は true だが、信頼できないサーバの注釈で判断しない。
  • 承認は増やすほど読まれなくなる。取り消せなくなる直前にだけ置く。
  • DB は ROLLBACK を持つ。ツールには一般に無いので、dry-run で確定前の窓を自分で作る。

このコースの芯に戻ります。温度を0にしても同じ答えは約束されず、スキーマが縛るのは形までで、1回通っても率は分かりませんでした。埋め込みの「近い」は訓練が近いとしたもので、ツールを実行するかはコードが決めます。確率で答える部品の確かさは、部品の外側にしか書けません。 受け手の検査も、評価の手順も、実行を決めるコードも外側でした。最後の外側に置く人も、読まれる数と場所に絞らなければ働きません。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

信頼できるか分からないサーバが、削除ツールに destructiveHint: false と付けてきました。MCP 仕様に沿う扱いはどれですか。

用語集(3語)
human in the loop
処理の途中に人の判断を挟むこと。MCP 仕様はツール呼び出しを拒否できる人を挟むべき(SHOULD)とする。
ToolAnnotations
MCP でサーバがツールに付ける注釈。destructiveHint(既定 true)、idempotentHint(既定 false)など。すべて hint で、振る舞いの忠実な記述は保証されない。
dry-run
実行はせず、実行したら何が起きるかだけを計算する段。この回では承認の前に置き、消える一覧を見せるために使った。