矢印の向きが逆になる

このブログではChrome DevTools MCPagent-browserを取り上げてきました。どちらも「エージェントに実ブラウザを持たせる」話で、 構図は共通しています——エージェントが外側からページを覗き込み、DOMやアクセシビリティツリーを読んで、 人間のふりをしてクリックする。サイト側は何も知りません。何も協力しません。

WebMCP は、この矢印を逆向きにする提案です。 サイトが自分で document.modelContext.registerTool() を呼び、 「うちにはこういう操作があります」とツールを宣言する。 エージェントはDOMを解釈する代わりに、サイトが公開した契約を呼ぶ

一見すると「Web APIを用意するのと何が違うのか」と思うところです。実際その疑問は正しくて、 WebMCPの面白さは機能そのものより「どこで実行されるか」を選び直したことにあります。 本記事はその設計判断と、いま実際に使えるのかという現実の両方を見ていきます。

WebMCP とは何か

W3C の Web Machine Learning Community Group で策定が進む、ブラウザ標準の提案です。 ブラウザの DocumentmodelContext という属性を1つ足し、 そこにページ内のJavaScript関数を「ツール」として登録できるようにします。 名前・自然言語の説明・入力のJSON Schema・実行関数——構成要素はMCPのツール定義とほぼ同じです。

MCP-B が生まれる

Amazonのエンジニアだった Alex Nahas が、社内サービスをMCPサーバー化する際の認証の面倒さから「ブラウザの中でMCPを走らせればログイン状態をそのまま使える」と気づき、MCP TypeScript SDK をページ内で動かす Chrome 拡張を作る。

3つの提案が WebMCP として合流

Chrome 側の Script Tools、Edge 側の Web Model Context、そして MCP-B の発想が1つにまとまり、W3C Web Machine Learning Community Group の作業項目に。編集者は Microsoft と Google のエンジニア3名。

Chrome 146 で開発者トライアル

chrome://flags の「WebMCP for testing」を有効にすると navigator.modelContext が触れるようになる。

Chrome 149 でオリジントライアル

M149〜M156でフラグなしの公開トライアル。Chrome公式ドキュメントも 2026-05-18 に公開。出荷目標は Chrome 157。

ポイントは、これがプロトコルの移植ではないことです。 Explainer は MCP から着想を得つつも「Web プラットフォームにネイティブな、クライアント安全な解」を作ると明言していて、 バックエンド向けMCP仕様をそのまま持ち込むことは非目標に挙げられています。 ヘッドレス用途、人間を外した完全自律ワークフロー、既存のMCPサーバーの置き換えも、いずれも非目標です。

2つのAPI面 — 命令的と宣言的

命令的API: registerTool()

JavaScript で書く方です。すでにページ内にある関数を、そのまま包んで登録します。

await document.modelContext.registerTool({
  name: 'addTodo',
  description: 'Add a new item to the to-do list',
  inputSchema: {
    type: 'object',
    properties: {
      text: { type: 'string', description: 'The text content of the todo item' },
    },
    required: ['text'],
  },
  execute: async ({ text }) => {
    await addTodoItemToCollection(text);   // 既存のアプリコードをそのまま呼ぶ
    return 'Added to-do: ' + text;
  },
  annotations: {
    readOnlyHint: false,          // 状態を変えるツールである
    untrustedContentHint: true,   // 戻り値にユーザー生成コンテンツが混じりうる
  },
});

登録解除は AbortSignal です。SPAでルートごとにツールを出し入れするなら、 この形がそのままクリーンアップになります。

const controller = new AbortController();
await document.modelContext.registerTool(myTool, { signal: controller.signal });

// 画面を離れるとき
controller.abort();

仕様側の IDL は registerTool() のほかにgetTools()executeTool()、そして ツール構成が変わったことを知らせる toolchange イベントを持ちます。 つまりページ自身がツールの一覧を取得して呼ぶこともできる—— エージェント専用の口ではなく、ページ内のエージェントUIからも同じ経路を使える設計です。

宣言的API: フォームに属性を足すだけ

もう1つは、既存の <form> に属性を足すやり方です。 ブラウザがフォーム構造を読み取って、JSON Schema を自動生成します。

<form toolname="supportRequestTool"
      tooldescription="Submit a request for support."
      action="/submit">

  <label for="firstName">First Name</label>
  <input type="text" name="firstName" id="firstName">

  <select name="team" required
          toolparamdescription="Determines what team this request is routed to.">
    <option value="Customer happiness team">Return my purchase.</option>
  </select>

  <button type="submit">Submit</button>
