WSL の Claude Code から Windows ネイティブ Chrome を DevTools MCP で操る
WSL 内の Chrome で痛かったところ
Claude Code に chrome-devtools-mcp を繋いで、ブラウザ自動化を任せているのですが、WSL2 側の Chrome を使う構成だと、細かい挙動で消耗するシーンがちょこちょこありました。
実際に引っかかっていたのはこのあたりです。
- ローディングアニメーションが壊れる。スピナーやシマーが途中で止まったり、一部のフレームだけ描画が崩れたり
- 想定外のフォントに置き換わる。サイトが指定している Web フォントまで到達せず、フォールバックで別書体になって、レビュー用スクショの雰囲気が変わる
- 起動ウィンドウが画面外に飛び出す。ウィンドウサイズが画面 DPI と噛み合わず、ドラッグで縮めようとしても反応しない
- マウスカーソルが消える。MCP とは直接関係ない操作中にも起きて、いちばん致命的
WSLg は頑張ってくれているのですが、GUI 描画を跨ぐ以上はどうしても揺れが残ります。Windows 側で普段使っている Chrome は、そもそも日常的に不具合なく動いているんですよね。同じマシンの隣の OS にある「ちゃんと動く Chrome」を借りに行けば済むはずです。
方針: Chrome だけ Windows ネイティブに寄せる
やりたい形はこれです。
[ WSL2 ] [ Windows ]
Claude Code
└─ wrapper.sh ── cmd.exe ──> chrome-devtools-mcp (npx)
└─ spawns ──> chrome.exe
chrome-devtools-mcp 本体は Windows 側で実行します。WSL の Claude Code からは cmd.exe 経由で呼び出して、標準入出力だけが WSL ⇔ Windows 間を流れる形です。Chrome はその MCP の子プロセスとして Windows ネイティブで起動するので、描画は普段遣いの Chrome と同じ挙動になります。
WSL 側に置くのは cd /mnt/c してから cmd.exe を exec するだけの 3 行シェルで、本体はこれだけ。
#!/bin/bash
cd /mnt/c || exit 1
exec cmd.exe /c 'C:\Users\<user>\bin\chrome-mcp.bat'
/mnt/c に cd してから起動しているのは、WSL ファイルシステムから cmd.exe を叩くと UNC パスはサポートされません の警告が stdout に出てしまい、MCP の JSON-RPC を汚染することがあるからです。
Claude Code を終了すれば MCP ごと Chrome も一緒に落ちるので、起動しっぱなしで裏に溜まる心配がないのも気に入っているポイントです。プロファイル (--userDataDir) を固定しておけば、ログインは次回セッションに持ち越されます。
手順: ファイル 2 本 + MCP 登録だけ
シェル 1 本とバッチ 1 本と、claude mcp add-json 1 回で終わりです。Node.js は Windows 側に入っている前提 (OpenJS.NodeJS から winget 等で入れておいてください)。
1. Windows 側: C:\Users\<user>\bin\chrome-mcp.bat
chrome-devtools-mcp を npx 起動するだけのバッチ。パスに空白が入る関係でシェルから直接クォートを通すと壊れやすいので、バッチに閉じ込めておくのがいちばん安定します。
@echo off
setlocal
set "CHROME_EXE=C:\Program Files\Google\Chrome\Application\chrome.exe"
set "PROFILE_DIR=%USERPROFILE%\.chrome-mcp-profile"
REM 前回の MCP が異常終了してオーファン化した Chrome / stale lockfile を掃除。
REM --userDataDir を見てフィルタしているので、普段遣いの Chrome には触らない。
REM 出力は stderr にまわして MCP の JSON-RPC (stdout) を汚染しない。
powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \"Name='chrome.exe'\" | Where-Object { $_.CommandLine -like '*.chrome-mcp-profile*' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }; Start-Sleep -Milliseconds 800; Remove-Item -LiteralPath '%PROFILE_DIR%\lockfile' -Force -ErrorAction SilentlyContinue" 1>&2
call npx -y chrome-devtools-mcp@latest ^
--executablePath="%CHROME_EXE%" ^
--userDataDir="%PROFILE_DIR%" ^
--chromeArg=--no-first-run ^
--chromeArg=--no-default-browser-check ^
--chromeArg=--disable-blink-features=AutomationControlled ^
--chromeArg=--disable-features=AutomationControlled,InterestCohort,PrivacySandboxSettings4 ^
--chromeArg=--disable-sync ^
--chromeArg=--disable-background-networking ^
--chromeArg=--disable-translate ^
--chromeArg=--password-store=basic
先頭の PowerShell ブロックが「MCP 起動時に毎回、自分の user-data-dir を掴んでいる Chrome を落として lockfile を消す」という掃除役です。MCP が正常終了していれば該当プロセスがなくて no-op、異常終了でゾンビが残っていたら自動で後始末されて次回起動が通ります。
--chromeArg の意図
- オートメーション扱いを避けるため:
--disable-blink-features=AutomationControlled/--disable-features=AutomationControlled,...。ログインを伴うサイトを MCP から触るときに、セッションが弾かれにくくなります - ノイズと横やりを減らすため:
--no-first-run/--no-default-browser-check(初回ダイアログを出さない) /--disable-sync(個人アカウントの Sync が MCP プロファイルに流れてこないように) /--disable-translate/--disable-background-networking/--password-store=basic
--userDataDir は 普段使いの Chrome プロファイルと別の場所にしておきます。MCP が触るプロファイルと日常で使うプロファイルが重なると、起動中にロック競合が起きたり、MCP のタブが作業ウィンドウに漏れてきたりします。
2. WSL 側: ~/.claude/chrome-mcp-wrapper.sh
方針セクションで紹介した 3 行シェルを ~/.claude/chrome-mcp-wrapper.sh として置いて、実行権限を付けます。
chmod +x ~/.claude/chrome-mcp-wrapper.sh
3. Claude Code の MCP 設定
既存の chrome-devtools があれば一度外してから、ラッパー経由で登録し直します。
claude mcp remove chrome-devtools -s user 2>/dev/null
claude mcp add-json chrome-devtools \
'{"type":"stdio","command":"/home/<user>/.claude/chrome-mcp-wrapper.sh","args":[]}' \
-s user
claude mcp get chrome-devtools で Status: ✓ Connected が返ってくれば疎通 OK。Claude Code を一度再起動すると、新しい設定で MCP が立ち上がります。
再起動後、ブラウザ系のツールを呼んでみて Chrome が Windows 側に新しいウィンドウとして立ち上がれば成功です。ログインが必要なサイトは初回だけそのウィンドウで手動ログインしておくと、次回以降はプロファイルからそのまま引き継がれます。
詰まりポイント
The browser is already running for ... が出るとき
バッチ先頭のクリーンアップで通常はここに来ないはずですが、それでも出るとしたら、--user-data-dir 違いのもう一つの MCP セッション (例: 別の Claude Code ウィンドウ) が動いている可能性があります。その場合は同時運用を一本に絞るか、二つ目は --chromeArg=--user-data-dir=別パス で分けるかで回避できます。
Chrome の初回起動プロンプト
Windows 側で初めて Chrome が MCP 経由で spawn されるとき、Defender / SmartScreen のタイミングでワンテンポ遅れることがあります。一度起動を通してしまえば次回以降は黙ります。
再現性の体感
移行してから、冒頭に挙げた 4 点のうち 3 点 (ローディング崩れ・フォント置き換わり・ウィンドウ画面外) はこちらからは見えなくなりました。マウスカーソル消失は WSLg とは別軸の Windows 側の不具合だと思うので、MCP 用の Chrome だけでも Windows ネイティブに動いてくれるのは、精神衛生の意味でもだいぶ違います。
まとめ
Chrome だけ Windows ネイティブに寄せ、MCP 本体も cmd.exe 経由で Windows 側に押し込めば、ネットワーク設定には触らずシェル 1 本 + バッチ 1 本で構成できます。WSL の Chrome のレンダリング揺れで消耗している方がいれば、試してみる価値があります。