webmcp-bridge-mcp
webmcp-bridge-mcp
Antigravity CLI(や他の MCP クライアント)から、Chrome 上で開いている WebMCP 対応ページの tool を呼び出せるようにする MCP サーバーです。
Antigravity CLI <--stdio(MCP)--> webmcp-bridge-mcp <--WebSocket--> webmcp-bridge-extension <--content/injected script--> Web Pageこのサーバー自身はページの DOM には一切触れません。あくまで Chrome Extension (webmcp-bridge-extension)との間の Bridge / Registry / Router です。 拡張機能を先にインストールしていなくても起動できますが、tool 呼び出しには拡張機能の接続が必要です。
インストール
bun install開発起動
bun run devMCP サーバーは stdio で待受けます(Antigravity CLI から起動されるのを想定)。
同時に WebSocket サーバーが
ws://127.0.0.1:58787に起動します(127.0.0.1のみ bind、外部からは接続不可)。 8787 ではなく 58787 なのは、wrangler dev(Cloudflare Workers)のデフォルトポートと衝突するため (Workers開発を並行して行っていると、拡張機能がこちらではなく wrangler 側に接続しにいこうとして ずっと繋がらなくなる不具合があった)。ログはすべて stderr に出力されます(stdout は MCP の JSON-RPC 通信専用のため)。
環境変数 WS_PORT で WebSocket のポートを変更できます。
WS_PORT=9000 bun run devビルド / 本番起動
bun run build
bun run startテスト
bun testtest/bridge.test.ts が実際の MCP サーバー(src/index.ts)を子プロセスとして起動し、実際の
@modelcontextprotocol/sdk の Client(= Antigravity CLI のような LLM エージェントの代わりに、
スクリプトで直接 tool を呼ぶ「モックされたエージェント」)を接続します。Chrome Extension の代わりに
FakeExtension クラスが同じ WebSocket プロトコルを喋って接続することで、ブラウザ無しに
接続状態・tabs/list の live 問い合わせ・discover_tools のキャッシュ / forceRefresh・
tool 呼び出しの並行実行・切断時のクリーンアップまで、ブリッジのロジック全体を検証します。
拡張機能自体(overlay の表示、DOM 操作を伴う tool 実行など)の検証は webmcp-bridge-extension 側の Playwright テストで行っています。
Antigravity CLI への登録例
npm に公開済みの場合、事前の bun install やパスの指定なしに bunx / npx だけで起動できます
(初回実行時に自動でダウンロード・キャッシュされます)。
{
"mcpServers": {
"webmcp-bridge": {
"command": "bunx",
"args": ["-y", "webmcp-bridge-mcp"]
}
}
}npx でも同様に動きます(bin エントリはプレーンな Node.js 上での動作を確認済みです)。
{
"mcpServers": {
"webmcp-bridge": {
"command": "npx",
"args": ["-y", "webmcp-bridge-mcp"]
}
}
}このリポジトリから直接ソースで動かしたい場合(開発中・未公開の場合)は、従来どおり cwd を指定してください。
{
"mcpServers": {
"webmcp-bridge": {
"command": "bun",
"args": ["run", "dev"],
"cwd": "/absolute/path/to/webmcp-bridge-mcp"
}
}
}npm 公開の準備について
package.json に bin エントリ(dist/index.js、#!/usr/bin/env node シェバン付き)を用意してあり、
bun run build は --target node でビルドするため node / bun どちらからも実行できます。
npm pack --dry-run で中身が dist/index.js と README.md だけになることを確認済みです。
実際に公開する場合は npm publish(または bun publish)を実行してください。
WebSocket URL
ws://127.0.0.1:58787(WS_PORT 環境変数で変更可)。Chrome Extension 側は起動時にこの URL へ接続しにいきます。
利用できる MCP tools
tool | 説明 |
| Extension の接続状態、既知タブ数、アクティブタブ ID を返す。WebSocket サーバー自体が bind に失敗している場合(他プロセスによるポート占有など)は |
| Extension が把握している WebMCP 対応タブ一覧を返す(Extension への live 問い合わせ) |
| 指定タブ(省略時はアクティブタブ)の WebMCP tool を検出する。デフォルトはキャッシュを返し、 |
| 指定タブの WebMCP tool を |
| [HIGH RISK] |
| [HIGH RISK] |
| Extension との疎通確認(レイテンシ計測)。ページ操作は行わない |
discoveryToken: タブ・オリジン・ナビゲーション世代・マニフェストへの束縛
タブスコープの寿命(タブを閉じたりページ遷移したりすれば tool は消える)はそれ自体はセキュリティ上 有用な性質ですが、ブリッジがそれを暗黙のうちに弱めてしまう経路があります。具体的には:
webmcp_discover_toolsを呼んだ後にタブが別のページへ遷移したSPA が同じ URL のまま DOM 上のフォームを差し替えた(
toolnameは同じだが中身が変わった)tool の定義(
inputSchemaなど)がwebmcp_discover_toolsの呼び出しとwebmcp_call_toolの呼び出しの間に変化した
こうした場合、"discover した時点の tool" と "実際に実行される tool" が食い違ったまま呼び出しが 通ってしまうと、ページ tool が実質的に「安定した MCP capability」であるかのように振る舞ってしまい、 タブスコープの寿命という前提が壊れます。
これを防ぐため、webmcp_discover_tools の結果には origin / navigationEpoch
(Extension がこのタブのナビゲーションごとにインクリメントするカウンタ)/ manifestHash
(そのタブの現在の tool 一覧から計算したハッシュ)の3つ組からなる discoveryToken が含まれます。
webmcp_call_tool / webmcp_request_submit_approval / webmcp_submit_tool はこの
discoveryToken を要求し、Extension 側 (background.ts) が実行の直前に タブの現在の
生の状態(origin・navigationEpoch・再スキャンした manifestHash)と突き合わせて一致することを
確認します。一致しなければ STALE_DISCOVERY エラーで実行を拒否するので、
webmcp_discover_tools をやり直してから再試行してください。
webmcp_submit_tool: 独立した high-risk capability としての承認フロー
webmcp_submit_tool は WebMCP 仕様が意図する安全機構(toolautosubmit の無い宣言型フォームは
既定で人間の確認を要求する)を迂回するものであり、webmcp_call_tool の単なる兄弟 tool として
扱うべきではありません。そのため2段階に分離しています。
webmcp_request_submit_approval([HIGH RISK])—tabId/toolId/args/discoveryTokenを渡すと、Extension がそのタブの現在の生の状態を確認した上で、フォームの 実際に画面上にある現在のフィールド値(reviewState)を読み取り、それを含めて{tabId, toolId, origin, navigationEpoch, manifestHash, args, reviewState}に束縛した 短命(既定20秒)・単回使用のapprovalTokenを発行します。reviewStateは承認前に 必ず確認してください。webmcp_submit_tool([HIGH RISK])—approvalTokenを渡して実際に送信します。 Extension は送信の直前に、トークンの有効期限・未使用であること・ タブの origin/navigationEpoch/manifestHash が承認時と変わっていないこと・ フォームの現在の実際のフィールド値が承認時のreviewStateと一致することを すべて再チェックします。いずれかが変化していれば拒否するので、webmcp_request_submit_approvalからやり直してください。
承認トークンは Extension の Service Worker のメモリ上にのみ保持され、どこにも永続化されません。 MV3 の Service Worker はアイドル状態でサスペンドされることがあり(README 下部の実機検証の節参照)、 再開後にこの状態が失われるのは意図的な挙動です。サスペンド前に発行された承認をサスペンド後の Service Worker が「復元」して、変わってしまったかもしれないページの状態にそのまま結び付けて しまうことがあってはならないため、再開後の Service Worker は単に「未知のトークン」として 拒否します。
入出力の例
webmcp_discover_tools
// input
{ "tabId": 123, "forceRefresh": true }
// output (content[0].text の JSON)
{
"tabId": 123,
"origin": "https://example.com",
"navigationEpoch": 0,
"manifestHash": "a1b2c3d4",
"tools": [ { "id": "reserve_hotel", "name": "reserve_hotel", "source": "imperative", ... } ],
"discoveryToken": { "origin": "https://example.com", "navigationEpoch": 0, "manifestHash": "a1b2c3d4" }
}webmcp_call_tool
// input
{
"toolId": "reserve_hotel",
"args": { "city": "Osaka" },
"discoveryToken": { "origin": "https://example.com", "navigationEpoch": 0, "manifestHash": "a1b2c3d4" }
}
// output
{ "ok": true, "result": { "ok": true, "city": "Osaka", "confirmationId": "RES-12345" } }
// or, if the tab navigated / the manifest mutated since discovery:
{ "ok": false, "error": "STALE_DISCOVERY: Tab navigated since discovery." }webmcp_request_submit_approval → webmcp_submit_tool
// input (webmcp_request_submit_approval)
{
"toolId": "search_hotels",
"args": { "city": "Osaka" },
"discoveryToken": { "origin": "https://example.com", "navigationEpoch": 0, "manifestHash": "a1b2c3d4" }
}
// output
{ "ok": true, "approvalToken": "1b3e...", "expiresAt": 1735900000000, "reviewState": { "city": "Osaka" } }
// input (webmcp_submit_tool)
{ "toolId": "search_hotels", "approvalToken": "1b3e..." }
// output
{ "ok": true, "result": { "ok": true, "city": "Osaka" } }source は WebMCP 仕様 準拠の検出元を表します
("imperative" = document.modelContext.registerTool()、"declarative" = annotated <form>)。
詳細は webmcp-bridge-extension の README を参照してください。
Extension が未接続の場合、すべての tool 呼び出しはエラー(ok: false またはエラーメッセージ)を
返すだけで、サーバーがクラッシュしたりハングしたりすることはありません。
注:
webmcp_discover_toolsの結果でrequiresUserGesture: trueになっている tool (toolautosubmitの無い宣言型フォーム)は、実機検証の結果、人間が実際に送信ボタンを押すまでwebmcp_call_toolが応答を返さないことがあります(ブラウザのネイティブ実装依存)。人が操作しない 自動実行の文脈でこの手の tool を呼ぶ場合は、timeoutMsを十分長く設定するか、そもそも呼び出さずに 人間へ操作を促す設計にしてください。
webmcp_request_submit_approval→webmcp_submit_toolはこの「人間のクリック待ち」を agent 側から 明示的に上書きするための、2段階に分かれた[HIGH RISK]tool です(詳細は上の節を参照)。 これは WebMCP 仕様が意図している安全機構(宣言型フォームは既定で人間の確認を要求する)を 迂回するものだと理解した上で使ってください。webmcp_call_toolで入力を埋めてpending: trueを 受け取った後、内容(reviewState)を確認して自動的に送信してよいと判断した場合にのみ呼び出す想定です。 なお、これが効くのはこの拡張機能自身の polyfill 実装(document.modelContextが未実装のブラウザ) に対してのみです。ネイティブ実装がある一部のブラウザはexecuteTool()自体を人間のクリックまで ブロックする(前述)ため、pending状態を経由せずそもそもwebmcp_call_toolから返ってこず、 この承認フローを使う隙がありません。
注:
resultは tool のexecute()が返した値をそのまま中継したものです。実機のdocument.modelContext.executeTool()で検証したところ、ページ側が{ ok: true, city }のような オブジェクトを返しても、ブラウザ側で JSON 文字列化されて渡ってくるケースがありました(WebMCP 仕様のexecuteは元々「エージェント向けの文字列サマリ」を返す想定のため)。このサーバーはresultを 一切加工せず素通しするので、MCP クライアント側で文字列か構造化データかを判定してください。
動作確認(拡張機能なしで疎通だけ見る場合)
MCP Inspector を使うと拡張機能なしでも サーバー自体の疎通を確認できます。
bunx @modelcontextprotocol/inspector bun run src/index.tswebmcp_get_status / webmcp_ping を呼び、extensionConnected: false /
{"ok":false,"error":"Extension not connected."} が返ってくれば正常です。