【注記】 本稿はWebMCP仕様のCommunity Groupドラフト(10 July 2026版)に基づく。APIも実装状況も速く動く領域で、命令的APIの入口、宣言的APIの詳細、ブラウザの対応は今後変わりうる。実装前に一次情報での再確認をおすすめする。
前回、問いに答える窓口をつくった。今回はその隣の段、「操作する」に上がる。
二段目までは、基本が読み取りだった。名乗る(見る)、応える(会話)―― どちらも、サイトは自分の持っているものを差し出すだけで、外の世界は変わらない。エージェントが取り違えても、返るのは間違った答えで、実害はそこで止まる。
三段目は違う。ここでエージェントは、サイトの機能を呼ぶ。予約を入れ、注文を出し、設定を変える。副作用を持つ操作だ。取り違えれば、間違った答えではなく、間違った結果が残る。渡す権限が、一段上がる。
この段を担うのがWebMCPだ。この連載の主役として置いた層で、応えるサイトを、動かせるサイトにする。
WebMCPとは何か ― サイトが「呼べる道具」を差し出す
WebMCPは、ページが自分の機能を「ツール」としてエージェントに差し出す仕組みだ。ツールとは、自然言語の説明とJSON Schemaの入力仕様を持つJavaScript関数のこと。エージェントは画面を撮って「このボタンはどれか」と当てるのではなく、「このページでできること、必要な引数、返る形」を関数の一覧として受け取り、直接呼ぶ。W3Cの仕様の言い方を借りれば、WebMCPを使うページは「ツールをサーバー側ではなくクライアント側スクリプトで実装したMCPサーバー」とみなせる。
前回のNLWebと並べると、担当の違いがはっきりする。NLWebはサイトの隣に立てるサーバー側の窓口で、問いに意味で答えた。WebMCPはブラウザのタブの中で動き、操作を実行する。NLWebのインスタンスがそのままMCPサーバーでもあったように、WebMCPのページもエージェントから見ればツールを持つ相手だが、動く場所が違う。WebMCPのツールはページのJavaScriptとして走るから、タブ(またはwebview)が開いていることが前提で、ヘッドレスでは動かない。サーバー側で完結するMCPやNLWebと、ここで分かれる。(WebMCPそのものは以前の連載で一通り扱った。今回は実装ガイドの一層として、他の層との接続の中に置き直している。全体像は完全スタックの記事にある。)
一つ、名前の誤解を先に解いておく。WebMCPという名だが、仕様はツールをエージェントに渡す通信フォーマットまでは規定しない。ブラウザはMCP形式で渡してもいいし、独自のfunction callingで渡してもいい。だから「WebMCP=ブラウザの中でMCPを喋る」は、半分だけ正しい。差し出す形(ツールという型)は共通だが、運ぶ管は実装に委ねられている。
現在地を、正確に置く。WebMCPは、GoogleとMicrosoftの技術者が、W3CのWeb Machine Learning Community Groupで進める提案だ。編集者はBrandon Walderman(Microsoft)、Khushal SagarとDominic Farolino(Google)。ここで大事なのは、これが W3C標準ではない こと ―― Community Groupのドラフトレポートであって、標準化トラックにも乗っていない。二次記事の多くが「W3C標準」と書くが、正確には草案だ。
動く場所も限られている。2026年5月のGoogle I/Oで、Chrome 149のオリジントライアルが公開された。Chrome 149から156までの期間、オリジントライアルのトークンを登録すれば、実ユーザーの本番トラフィックでWebMCPを試せる。手元での実験なら chrome://flags/#enable-webmcp-testing を有効にすればいい。ただし、呼ぶ側はまだ薄い。ツールの消費側としてGoogleが名指しするのはChromeのGeminiだけで、それもI/Oの発表では「近く対応」という予告だった。一般ユーザーのGeminiがツールを呼べる状態になったという一次発表は、本稿の時点で確認できない。安定版にも入っていない。
Edgeについては注意がいる。「Edge 147がネイティブ対応した」という記述を複数の二次記事で見かけるが、Microsoft自身のEdge 147のWeb Platformリリースノートに、WebMCPは載っていない(同時期の他のオンデバイスAI APIは載っている)。安定版でのネイティブ対応ではない、ということだ。ただしMicrosoftは仕様の共同編集者であり、Edge自身も独自のオリジントライアルを開いている。MozillaやAppleからの実装コミットは、今のところ確認できない。だから現況はこうだ ―― 仕様はCommunity Groupの草案、実装はChromeとEdgeのオリジントライアル(安定版対応はまだどのブラウザにもない)、消費するエージェントは予告されたChromeのGeminiのみ。
では「なぜ今か」。全ユーザーに配れるからではない。配れない。そうではなく、本番で試せる窓が開いたからだ。仕様は動いているし(ここ数週間でもAPIが変わった。後で触れる)、主要なブラウザベンダーが二社がかりで進めている。安定版とマルチブラウザを待ってから設計を考え始めるのでは、遅い。今は、試作して勘所を掴む時期だ。
設置 ― 何を、どこに置くか
WebMCPの設置は、「サイトのどの操作を、ツールとして公開するか」を決めることだ。入れ方は二つある。
宣言的APIは、既存のHTMLフォームに属性を足すだけの、いちばん軽い一歩だ。<form> に toolname(ツール名)と tooldescription(何をするか)を、各入力欄に toolparamdescription(その項目の説明)を付ける。ブラウザがフォームの構造からJSON Schemaを起こし、エージェントの呼べるツールに変える。検索・絞り込み・問い合わせ送信のような、フォームで完結する操作はこれで足りる。ただし宣言的APIは、仕様上はまだ策定中だ ―― ドラフトの当該節はTODOのままで、いまの属性名はexplainerとChromeの実装が先行しているもの。今後変わりうる前提で使う。
ここに一つ、安全の要がある。toolautosubmit という属性を付けると、エージェントは確認なしにフォームを送信できる。だから破壊的な操作 ―― 購入・削除・アカウント変更 ―― には付けない。ユーザーの確認が挟まること自体が、この層の安全機構だからだ。付けてよいのは、送っても取り返しがつく範囲に限る。
命令的APIは、フォームに収まらない動的な操作のためのものだ。JavaScriptでツールを登録する。登録は document.modelContext.registerTool(...) で行う。7月10日のCommunity Groupドラフトは、この入口を Document(document.modelContext)に置いている ―― 初期のChrome実装(149のオリジントライアル)は navigator.modelContext を使っていたが、この形はChrome 150で非推奨になった。少し前の記事や検証ツールには navigator 版が残っているので、読み替えに注意する。オリジントライアル中のChrome 149のような旧環境も拾いたければ、document.modelContext || navigator.modelContext とフォールバックさせる手がある。
登録したツールの撤収は、以前あった unregisterTool ではなく、AbortSignal で行う ―― 登録時に AbortController の signal を渡し、不要になったら abort() する。(unregisterTool はその後のドラフト改訂で外れ、撤収は AbortSignal に一本化された。少し前の記事はまだ古いAPIで書かれていることがある。)これは作法の問題でもある。ツールは、それが対応する画面が出ている間だけ登録し、画面を離れたら撤収する。見えない操作をエージェントに残しておくと、ユーザーには存在しないはずの操作が呼べてしまう ―― 静かな露出になる。
ツールの中身(契約)は、次の部品でできている。
name は識別子(1〜128字、英数字と _ - .)。description は自然言語の説明で、エージェントが「いつ・どう使うか」を判断する拠り所になる。inputSchema はJSON Schemaで入力の型を縛る。execute は実際に走る処理。annotations には二つの印がある ―― readOnlyHint(状態を変えない読み取り専用。エージェントが確認を省いてよい判断に使う)と untrustedContentHint(返り値に外部由来の信頼できない内容が混じる、という警告)。
説明文の扱いは、前々回のSchema.orgと接続する。以前は provideContext で別に渡していたページの文脈を、いまは description に畳み込む。そのうえで、型としての意味はSchema.org JSON-LDに寄せる ―― 「いま表示中の商品はSKU 1234」のような個別の文脈はdescriptionへ、「これはProductだ」という型の意味はJSON-LDへ、と役割を分ける。ただし説明は無限に書けるわけではない。実装のガイダンスは、ツール説明500字・パラメータ説明150字・名前30字・ツール出力1,500字といった目安を示している。超えれば切られる。長く盛るより、短く正確に。
なお、ツールが登録できる場所には条件がある。HTTPS(セキュアコンテキスト)で、オリジン分離された文書であること。登録の可否はPermissions Policyの tools 機能で制御され、既定は self(自分のオリジンのみ)。クロスオリジンのiframeにツールを登録させたければ、iframe側に allow="tools" を明示する。逆に言えば、埋め込んだ第三者ウィジェットが勝手にツールを生やすことは、既定では起きない。特定のオリジンにだけツールを見せたいときは、登録時の exposedTo で露出先を絞れる。
そして公開の線引き。WebMCPで開くのは「サイトのどの操作をエージェントに委ねるか」であって、全部ではない。管理者機能、機密を返すツール、不可逆な操作は、原則として公開しない。何を出さないかが、設置の半分を占める。ここは、このあとの観点の節で正面から扱う。
貼れる最小実例
二種類のツールを並べる。片方は状態を変えない読み取り、もう片方は副作用を持つ操作だ。差が、そのまま設計の差になる。
まず、読み取り専用のツール。readOnlyHint: true を付け、撤収用に AbortSignal を渡す。
// 読み取り専用のツール(状態を変えない)
const ac = new AbortController();
document.modelContext.registerTool({
name: "search_services",
description: "アンタイプの提供サービスをキーワードで検索し、該当するサービス名・概要・URLの一覧を返す。",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "検索キーワード(例:AIエージェント対応、Web制作)" }
},
required: ["query"]
},
annotations: { readOnlyHint: true },
execute: async ({ query }) => {
const results = await searchServices(query); // 既存の検索ロジックを再利用
return { content: [{ type: "text", text: JSON.stringify(results) }] };
}
}, { signal: ac.signal });
// 画面を離れるときに撤収
// ac.abort();
次に、副作用を持つツール。送信という結果が残るので、readOnlyHint は付けず、説明に「取り消せない」と明記して、確認を促す。
// 副作用を持つツール(送信という結果が残る)
document.modelContext.registerTool({
name: "submit_inquiry",
description: "問い合わせフォームを送信する。氏名・メール・本文を受け取り、送信結果を返す。送信は取り消せないため、実行前にユーザーの確認を挟む。",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "問い合わせ者の氏名" },
email: { type: "string", description: "返信先メールアドレス" },
message: { type: "string", description: "問い合わせ本文" }
},
required: ["name", "email", "message"]
},
// readOnlyHint は付けない ― これは状態を変える操作
execute: async (input) => {
const ok = await postInquiry(input);
return { content: [{ type: "text", text: ok ? "送信しました" : "送信に失敗しました" }] };
}
});
(searchServices / postInquiry や search_services が指す先は、この記事のための例示だ。自社に合わせて実値化するか、汎用化して読んでほしい。)
確認を挟む手立ては、宣言的APIなら「toolautosubmit を付けない」、命令的APIなら「readOnlyHint を付けず、説明に不可逆と明記する」。そのうえで、実行前の確認はブラウザとエージェント側の判断に委ねられる。確認ダイアログを明示的に要求するAPIも実装側にはあるが、その名前と形はここ数週間でも動いているので、実装するときは公開時点の最新に合わせてほしい。要点は、読み取りと操作を readOnlyHint で区別し、取り消せないものには確認を残す、という設計の側にある。
この層で確かめること(観点だけ)
前回と同じく、この層も二段で見る。何を入れるか(設置)は書いた。では、それが意図どおりに備わっているかを、どの観点で見るか。合否をどこで切るかという判定基準は、ここには書かない。観点までを示す。
この記事の芯を、先に図にする。
公開したツールの集合は、運営側が「エージェントに委ねてよい」と意図した操作の集合と、ぴったり重なっているのが理想だ。だが実際には、意図より広く露出しがちで、そのはみ出した差分が、そのまま脆弱性になる。以下の観点は、すべてこのズレを別の角度から見ている。
第一に、露出範囲の意図適合。公開したツール群は、運営側が意図した操作集合と一致しているか。管理者機能、内部API、debug用のツールが、紛れて露出していないか。人間向けのUIには出していない操作を、ツールとしてだけ開いてしまっていないか。意図より広い露出は、機能の追加ではなく、攻撃面の追加だ。
第二に、契約の忠実性。各ツールの description と inputSchema が、execute の実際の挙動と一致しているか。仕様自身が、危うい例を挙げている ―― finalizeCart(「カートを確定する」)という名と説明のツールが、実は購入を確定する。エージェントは説明を読んで「最終状態を表示するだけ」と解釈し、呼び、実際には決済が走る。ユーザーは買うつもりがなかった。「searchと名乗って書き込みもする」型の乖離は、悪意でも、雑な設計でも起きる。そしてこの乖離は、エラーを出さない。静かに、意図と違う結果を残す。
第三に、情報露出の意図適合。ツールが返す内容や、要求するパラメータに、意図しない個人情報・機密が載っていないか。仕様は、過剰にパラメータ化したツールの例を挙げている ―― 「ドレスを検索」するだけのツールが、サイズや予算に紛れて、年齢・妊娠の有無・所在地・肌の色・購入履歴まで引数に要求する。エージェントは「役に立とう」として、手元の個人化情報からそれらを埋めてしまう。サイトは、明示的な同意なしにプロファイルを組み立てられる。何を返すかだけでなく、何を訊くかも、露出の設計だ。
第四に、委任・確認境界。破壊的・不可逆・課金を伴う操作に、確認が挟まる設計になっているか。readOnlyHint が実態と一致しているか(読み取り専用と名乗るツールが、裏で状態を変えていないか)。宣言的フォームに、送ってはいけないものへ toolautosubmit が付いていないか。
第五に、プロンプトインジェクション境界。ツールの description や、ツールが返す内容を経由して、エージェントが意図しない操作へ誘導されないか。仕様はこの入口を三方向に整理している ―― 説明文に仕込む「ツール汚染」、返り値に仕込む「出力注入」、そしてツール自体が攻撃の的になる場合。返り値に外部由来の内容が混じるなら untrustedContentHint で印を付け、クライアント側の警戒を促せる。ここは入口だけ触れる。正面は、全層を貫く課題として後の回(横断のセキュリティ)で扱う。(サイトが操作の踏み台にされる筋は、以前も扱った。)
第六に、オリジン・権限境界。オリジン分離、Permissions Policyの tools(既定 self)、iframeの allow="tools"、exposedTo の露出先が、意図どおりか。埋め込んだ第三者が、勝手にツールを生やしたり、既存のツールを同名で上書きしたりできない設計になっているか。
この六つのうち、機械にすぐ任せられるものと、そうでないものがある。ツールが登録されているか、JSON Schemaが妥当か、HTTPSか、といった形式面は、自動で確かめられる ―― Chromeが配るModel Context Tool Inspectorでツールを一覧し手で叩けるし、Lighthouseのエージェント向け監査もこの種の形式チェックを担う。機械的な適合チェックは、すでに無料で手に入る。
だが「露出が意図と一致しているか」「契約が挙動に忠実か」「返す情報が意図の範囲か」は、ツールの中身と事業の実態を突き合わせなければ判断できない。ツールが登録されて動くことと、意図の範囲だけを正しく開けていることは、別のことだ。前者は機械に、後者は人に残る。前回と同じ線引きが、この層でも引かれる ―― ただし、副作用を持つぶん、後者の重みが増している。
よくある取り違え
取り違えは、だいたい四つの形で現れる。
一つ目は、操作を開くことを「ボタンを増やすこと」と混同する取り違えだ。WebMCPで公開するのは、UIの部品ではなく、意図そのものだ。ツールを一つ開くたびに、「この操作をエージェントに委ねてよい」と宣言している。開いた分だけ、渡している。だから設計の問いは「何を出せるか」ではなく「何を委ねてよいか」になる。
二つ目は、見えないツールを登録したままにする取り違えだ。画面を離れてもツールを撤収しないと、ユーザーの目の前には存在しない操作が、エージェントには呼べる状態で残る。登録は画面の表示に、撤収は画面の離脱に結びつける。AbortSignal はそのための道具だ。
三つ目は、説明文に文脈を盛りすぎる取り違えだ。provideContext が廃れて文脈を description に畳むようになったぶん、つい長く書きたくなる。だが説明には字数の目安があり、超えれば切られる。個別の文脈は短くdescriptionへ、型としての意味はSchema.org JSON-LDへ、と分けるほうが確実だ。前々回の投資が、ここでも効く。
四つ目は、現況を読み違える取り違えだ。「W3C標準だから安泰」「Edgeも対応済み」「置けば世界中のエージェントが呼びに来る」―― どれも正確ではない。実態は、Community Groupの草案、ChromeとEdgeのオリジントライアル(安定版対応はまだない)、消費側は予告段階のChromeのGeminiのみ。そして発見(どうやって「このサイトにツールがある」と気づかせるか)は、まだ固まっていない。今の実態は、エージェントがページを訪れて document.modelContext を覗いて初めて分かる、という素朴な方式だ。共通の目録やレジストリで自動発見させる動きは始まっているが ―― 前回の窓口の「見つけ方」と同じ宿題で ―― 連載の後半で改めて扱う。過大評価も過小評価もせず、「本番で試せる草案」として置くのが、いちばん実態に近い。
次回
自サイトの操作を、意図の範囲で開けた。次は、その操作を越えて、エージェント同士が「取引・決済」する層へ上がる。予約や注文を自分のサイトで受けるのと、エージェントが別のエージェントに支払いまで委ねるのとでは、渡す権限がもう一段変わる。委任状に、金額と条件を書き込む話になる。第5回は取引・決済の層 ―― A2A、AP2、そして名前の紛らわしい二つのプロトコルを解く。
参考情報
この記事をシェアする