Let Claude Code or Codex in WSL Use Windows Chrome
Bridging WSL AI Agents to Windows Chrome via Chrome DevTools MCP
Current Situation Analysis
Modern AI coding assistants like Claude Code and Codex increasingly rely on browser automation for frontend debugging, visual regression testing, and network inspection. When these agents run inside Windows Subsystem for Linux (WSL), a fundamental architectural mismatch emerges: the agent operates in a virtualized Linux environment, while the developer's primary debugging surface remains Windows Chrome.
This disconnect is frequently overlooked because developers assume cross-OS tooling will resolve itself through standard networking. In reality, WSL's NAT-like networking stack, Chrome's strict profile isolation, and the Model Context Protocol (MCP) routing layer introduce silent failures. The AI agent defaults to spawning a disposable Linux browser instance, which immediately severs session cookies, breaks authentication flows, and disconnects from Chrome DevTools. Network traces become fragmented, console logs are lost, and DOM inspection falls back to headless rendering that doesn't match production behavior.
The operational cost compounds quickly. Engineers spend 15β30 minutes per session manually reconstructing context, re-authenticating, or switching between terminal and GUI debugging tools. For teams running iterative frontend development or QA pipelines, this friction directly impacts velocity. The solution isn't to force Linux browsers into production parity, but to establish a controlled, low-latency bridge between the WSL agent and the Windows host's Chrome instance using the Chrome DevTools Protocol (CDP) and MCP routing.
WOW Moment: Key Findings
When properly bridged, the architectural shift transforms how AI agents interact with browser surfaces. The following comparison highlights the operational delta between default behavior and a CDP-routed Windows Chrome bridge:
| Approach | Session Persistence | DevTools Protocol Stability | Network/Console Visibility | Configuration Overhead |
|---|---|---|---|---|
| Default WSL Browser Instance | None (ephemeral) | Low (headless fallback) | Fragmented (no host DevTools) | Minimal |
| Windows Chrome via CDP Bridge | High (persistent profile) | High (direct WebSocket) | Complete (full DevTools sync) | Moderate |
This finding matters because it decouples agent execution from browser lifecycle management. By routing through Chrome DevTools MCP, the AI agent gains direct access to the host's rendering engine, network stack, and JavaScript execution context without leaving the WSL terminal. The bridge preserves authentication states, enables real-time DOM mutation tracking, and allows the agent to capture accurate performance metrics. More importantly, it eliminates context-switching overhead, allowing developers to keep debugging workflows entirely within their AI-assisted terminal environment.
Core Solution
The architecture follows a strict separation of concerns: the AI agent issues high-level browsing instructions, an MCP server translates those into CDP commands, and Windows Chrome executes them within an isolated profile. This design prevents session pollution while maintaining full debugging fidelity.
Step 1: Isolate the Chrome Profile
Chrome's default profile contains extensions, bookmarks, and active sessions that should never be touched by automated agents. Create a dedicated directory for the bridge:
# PowerShell (Windows Host)
$ProfilePath = "$env:LOCALAPPDATA\ChromeDevBridge"
New-Item -ItemType Directory -Force -Path $ProfilePath
This directory will store cookies, cache, and local storage exclusively for agent-driven sessions.
Step 2: Launch Chrome with Remote Debugging
Chrome must be started with the --remote-debugging-port flag and pointed to the isolated profile. Use a launcher script that checks for existing instances to prevent port conflicts:
# Launch-ChromeBridge.ps1
param(
[int]$Port = 9222,
[string]$ProfileDir = "$env:LOCALAPPDATA\ChromeDevBridge"
)
$ChromeExe = "${env:ProgramFiles}\Google\Chrome\Application\chrome.exe"
$Args = @(
"--remote-debugging-port=$Port",
"--user-data-dir=`"$ProfileDir`"",
"--no-first-run",
"--disable-background-timer-throttling"
)
if (Get-Process -Name "chrome" -ErrorAction SilentlyContinue) {
Write-Warning "Chrome is already running. Closing existing instances..."
Stop-Process -Name "chrome" -Force -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
}
Start-Process -FilePath $ChromeExe -ArgumentList $Args -WindowStyle Hidden
Write-Host "Chrome DevTools Bridge active on port $Port"
Step 3: Configure Chrome DevTools MCP Server
The MCP server acts as the translation layer between the AI agent's JSON-RPC calls and Chrome's CDP WebSocket endpoint. Install the official server and configure it to target the Windows host:
// mcp-chrome-bridge.json
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "@anthropic-ai/chrome-devtools-mcp"],
"env": {
"CHROME_DEBUG_URL": "ws://host.docker.internal:9222/devtools/page/1",
"MCP_TRANSPORT": "stdio"
}
}
}
}
Note the use of host.docker.internal. In WSL2, this DNS alias reliably resolves to the Windows host IP, bypassing the need to manually parse /etc/resolv.conf or track dynamic gateway addresses.
Step 4: Wire the MCP Server into Claude Code or Codex
Both tools support MCP configuration via project-level or user-level JSON files. Add the server definition to your agent's configuration:
// .claude/settings.json (or equivalent for Codex)
{
"mcp": {
"servers": {
"chrome-bridge": {
"command": "npx",
"args": ["-y", "@anthropic-ai/chrome-devtools-mcp"],
"env": {
"CHROME_DEBUG_URL": "ws://host.docker.internal:9222/devtools/page/1"
}
}
}
}
}
Step 5: Implement Lazy Launch Routing
Agents should not initialize the browser on startup. Instead, route browser commands conditionally. Add explicit instructions to your project's agent configuration file:
# .agents.md
## Browser Routing Rules
- Use the `chrome-bridge` MCP server for all navigation, DOM inspection, and network capture.
- Do not spawn headless Linux browsers or use Playwright/Puppeteer unless explicitly requested.
- Launch Chrome only when a browsing action is required. Close the debug session after task completion to free port 9222.
- If `host.docker.internal` fails, fallback to the WSL gateway IP extracted via `ip route | grep default | awk '{print $3}'`.
Architecture Rationale
- Isolated Profile: Prevents cookie/session leakage between development automation and personal browsing. Chrome's profile directory structure is strictly scoped, making cleanup trivial.
- CDP over WebSocket: Provides binary-efficient, bidirectional communication. Unlike HTTP-based automation, CDP supports real-time event streaming (console logs, network requests, DOM mutations).
- MCP Translation Layer: Standardizes tool invocation across Claude Code and Codex. The agent interacts with a consistent JSON-RPC interface, while the MCP server handles Chrome-specific protocol nuances.
- Lazy Initialization: Reduces resource consumption and minimizes attack surface. The browser only runs when the agent explicitly requests navigation or inspection.
Pitfall Guide
1. Profile Collision with Default Chrome
Explanation: Pointing the debug launcher to %LOCALAPPDATA%\Google\Chrome\User Data\Default corrupts active sessions, syncs automation cookies to your main profile, and triggers Chrome's "already running" lock.
Fix: Always use a custom --user-data-dir. Verify the path exists and is empty before launching.
2. WSL Network Resolution Failure
Explanation: localhost inside WSL refers to the virtual machine, not the Windows host. The MCP server will timeout trying to connect to 127.0.0.1:9222.
Fix: Use host.docker.internal or dynamically resolve the gateway IP. Add a health check in your launcher script that pings the port before starting the MCP server.
3. Eager Browser Initialization
Explanation: Starting Chrome during agent boot wastes memory and keeps port 9222 occupied, causing subsequent runs to fail with EADDRINUSE.
Fix: Implement conditional launch triggers. Use a wrapper script that checks for an active CDP endpoint before spawning Chrome.
4. MCP Server Port Conflicts
Explanation: Multiple tools (DevTools, Node inspector, other MCP servers) may bind to port 9222. Chrome will silently fail to start debugging or attach to the wrong process.
Fix: Use dynamic port allocation or enforce a dedicated port range (e.g., 9222β9230). Add port validation to your launcher: Test-NetConnection -ComputerName host.docker.internal -Port 9222.
5. Silent Tool Fallback
Explanation: Claude Code or Codex may ignore the MCP server and default to built-in browser tools or Puppeteer, breaking the bridge.
Fix: Explicit routing directives in .agents.md or CLAUDE.md. Override default tool preferences using the agent's configuration schema. Test with a verbose prompt that forces MCP invocation.
6. Windows Defender or Firewall Blocking
Explanation: Windows security software may block inbound connections to the CDP port, especially on private networks.
Fix: Create an explicit inbound rule for chrome.exe on the designated port. Alternatively, run the launcher with netsh advfirewall commands to whitelist the port programmatically.
7. Misaligned Expectations on Anti-Bot Systems
Explanation: The bridge provides debugging access, not automation bypass. CAPTCHA, 2FA, and rate-limiting mechanisms remain active. Fix: Document scope limitations in project READMEs. Use the bridge strictly for development, QA, and network inspection. Never rely on it for production scraping or authentication bypass.
Production Bundle
Action Checklist
- Create isolated Chrome profile directory on Windows host
- Configure Chrome launcher with
--remote-debugging-portand--user-data-dir - Verify WSL-to-Host networking via
host.docker.internalor gateway IP - Install and configure Chrome DevTools MCP server with correct WebSocket endpoint
- Add MCP server definition to Claude Code/Codex configuration
- Implement lazy-launch routing in
.agents.mdorCLAUDE.md - Test with a controlled navigation command and verify DevTools sync
- Document port allocation and cleanup procedures for team handoff
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|---|---|---|
| Local Frontend Development | Windows Chrome via CDP Bridge | Preserves auth states, enables real-time DOM/network inspection | Low (setup time only) |
| CI/CD Pipeline Testing | Headless Linux Browser (Playwright/Puppeteer) | Deterministic, no GUI dependencies, faster execution | Medium (requires test refactoring) |
| Multi-User Workstation | Dedicated Chrome Profile per User | Prevents session collision and port conflicts | Low (directory isolation) |
| Security-Strict Environment | Isolated VM with Linux Browser | Eliminates host exposure, complies with zero-trust policies | High (infrastructure overhead) |
Configuration Template
// mcp-bridge-config.json
{
"mcpServers": {
"chrome-devtools-bridge": {
"command": "npx",
"args": ["-y", "@anthropic-ai/chrome-devtools-mcp"],
"env": {
"CHROME_DEBUG_URL": "ws://host.docker.internal:9222/devtools/page/1",
"MCP_TRANSPORT": "stdio",
"MCP_LOG_LEVEL": "warn"
},
"timeout": 30000,
"retry": true
}
}
}
# Setup-ChromeBridge.ps1
param([int]$Port = 9222)
$ProfileDir = "$env:LOCALAPPDATA\ChromeDevBridge"
$ChromeExe = "${env:ProgramFiles}\Google\Chrome\Application\chrome.exe"
if (-not (Test-Path $ProfileDir)) {
New-Item -ItemType Directory -Force -Path $ProfileDir | Out-Null
}
$Proc = Get-Process -Name "chrome" -ErrorAction SilentlyContinue
if ($Proc) {
Write-Host "Terminating existing Chrome instances..."
$Proc | Stop-Process -Force
Start-Sleep -Seconds 2
}
Start-Process -FilePath $ChromeExe -ArgumentList @(
"--remote-debugging-port=$Port",
"--user-data-dir=`"$ProfileDir`"",
"--no-first-run",
"--disable-background-timer-throttling",
"--disable-extensions"
) -WindowStyle Hidden
Write-Host "Bridge ready. CDP endpoint: ws://host.docker.internal:$Port"
Quick Start Guide
- Create the isolated profile: Run
Setup-ChromeBridge.ps1on your Windows host to generate the dedicated directory and launch Chrome with debugging enabled. - Verify connectivity: From WSL, execute
curl -s http://host.docker.internal:9222/json/versionto confirm the CDP endpoint is reachable. - Wire the MCP server: Place
mcp-bridge-config.jsonin your project root and reference it in Claude Code or Codex settings. - Test routing: Issue a prompt like
Navigate to https://example.com and log the network requests.Confirm Windows Chrome opens and DevTools syncs. - Clean up: When finished, terminate the Chrome process or close the debug session to free port 9222 for subsequent runs.
Mid-Year Sale β Unlock Full Article
Base plan from just $4.99/mo or $49/yr
Sign in to read the full article and unlock all tutorials.
Sign In / Register β Start Free Trial7-day free trial Β· Cancel anytime Β· 30-day money-back
