
MCP chrome-devtools depuis WSL : piloter (et auto-lancer) une Chrome Windows
Le problème : le MCP chrome-devtools en WSL veut une Chrome Linux
Le MCP chrome-devtools permet à l'agent d'inspecter des pages tout seul — console, requêtes réseau, captures — sans lâcher le terminal. Mais son mode par défaut se connecte en --remote-debugging-pipe et lance sa propre Chrome dans l'environnement Linux. Sous WSLg, ça donne une fenêtre Chrome quasi morte : elle s'affiche, mais le clavier ne suit pas. Concrètement, impossible de taper un mot de passe sur un écran de login. Dès qu'une action humaine entre en jeu (login, captcha, 2FA), c'est mort.
L'objectif est l'inverse exact : piloter la vraie Chrome Windows, celle où on tape normalement, et laisser le MCP l'inspecter en parallèle.
La solution : viser la Chrome Windows
En networkingMode=mirrored, Windows et WSL partagent localhost. L'idée tient en deux points : lancer une Chrome côté Windows avec le port de debug ouvert, et dire au MCP de s'y brancher via --browserUrl http://127.0.0.1:9222.
La config MCP, dans ~/.claude.json :
"chrome-devtools": {
"type": "stdio",
"command": "npx",
"args": [
"chrome-devtools-mcp@latest",
"--browserUrl", "http://127.0.0.1:9222",
"--acceptInsecureCerts"
],
"env": {}
}
Le --acceptInsecureCerts n'est pas de la déco : il permet de charger des domaines locaux auto-signés (*.dev.fransys.io derrière Caddy) sans que Chrome refuse le certificat.
C'est mot pour mot la méthode WSL que le README officiel de chrome-devtools-mcp documente depuis son passage en 1.x (mai 2026) : mirrored networking, chrome.exe --remote-debugging-port=9222, --browser-url http://127.0.0.1:9222.
Le prérequis réseau que personne ne documente
Les tutos s'arrêtent tous à « active le mirrored networking ». Ce n'est pas toujours suffisant : 127.0.0.1:9222 depuis WSL ne tombe pas systématiquement sur la Chrome Windows. Une fois sur deux, connexion refusée sans raison apparente — de quoi soupçonner à tort le pare-feu Windows.
Le maillon manquant est planqué dans le bloc [experimental] du .wslconfig (côté Windows, C:\Users\<user>\.wslconfig) :
[wsl2]
networkingMode=mirrored
[experimental]
hostAddressLoopback=true # la clé : loopback bidirectionnel WSL <-> Windows
ignoredPorts=9000
hostAddressLoopback=true rend le loopback bidirectionnel entre l'hôte Windows et la VM WSL. Sans lui, mirrored couvre 90 % des cas mais laisse des trous — et le port de debug tombe pile dans un trou. Après modification du .wslconfig, un wsl --shutdown côté Windows pour appliquer.
Si vous ne retenez qu'une ligne de cet article, prenez celle-là.
Pourquoi --autoConnect ne marche pas en cross-OS
--autoConnect (Chrome 144+) promet de se brancher sur la vraie Chrome loggée, sans profil jetable, via chrome://inspect/#remote-debugging. Sauf que :
--autoConnectcherche unuser-data-dirlocal à la machine du serveur MCP.
En WSL→Windows, le serveur MCP est côté Linux, Chrome côté Windows. Le flag ne trouvera jamais le profil Windows depuis Linux. Mort-né en cross-OS : --browserUrl reste la bonne réponse.
Autre détail non négociable : le profil dédié n'est pas optionnel.
--user-data-dir="C:\Temp\chrome-mcp"
Si la Chrome perso est déjà ouverte, relancer chrome.exe --remote-debugging-port=9222 sans user-data-dir distinct ouvre juste un onglet dans l'instance existante, sans ouvrir le port de debug. On croit que c'est lancé, et non. Le profil séparé garantit qu'une nouvelle instance « debuggable » démarre, sans toucher aux onglets, cookies et extensions perso.
Le déclic : la connexion MCP est lazy
L'objectif suivant : que Chrome démarre toute seule au bon moment, sans lancement manuel à chaque session. Reste à savoir quand déclencher. Et une information change tout :
Le serveur
chrome-devtools-mcpne se connecte pas au navigateur au démarrage. La connexion est lazy : elle se fait au premier appel d'outil qui a besoin du navigateur.
Conséquence directe : un hook PreToolUse qui matche mcp__chrome-devtools__.* se déclenche juste avant ce premier appel. Il lance Chrome, attend qu'elle réponde, et la connexion lazy du MCP enchaîne.
L'approche communautaire habituelle met plutôt un wrapper à la place de la command du MCP. Ça marche, mais ce wrapper s'exécute au démarrage du serveur MCP, donc à chaque lancement de Claude Code : une fenêtre Chrome qui pop à chaque session, même sans toucher au navigateur. Le hook PreToolUse est lazy comme la connexion : Chrome ne démarre que quand elle sert.
Le hook PreToolUse
Le hook va dans ~/.claude/settings.json. Point important : c'est bien un hook qu'il faut, pas une consigne dans CLAUDE.md ou en mémoire. Ces deux-là sont du contexte (l'agent « essaie » de suivre), pas de l'exécution garantie. Un comportement automatique déclenché par un évènement, c'est le boulot d'un hook.
{
"hooks": {
"PreToolUse": [
{
"matcher": "mcp__chrome-devtools__.*",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/ensure-chrome-windows.sh",
"timeout": 30,
"statusMessage": "Lancement de Chrome (Windows) pour MCP…"
}
]
}
]
}
}
Le matcher mcp__chrome-devtools__.* colle au nommage des outils MCP (mcp__<serveur>__<outil>), donc il attrape tous les outils du serveur.
Le script doit être idempotent : ne rien faire si Chrome répond déjà, ne la lancer que si absente, et bloquer l'appel (sortie 2) avec un message clair plutôt que de laisser le MCP planter façon énigme. Plus une ligne de log par exécution, dont l'intérêt apparaît juste après.
#!/usr/bin/env bash
# Garantit qu'une Chrome côté Windows expose le port de debug 9222
# AVANT tout appel d'outil chrome-devtools MCP (pont WSL -> Windows).
# Idempotent. Trace dans ~/.claude/chrome-hook.log (already-up | launched | FAIL).
set -u
PORT=9222
URL="http://127.0.0.1:${PORT}/json/version"
CHROME="/mnt/c/Program Files/Google/Chrome/Application/chrome.exe"
LOG="$HOME/.claude/chrome-hook.log"
ts() { date '+%Y-%m-%d %H:%M:%S'; }
# Déjà up ? rien à faire (cas le plus fréquent).
if curl -fsS "$URL" >/dev/null 2>&1; then
echo "$(ts) already-up" >> "$LOG"
exit 0
fi
if [ ! -f "$CHROME" ]; then
echo "$(ts) FAIL chrome.exe introuvable ($CHROME)" >> "$LOG"
echo "ensure-chrome-windows: chrome.exe introuvable ($CHROME)" >&2
exit 2
fi
# Profil dédié OBLIGATOIRE : le port de debug ne s'ouvre que dans une
# instance avec --user-data-dir distinct.
mkdir -p /mnt/c/Temp/chrome-mcp 2>/dev/null
"$CHROME" \
--remote-debugging-port="$PORT" \
--remote-allow-origins='*' \
--user-data-dir='C:\Temp\chrome-mcp' \
--no-first-run --no-default-browser-check \
>/dev/null 2>&1 &
disown
# Attendre que l'endpoint réponde (jusqu'à ~10s) pour que la connexion
# lazy du serveur MCP réussisse au moment de l'appel d'outil.
for i in $(seq 1 20); do
if curl -fsS "$URL" >/dev/null 2>&1; then
echo "$(ts) launched (~$((i*500))ms)" >> "$LOG"
exit 0
fi
sleep 0.5
done
echo "$(ts) FAIL pas de réponse sur :${PORT} après 10s" >> "$LOG"
echo "ensure-chrome-windows: Chrome n'a pas exposé :${PORT} après 10s" >&2
exit 2
Ne pas oublier chmod +x ~/.claude/hooks/ensure-chrome-windows.sh. Et comme les hooks se chargent au démarrage de Claude Code, après avoir édité settings.json il faut ouvrir une fois /hooks (ça recharge la config) ou redémarrer.
La preuve par le log
Sans log, impossible de distinguer « le hook a fait son no-op » de « le hook ne s'est pas déclenché, mais Chrome était là par hasard ». Même résultat à l'écran.
Avec Chrome déjà ouverte, le hook tire et court-circuite :
13:07:42 already-up
13:07:45 already-up
13:07:59 already-up
13:08:13 already-up
Un déclenchement par appel d'outil : preuve que le hook est chargé et qu'il matche. Puis le test décisif, Chrome fermée avant la session :
13:24:22 launched (~1000ms)
13:24:26 already-up
13:24:26 already-up
Chrome était absente, le hook l'a démarrée en une seconde, les appels suivants l'ont trouvée up. Sans cette branche de log, on jurerait que « ça marche » sur la foi d'un faux positif.
Diagnostic des problèmes courants
curl http://127.0.0.1:9222/json/versionne répond pas → le hook aurait dû relancer Chrome ; vérifier qu'il est actif (/hooks) et tester le script à la main.list_pagesrenvoie une vieille Chrome → si le MCP a démarré avec l'ancienne config (pipe), redémarrer Claude Code pour qu'il recharge ses args.- Erreur de validation du header
Host(connexion VM→hôte rejetée par Chrome) → c'est exactement à ça que sert--remote-allow-origins='*'. En dernier recours, le troubleshooting officiel propose un tunnel SSH depuis WSL :
ssh -N -L 127.0.0.1:9222:127.0.0.1:9222 <user>@<host-ip>
Tant que le mirroring fonctionne, ce tunnel est inutile — mais bon à connaître.
Stabiliser dans la durée : le piège OOM
Après quelques jours d'usage intensif, WSL peut se mettre à planter sans raison apparente. Diagnostic : un bug de fuite mémoire confirmé dans chrome-devtools-mcp (issues #1192, #1214), aggravé par le fait que chaque session Claude Code lance sa propre instance MCP. Au bout de 4-5 sessions cumulées, la VM sature, l'OOM killer dégaine et flingue systemd/dbus. Reboot obligatoire.
Deux garde-fous suffisent, ensemble.
Donner du mou à WSL. Dans .wslconfig, monter le swap (8 GB par défaut, c'est trop juste) et ne pas activer autoMemoryReclaim=dropCache, trop agressif :
[wsl2]
swap=24GB
# [experimental]
# autoMemoryReclaim=gradual # la version douce — éviter dropCache
Plafonner chaque instance MCP via cgroup. Dans ~/.claude.json, enrober la commande dans systemd-run --user --scope :
"chrome-devtools": {
"type": "stdio",
"command": "bash",
"args": [
"-lc",
"exec systemd-run --user --scope -p MemoryMax=6G -p MemorySwapMax=4G npx chrome-devtools-mcp@latest --browserUrl http://127.0.0.1:9222 --acceptInsecureCerts"
]
}
Si une instance fuit, le cgroup la tue elle seule au lieu de déclencher l'OOM global qui ferait tomber systemd. C'est la recommandation explicite du ticket upstream.
Détail piégeux : appliquer un nouveau .wslconfig demande un wsl --shutdown. Si vous tombez ensuite sur 0x8007054f (CreateInstance/CreateVm/ConfigureNetworking), c'est connu et transitoire — un reboot Windows suffit (HNS/WinNat se réinitialisent).
Ce qui marche, ce qui ne marche pas
Ça marche :
- Inspection complète d'une page (console, réseau, captures) sur une vraie Chrome Windows, interactive
- Lancement automatique et paresseux de Chrome au premier appel d'outil, zéro geste manuel
- Idempotence : pas de fenêtre Chrome en double, pas d'ouverture inutile sur les sessions sans navigateur
- Actions humaines (OAuth, captcha, 2FA) : on tape direct dans la fenêtre Windows pendant que le MCP inspecte en parallèle
Ça ne marche pas (ou pas comme ça) :
--autoConnecten cross-OS WSL→Windows (cherche un profil local qui n'est pas là)- Lancer Chrome sans
--user-data-dirdédié quand la Chrome perso tourne déjà (le port de debug reste fermé) - Compter sur
CLAUDE.mdou la mémoire pour un comportement « à chaque fois » : il faut un hook
Les deux leçons à retenir : hostAddressLoopback=true, le prérequis réseau qu'on oublie systématiquement, et la connexion MCP lazy, qui rend le hook PreToolUse non seulement viable mais meilleur que le wrapper. Plus une habitude générale : quand on veut être sûr qu'une automatisation tire, on lui colle une ligne de log. Une seconde à écrire, et plus jamais le doute du faux positif.
Articles similaires
Claude Code comme back-office : connecter Drive, Gmail et Trello pour piloter sa boîte
claude-code · ia · mcp
Claude Code Remote Control : reprendre ses sessions WSL depuis le téléphone
claude-code · ia · productivite
Créer une skill Claude Code pour vérifier les affirmations scientifiques
claude-code · ia · mcp