</form>
属性付ける場所役割
toolnameformツール名。必須
tooldescriptionformツールの説明。必須
toolparamdescriptioninput / select個々の項目の説明。JSON Schema のプロパティ説明になる
toolautosubmitformエージェント呼び出し時に自動送信(=遷移)を許可する

通常のMCPサーバーと何が違うのか

ここが本題です。「サイトの機能をエージェントに公開したい」だけなら、 普通にMCPサーバーを立てればいい。実際それが今の主流です。 WebMCPが変えたのは実行場所で、それが認証の話に直結します。

graph LR
  subgraph SERVER["従来: MCP サーバー型"]
    S1["エージェント"] -->|"呼び出し"| S2["MCP サーバー<br/>別プロセス"]
    S2 -->|"認証を自前で"| S3["バックエンド"]
  end
  subgraph WEB["WebMCP 型"]
    W1["エージェント"] -->|"executeTool"| W2["ページ内 JS"]
    W2 -->|"Cookie が乗る"| W3["バックエンド"]
  end
ツールをページの中で走らせると、認証レイヤーがまるごと不要になる

MCP-B が生まれた経緯がこれをよく表しています。 Nahas が直面したのは、社内サービスを片っ端からMCPサーバー化するときの認可の面倒さでした。 誰がどのサービスにアクセスしていいのかを、MCPサーバー側で作り直さなければならない。 そこで彼は「ブラウザはその問題をすでに解いている」と気づきます。 ユーザーはもうログインしている。Cookie もセッションもある。ならばMCPをブラウザの中で走らせて、既存のフェデレーションに乗ればいい

観点MCP サーバーWebMCP
実行場所サーバー / ローカルプロセスページ内のJavaScript
認証OAuth・APIキーを別途設計ユーザーのログインセッションを継承
デプロイ独立したサーバーの運用が必要サイトのデプロイに同梱
扱える状態バックエンドの状態クライアント状態も含む(入力中のフォーム、開いているタブ)
ツールの寿命サーバーが生きている間ずっとそのページを開いている間だけ
向く用途自動化・バックグラウンド処理人が画面を見ている前での協働操作

最後の2行が効きます。WebMCPのツールはページを閉じれば消えるので、 「夜間バッチでエージェントに在庫を更新させる」といった用途には向きません。 そこは今まで通りMCPサーバーの仕事です。仕様が「MCPの置き換えではない」と繰り返すのはこの意味です。

なぜDOM操作ではなくツールなのか

もう一方の比較対象——agent-browser や Playwright MCP のような外側からの操作——との差は3点あります。

  1. 壊れない。DOM操作はクラス名やレイアウトが変われば壊れます。 ツールは名前とスキーマという契約なので、実装を差し替えても契約が同じなら壊れません。
  2. 軽い。「送信ボタンを探してクリック」を成立させるには、 エージェントはページの構造を読む必要があります。addTodo({ text }) を呼ぶだけなら、読む必要があるのは説明文とスキーマだけです。
  3. 意図が明示される。DOM操作は「クリックした結果何が起きるか」をエージェントが推測しますが、 ツールは readOnlyHint で副作用の有無を宣言できます。

ただし前提が1つあります。サイト側が実装していること。 これが後述する最大の問題です。

実装する前に知っておくこと

API名はすでに2回引っ越している

実務でいちばん刺さるのはここです。 この提案は window.agent として始まり、Chrome の初期ビルドではnavigator.modelContext として出荷され、 2026年半ばに仕様が document.modelContext へ移動しました。 「ツールは特定のページに属する」という意味づけを反映した変更です。 Chrome 150 で navigator.modelContext は非推奨になり、 初回アクセス時に一度だけ警告を出す後方互換エイリアスとして残っています。

// 移行期の安全な取り方
const modelContext = document.modelContext || navigator.modelContext;

if (!modelContext) {
  // 未対応ブラウザ。ツール登録をスキップする
  return;
}

await modelContext.registerTool(myTool);

オリジンとPermissions Policy

ツールはセキュアコンテキスト(HTTPS)でのみ登録でき、tools という Permissions Policy トークンで制御されます。 既定の許可リストは ['self']、つまり同一オリジンのみ。 クロスオリジンの iframe からツールを登録させたい場合は明示が必要です。

<!-- 決済ウィジェットなど、別オリジンの iframe にツール登録を許可する -->
<iframe src="https://checkout.example.com/widget" allow="tools"></iframe>

