課題
サードパーティのWebアプリを改造するChrome拡張の開発は厄介だ。DOM構造は非公開、クラス名はminifyされてデプロイごとに変わる、公式APIもない。従来の開発ループはこうなる:
- DevToolsでDOMを手動で調べる
- セレクタとContent Scriptを書く
- 拡張をリロード
- 動作確認
- 繰り返し
このサイクルは遅い。AIコーディングエージェントにブラウザの実際の状態を見せて、自分の変更を自分で検証させたい — コードを盲目的に生成するのではなく。
そこで辿り着いたのが、WXT(拡張フレームワーク)+ Chrome DevTools MCP(AIにブラウザアクセスを与える)+ Cursor(MCP対応IDE)という構成。
技術スタック
| ツール | 役割 |
|---|---|
| WXT | Chrome拡張フレームワーク(TypeScript、ホットリロード、Manifest V3) |
| Chrome DevTools MCP | Chrome DevTools ProtocolをAIエージェントに公開するMCPサーバー |
| Cursor | MCPネイティブ対応のAI搭載IDE |
Step 1: WXT で CDPポートを固定する
WXTはChrome拡張開発をファイルベースルーティング、ホットリロード、TypeScriptサポートで包んだフレームワーク。ポイントは、WXTのrunner設定でChromium起動引数をカスタマイズできること — --remote-debugging-port を含めて。
// wxt.config.ts
import { defineConfig } from 'wxt';
export default defineConfig({
manifest: {
name: 'My Extension',
version: '1.0',
permissions: ['storage'],
},
extensionApi: 'chrome',
runner: {
chromiumArgs: [
'--remote-debugging-port=9222',
`--user-data-dir=${process.cwd()}/.chrome-debug-profile`,
'--exclude-switches=enable-automation',
],
startUrls: ['https://example.com'],
},
});
3つの要点:
-
--remote-debugging-port=9222— Chrome DevTools Protocolを固定ポートで公開。MCPサーバーがここに接続する。 -
--user-data-dir— 普段のChromeとは別の専用プロファイルディレクトリ。ログイン状態がdev再起動しても保持される。必ず.gitignoreに追加すること — Cookie やセッショントークンが含まれるため、リポジトリにpushすると認証情報が漏洩する。 -
--exclude-switches=enable-automation— これがないと一部のサイトが「自動操作ブラウザ」を検知してログインをブロックする。
wxt(= npm run dev)を実行すると、WXTがこれらの引数でChromeを起動し、拡張をロードし、ファイル変更を監視する — すべて1コマンド。
Step 2: Chrome DevTools MCP の設定
MCP(Model Context Protocol)はAIエージェントが外部ツールを呼び出すためのプロトコル。Chrome DevTools MCPはChrome DevTools Protocolをラップしたサーバーで、AIエージェントにページ遷移、JavaScript実行、スクリーンショット取得、DOM検査の能力を与える。
設定は .cursor/mcp.json に置く:
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": [
"-y",
"chrome-devtools-mcp@latest",
"--browserUrl=http://127.0.0.1:9222"
]
}
}
}
これだけ。Cursor起動時にMCPサーバーが立ち上がり、ポート9222のChromeに接続する。AIエージェントがブラウザを操作できるようになる。
Step 3: 開発用スクリプト(任意だが推奨)
2つのメインワークフローをラップするシェルスクリプトがあると便利:
./dev.sh dev # WXT dev server + Chrome(ホットリロード + MCP)
./dev.sh start # ビルド済み拡張をロード(ホットリロードなし、MCPのみ)
./dev.sh stop # デバッグ用Chrome停止
./dev.sh status # CDP接続確認
dev がメインモードで、WXTがすべて管理しホットリロードが効く。start はプロダクションビルドをMCPで検証する用途。ポート競合チェック、PID管理、curl http://localhost:9222/json/version での接続確認などのエッジケースもスクリプトで対応する。
実際のワークフロー
1. 環境を起動
npm run dev # or ./dev.sh dev
Chromeが拡張をロードした状態で自動起動。WXTがファイル変更を監視。MCPサーバーがポート9222に接続。CursorのAIエージェントがブラウザを認識する。
2. AIが現在の状態を検査
AIエージェントがMCP経由でブラウザ上のJavaScriptを実行し、DOMの状態を把握する:
// AIエージェントがMCP経由で実行
evaluate_script({
function: `() => ({
injectedStyle: !!document.getElementById('my-extension-styles'),
buttonCount: document.querySelectorAll('.my-custom-button').length,
panelVisible: !!document.getElementById('my-panel'),
})`
})
エージェントは開発者がコンテキストスイッチしなくても自分の変更を検証できる。コードを書く → WXTがホットリロード → エージェントがDOMの更新を確認、という流れ。
3. AIがブラウザを通じて変更を検証
通常のAIアシスト開発との決定的な違いはここ。従来:
「パネルを追加しました。リフレッシュして動作確認してください。」
この構成だと:
「パネルを追加しました。検証します… [MCP経由でスクリプト実行] …
#my-panel要素が存在、子要素5個、位置は正常。レンダリングOKです。」
テキストベースのDOM検証はスクリーンショットより高速・低コスト・高精度:
// Good: 構造化された検証
evaluate_script(() => {
const buttons = document.querySelectorAll('.my-button');
return {
count: buttons.length,
firstButton: buttons[0]?.outerHTML.substring(0, 200),
};
});
// スクリーンショットはビジュアルレイアウトの確認が必要な時だけ
take_screenshot()
Tips & Gotchas
Content Script の Isolated World
Chrome拡張のContent Scriptは Isolated World で動作する。Content Scriptで window に設定した変数は、MCP経由の evaluate_script からは見えない。MCPは page context で実行されるため。
回避策: グローバル変数ではなく、DOMの副作用で検証する。
// NG: window変数はIsolated Worldにいる
evaluate_script(() => window.myExtensionState) // → undefined
// OK: 拡張が加えたDOM変更をチェック
evaluate_script(() => ({
styleInjected: !!document.getElementById('my-extension-styles'),
panelExists: !!document.getElementById('my-panel'),
}))
サードパーティUI向けセレクタ戦略
自分が管理しないサイトの拡張を作ると、セレクタが頻繁に壊れる。フォールバックチェーンが有効:
const el =
document.querySelector('[aria-label*="Submit"]') ||
document.querySelector('[data-test-id="submit"]') ||
document.querySelector('.submit-btn');
優先順位:
-
ARIA属性(
aria-label,role)— 更新に対して最も安定 -
セマンティック属性(
data-test-id)— 中程度の安定性 - クラス名 — 最終手段、常にフォールバックとして用意
DOM構造をAIが読める形式で出力するショートカット(Ctrl+Shift+D 等)を仕込んでおくと便利。セレクタが壊れたらショートカットを押してCursorに貼り付け、エージェントがフォールバックセレクタを更新する。
非同期DOM待機
SPAの要素は非同期に出現する。脆い setTimeout チェーンの代わりに、上限付きポーリングを使う:
let retries = 0;
const interval = setInterval(() => {
const el = document.querySelector(selector);
if (el || retries++ > 10) {
clearInterval(interval);
if (el) callback(el);
}
}, 500);
要素が出現しなかった場合はサイレントに失敗する。コンソールを汚さない。
Googleログインのハマりポイント
--remote-debugging-port 付きでChromeを起動すると、Googleが「安全でないブラウザ」と検知してログインをブロックすることがある。--exclude-switches=enable-automation で大体解決するが、それでもダメな場合:
- 専用プロファイルでChromeを起動(WXTなし)
- 手動でGoogleにログイン
- Chrome終了
-
npm run devを実行 — WXTが同じプロファイルを使うのでセッションが有効
--user-data-dir で指定した専用ディレクトリがログイン状態を保持する。
--user-data-dir とセキュリティ
専用プロファイルを使う理由は2つ:
- 普段のChromeと分離: 開発用Chromeが普段のブックマーク・拡張・セッションに干渉しない。逆に普段のChromeに入っている認証情報が開発環境に漏れない。
- 最小限の認証情報: 開発に必要なサイト(対象のWebアプリ)だけにログインすればよい。個人のGmail等に余計なログインをしない。
注意点:
-
.gitignoreに必ず追加する。プロファイルディレクトリにはCookie、セッショントークン、LocalStorageなどが含まれる。
.chrome-debug-profile/
-
CDPポート(9222)はローカルからアクセス可能。
--remote-debugging-portはデフォルトで127.0.0.1にバインドされるが、開発マシン上の他プロセスからはブラウザの全タブにアクセスできる状態になる。開発中のみ起動し、使い終わったら停止する。 - 共有マシンでは使わない。CDPが開いている間、同じマシン上の誰でもブラウザセッションを操作できる。
セットアップ手順
1. WXTプロジェクトを作成
npm create wxt@latest my-extension
cd my-extension
2. wxt.config.ts にCDPポートを追加
runner: {
chromiumArgs: [
'--remote-debugging-port=9222',
`--user-data-dir=${process.cwd()}/.chrome-debug-profile`,
'--exclude-switches=enable-automation',
],
},
3. .cursor/mcp.json を作成
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest", "--browserUrl=http://127.0.0.1:9222"]
}
}
}
4. 実行
npm run dev
CDPが有効なChromeが起動し、CursorのエージェントがMCP経由で接続。AIエージェントがブラウザを見れるようになる。
この構成の意義
従来のChrome拡張開発ループは 書く → リロード → 手動確認 → 繰り返し。WXT + Chrome DevTools MCP では 書く → 自動リロード → AIが検証 → 反復 になり、最初と最後のステップもAIが担える。
- デバッグ: 「console.log読んでブレークポイント張って手動再現」→「AIがライブブラウザでスクリプト実行して状況を報告」
- セレクタ保守: 「DevTools開いて要素を調べてセレクタをコピペ」→「AIがDOMを読んでフォールバックセレクタを更新」
- 機能開発: 「コード書いて手動テスト」→「AIがコード書いてDOM状態を確認して問題を修正 — 全部ワンターン」
拡張への理解が不要になるわけではない。だがフィードバックループは劇的に短くなる。特にサードパーティDOM操作の泥臭い部分で効果が大きい。