また、オリジン隔離が要求されるためOrigin-Agent-Cluster: ?0document.domain を併用しているページでは使えません。 レガシーなサブドメイン間連携が残っているサイトは、ここで引っかかる可能性があります。

セキュリティ — ツールを出すことはボタンを増やすことではない

仕様の Security and Privacy Considerations は、脅威を5つに分類しています。

脅威中身
プロンプトインジェクションツールの説明・引数の説明・戻り値に悪意ある指示を埋め込み、エージェントを乗っ取る
意図の詐称宣言したツールの挙動と、実際に起きることが一致しない
過剰パラメータ化本来不要な引数を要求し、エージェント経由でユーザーの機微情報を抜く
同一オリジンの侵害エージェントの活動を通じたクロスオリジンの状態漏洩
プライベートブラウジング通常モードとシークレットモードの情報境界が破られる

実装側として重いのは1つ目と3つ目です。 ツールはユーザーのログイン済みセッション上で走るので、プロンプトインジェクションを踏んだエージェントが正規のツールを正規の権限で呼ぶ—— これが攻撃の完成形になります。DOM操作より安全になるどころか、 「壊れにくく意図が明確な攻撃経路」を用意していることにもなります。

現実 — 「ユーザー以外すべてが揃った標準」

ここまで設計の筋の良さを見てきましたが、2026年8月時点の採用状況は率直に言って厳しいです。

状況
ChromeM149〜M156 でオリジントライアル中。出荷目標は Chrome 157
Edge同じエンジンだがフラグ止まり。公式リリースノートには未掲載
Firefox (Gecko)Intent to Experiment 時点で シグナルなし
Safari (WebKit)同じく シグナルなし
ツールを出すサイトデモを除けばほぼゼロ。パイロットの名前は挙がるが実登録は限定的
ツールを呼ぶエージェント主要なものは未対応。Google は「Gemini in Chrome が最初」と表明

これは典型的な両面市場の鶏と卵です。 サイトは、呼んでくれるエージェントがいないのにツールを書く動機がない。 エージェントは、ツールを出しているサイトがないのに対応する動機がない。 ブラウザだけが先に走っている状態で、決着はおそらくGemini in Chrome が実際にWebMCPツールを呼び始めるかどうかにかかっています。

仕様側にも空白が残っています。宣言的APIの詳細は仕様本文でまだ TODO のままで、 マルチモーダルな入出力、ナビゲーション後のツール応答の扱い、outputSchema、長時間タスクの進捗通知、 実行前のユーザー確認フロー——このあたりは Open Questions に並んでいます。

では今なにをするか

「本番に入れる」はまだ早い、というのが素直な結論です。 ただしやっておくと効く準備はあります。そしてそれは WebMCP 専用の作業ではありません。

  1. UIハンドラとドメイン関数を分けるregisterToolexecute に渡せるのは、クリックイベントから独立して呼べる関数です。 ボタンの onClick の中に処理が直書きされているなら、そこを剥がしておく。 これはテスタビリティの話とまったく同じで、WebMCPが来なくても無駄になりません。
  2. フォームに namelabel を正しく付ける。 宣言的APIはフォーム構造からスキーマを起こすので、 アクセシビリティのために正しくマークアップされたフォームがそのまま素材になります。 ここでも作業は二重になりません。
  3. 「エージェントに公開してよい操作」の線引きを先に決める。 技術的な実装は数行ですが、決済確定・退会・権限変更をツールにするかどうかは設計判断です。 出すなら readOnlyHint の付け方と確認フローを含めて決めておく。

まとめ

WebMCP は「サイトをエージェント対応にする機能」というより、Web の表面をもう1枚増やす提案です。 人間向けのUI、検索エンジン向けの構造化データ、その隣にエージェント向けのツール宣言を置く。

agent-browser の記事では「エージェントにツールを渡すコスト」を測り直しました。 WebMCP はその裏側からの答えです——コストを下げる最短経路は、そもそも読ませる量を減らすこと。 そしてページの構造を一番よく知っているのはサイト自身。 この論理は正しいと思います。

問題は論理ではなく順番です。標準は先に来て、使う側は後から来る。 だから今やる価値があるのは registerTool を書くことではなく、書こうと思ったときに包める形の関数を持っておくことのほうです。 UIから剥がされたドメイン関数は、WebMCPが普及してもしなくても資産として残ります。

参考リンク

理解度チェック

問題 0 / 50%
Q1

WebMCP でサイトがエージェント向けのツールを登録する際、現在の仕様で使うオブジェクトは「____.modelContext」です。空欄に入るものを答えてください。