@dbalabka/chrome-wsl 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -15
- package/chrome-wsl +142 -7
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,22 +1,29 @@
|
|
|
1
1
|
# Chrome Start From WSL Helper for MCP
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
A lightweight shell script that **automates Chrome DevTools MCP setup** from **WSL** by launching **Windows Chrome** with remote debugging enabled and transparently proxying traffic from WSL using `socat`.
|
|
4
|
+
|
|
5
|
+
No Linux Chrome, no gWSL, no manual Windows networking setup.
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
* 🤖 Automatically bridges **WSL → Windows Chrome DevTools (9222)** using `socat`
|
|
10
|
+
* ✅ Validates required **Windows portproxy and firewall rules**
|
|
11
|
+
* 🚀 Launches **existing Windows Chrome** with remote debugging and a temporary profile
|
|
12
|
+
* 🧹 Cleans up easily with a stop command and leaves no permanent system changes
|
|
13
|
+
|
|
14
|
+
## Key Features
|
|
15
|
+
|
|
16
|
+
* **Fully automated**: one command to get Chrome DevTools MCP working from WSL
|
|
17
|
+
* **Lightweight**: simple, readable shell script
|
|
18
|
+
* **Uses your existing Windows Chrome** — no Linux Chrome or gWSL required
|
|
19
|
+
* **Clean & reversible**: minimal system changes, easy to uninstall
|
|
14
20
|
|
|
15
21
|
## Prerequisites
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
|
|
23
|
+
* Windows Chrome already installed
|
|
24
|
+
* WSL environment
|
|
25
|
+
* The script will install one small dependency (`socat`) automatically for port proxying
|
|
26
|
+
|
|
20
27
|
|
|
21
28
|
## Usage
|
|
22
29
|
|
|
@@ -26,6 +33,10 @@ npx @dbalabka/chrome-wsl
|
|
|
26
33
|
```
|
|
27
34
|
- If portproxy/firewall entries are missing, run the printed admin PowerShell commands on Windows, then rerun the script.
|
|
28
35
|
- The script logs `socat` output to `/tmp/socat-9222.log`.
|
|
36
|
+
- To run Chrome in headless mode (no visible window, requires Chrome 112+):
|
|
37
|
+
```sh
|
|
38
|
+
npx @dbalabka/chrome-wsl --headless
|
|
39
|
+
```
|
|
29
40
|
- To stop the `socat` forwarder:
|
|
30
41
|
```sh
|
|
31
42
|
npx @dbalabka/chrome-wsl --stop
|
|
@@ -36,6 +47,8 @@ npx @dbalabka/chrome-wsl
|
|
|
36
47
|
```
|
|
37
48
|
- Runs directly via npm without cloning; default entrypoint is `chrome-wsl` (matching the package name).
|
|
38
49
|
|
|
50
|
+
> ℹ️️ Note: Must be run from WSL. Docker is supported only for proxying (no Chrome launch).
|
|
51
|
+
|
|
39
52
|
#### Example
|
|
40
53
|
```sh
|
|
41
54
|
❯ npx @dbalabka/chrome-wsl
|
|
@@ -61,6 +74,7 @@ To install globally instead of npx:
|
|
|
61
74
|
Then run:
|
|
62
75
|
```sh
|
|
63
76
|
chrome-wsl
|
|
77
|
+
chrome-wsl --headless
|
|
64
78
|
chrome-wsl --stop
|
|
65
79
|
chrome-wsl --uninstall
|
|
66
80
|
```
|
|
@@ -81,6 +95,7 @@ to cofigure the DevTools to connect to `--browser-url=http://127.0.0.1:9222`.
|
|
|
81
95
|
|
|
82
96
|
### Codex
|
|
83
97
|
|
|
98
|
+
Configuration of [Chrome DevTools](https://github.com/ChromeDevTools/chrome-devtools-mcp):
|
|
84
99
|
```toml
|
|
85
100
|
[mcp_servers.chome-devtools]
|
|
86
101
|
command = "npx"
|
|
@@ -88,6 +103,13 @@ args = ["-y", "chrome-devtools-mcp@latest", "--browser-url=http://127.0.0.1:9222
|
|
|
88
103
|
startup_timeout_sec = 20.0
|
|
89
104
|
```
|
|
90
105
|
|
|
106
|
+
Configuratio of [Playwrite](https://github.com/microsoft/playwright-mcp):
|
|
107
|
+
```toml
|
|
108
|
+
[mcp_servers.playwright]
|
|
109
|
+
command = "npx"
|
|
110
|
+
args = ["@playwright/mcp@latest", "--browser=chrome", "--isolated", "--cdp-endpoint=http://127.0.0.1:9222"]
|
|
111
|
+
```
|
|
112
|
+
|
|
91
113
|
To run Codex inside the container and use the same MCP configuration and authorisation token, mount the Codex configuration folder inside the docker container using the following docker composer settings:
|
|
92
114
|
```shell
|
|
93
115
|
services:
|
package/chrome-wsl
CHANGED
|
@@ -74,6 +74,66 @@ stop_socat() {
|
|
|
74
74
|
fi
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
debug_socat_info() {
|
|
78
|
+
echo "socat status:"
|
|
79
|
+
if command -v socat >/dev/null 2>&1; then
|
|
80
|
+
echo " version: $(socat -V 2>&1 | head -n 1)"
|
|
81
|
+
else
|
|
82
|
+
echo " version: not installed"
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
local pattern="socat TCP-LISTEN:${PORT},fork,reuseaddr"
|
|
86
|
+
if pgrep -af "$pattern" >/dev/null 2>&1; then
|
|
87
|
+
pgrep -af "$pattern" | while read -r line; do
|
|
88
|
+
echo " process: ${line}"
|
|
89
|
+
done
|
|
90
|
+
else
|
|
91
|
+
echo " process: not running"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
local socat_pid
|
|
95
|
+
socat_pid=$(get_pid "SOCAT_PID")
|
|
96
|
+
if [[ -n "${socat_pid:-}" ]]; then
|
|
97
|
+
echo " tracked pid: ${socat_pid}"
|
|
98
|
+
else
|
|
99
|
+
echo " tracked pid: none"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [[ -f /tmp/socat-9222.log ]]; then
|
|
103
|
+
echo " log: /tmp/socat-9222.log"
|
|
104
|
+
else
|
|
105
|
+
echo " log: not found"
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
debug_linux_listeners() {
|
|
110
|
+
echo "Linux listeners on port ${PORT}:"
|
|
111
|
+
if command -v ss >/dev/null 2>&1; then
|
|
112
|
+
if ss -lntp 2>/dev/null | awk -v port=":${PORT}" '$0 ~ port {print " " $0}' | head -n 50; then
|
|
113
|
+
return 0
|
|
114
|
+
fi
|
|
115
|
+
fi
|
|
116
|
+
if command -v lsof >/dev/null 2>&1; then
|
|
117
|
+
if lsof -nP -iTCP:"${PORT}" -sTCP:LISTEN 2>/dev/null | awk 'NR==1 {print " " $0; next} {print " " $0}'; then
|
|
118
|
+
return 0
|
|
119
|
+
fi
|
|
120
|
+
fi
|
|
121
|
+
echo " unavailable (no permissions or tools)"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
debug_windows_listeners() {
|
|
125
|
+
echo "Windows listeners on port ${PORT}:"
|
|
126
|
+
run_powershell "netstat -ano | Select-String -Pattern \"LISTENING\" | Select-String -Pattern \":${PORT}\" | ForEach-Object { \" \" + \$_.Line }"
|
|
127
|
+
run_powershell "if (-not (netstat -ano | Select-String -Pattern \"LISTENING\" | Select-String -Pattern \":${PORT}\")) { Write-Output ' none' }"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
debug_windows_rules() {
|
|
131
|
+
echo "Portproxy rules for port ${PORT}:"
|
|
132
|
+
run_powershell "\$lines = netsh interface portproxy show all | Select-String -SimpleMatch '${PORT}' | ForEach-Object { \$_.Line }; if (\$lines) { \$lines } else { ' none' }"
|
|
133
|
+
echo "Firewall rules for port ${PORT}:"
|
|
134
|
+
run_powershell "\$port='${PORT}'; \$rules = Get-NetFirewallRule -ErrorAction SilentlyContinue | Where-Object { \$filter = Get-NetFirewallPortFilter -AssociatedNetFirewallRule \$_ -ErrorAction SilentlyContinue; \$filter -and (\$filter.LocalPort -contains \$port) }; if (\$rules) { \$rules | ForEach-Object { \" {0} enabled={1} direction={2} action={3} profile={4}\" -f \$_.DisplayName, \$_.Enabled, \$_.Direction, \$_.Action, \$_.Profile } } else { ' none' }"
|
|
135
|
+
}
|
|
136
|
+
|
|
77
137
|
run_powershell() {
|
|
78
138
|
# Runs a PowerShell command from WSL. If RUN_AS_ADMIN=1, invoke via Start-Process -Verb RunAs
|
|
79
139
|
# and capture output through a temp script/output file to mirror direct execution.
|
|
@@ -106,7 +166,11 @@ confirm() {
|
|
|
106
166
|
local prompt=$1
|
|
107
167
|
local reply
|
|
108
168
|
local prefix="${CONTAINER_LABEL:+[${CONTAINER_LABEL}] }"
|
|
109
|
-
|
|
169
|
+
if [[ -r /dev/tty ]]; then
|
|
170
|
+
read -r -p "${CONFIRM_MARK} ${prefix}${prompt} [y/N] " reply </dev/tty
|
|
171
|
+
else
|
|
172
|
+
read -r -p "${CONFIRM_MARK} ${prefix}${prompt} [y/N] " reply
|
|
173
|
+
fi
|
|
110
174
|
[[ "${reply}" =~ ^[Yy]$ ]]
|
|
111
175
|
}
|
|
112
176
|
|
|
@@ -145,6 +209,38 @@ get_browser_version() {
|
|
|
145
209
|
| head -n 1
|
|
146
210
|
}
|
|
147
211
|
|
|
212
|
+
get_protocol_version() {
|
|
213
|
+
local host_ip=${1:-$HOST_IP}
|
|
214
|
+
[[ -z "${host_ip:-}" ]] && return 0
|
|
215
|
+
curl -s "http://${host_ip}:${PORT}/json/version" \
|
|
216
|
+
| sed -n "s/.*\"Protocol-Version\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" \
|
|
217
|
+
| head -n 1
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
check_devtools_access_wsl() {
|
|
221
|
+
local url_local="http://127.0.0.1:${PORT}/json/version"
|
|
222
|
+
local url_host="http://${HOST_IP}:${PORT}/json/version"
|
|
223
|
+
local json browser protocol
|
|
224
|
+
|
|
225
|
+
json=$(curl -s --max-time 2 "$url_local" || true)
|
|
226
|
+
if [[ -z "${json:-}" ]]; then
|
|
227
|
+
warn "Chrome DevTools not reachable at ${url_local} from WSL."
|
|
228
|
+
json=$(curl -s --max-time 2 "$url_host" || true)
|
|
229
|
+
if [[ -z "${json:-}" ]]; then
|
|
230
|
+
err "Chrome DevTools not reachable at ${url_host} from WSL."
|
|
231
|
+
return 1
|
|
232
|
+
fi
|
|
233
|
+
fi
|
|
234
|
+
|
|
235
|
+
browser=$(echo "$json" | sed -n "s/.*\"Browser\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n 1)
|
|
236
|
+
protocol=$(echo "$json" | sed -n "s/.*\"Protocol-Version\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" | head -n 1)
|
|
237
|
+
if [[ -n "${browser:-}" || -n "${protocol:-}" ]]; then
|
|
238
|
+
ok "DevTools reachable from WSL: ${browser:-unknown} (Protocol ${protocol:-unknown})."
|
|
239
|
+
else
|
|
240
|
+
warn "DevTools reachable from WSL, but version info was not parsed."
|
|
241
|
+
fi
|
|
242
|
+
}
|
|
243
|
+
|
|
148
244
|
get_wsl_host_ip() {
|
|
149
245
|
local host_ip
|
|
150
246
|
host_ip=$(ip route | awk '/^default via / {print $3; exit}')
|
|
@@ -158,8 +254,23 @@ get_wsl_host_ip() {
|
|
|
158
254
|
check_portproxy() {
|
|
159
255
|
local host_ip=$1
|
|
160
256
|
local output regex
|
|
161
|
-
output=$(run_powershell "netsh interface portproxy show all" | tr -d '
|
|
162
|
-
|
|
257
|
+
output=$(run_powershell "netsh interface portproxy show all" | tr -d '\r')
|
|
258
|
+
|
|
259
|
+
echo "$output" | awk 'NF>=4 && $2 ~ /^[0-9]+$/ {print $1, $2, $3, $4}' | while read -r listen_addr listen_port connect_addr connect_port; do
|
|
260
|
+
if [[ "$listen_port" == "$PORT" && "$listen_addr" != "$host_ip" ]]; then
|
|
261
|
+
if confirm "Remove portproxy ${listen_addr}:${listen_port} -> ${connect_addr}:${connect_port} (conflicts with ${host_ip}:${PORT})?"; then
|
|
262
|
+
if RUN_AS_ADMIN=1 run_powershell "netsh interface portproxy delete v4tov4 listenaddress=${listen_addr} listenport=${listen_port}"; then
|
|
263
|
+
ok "Removed conflicting portproxy ${listen_addr}:${listen_port}."
|
|
264
|
+
else
|
|
265
|
+
warn "Failed to remove conflicting portproxy ${listen_addr}:${listen_port}."
|
|
266
|
+
fi
|
|
267
|
+
else
|
|
268
|
+
warn "Keeping conflicting portproxy ${listen_addr}:${listen_port}."
|
|
269
|
+
fi
|
|
270
|
+
fi
|
|
271
|
+
done
|
|
272
|
+
|
|
273
|
+
output=$(run_powershell "netsh interface portproxy show all" | tr -d '\r')
|
|
163
274
|
regex="${host_ip//./\.}[[:space:]]+${PORT}[[:space:]]+127\.0\.0\.1[[:space:]]+${PORT}"
|
|
164
275
|
if echo "$output" | grep -Eq "$regex"; then
|
|
165
276
|
ok "Portproxy ${host_ip}:${PORT} -> 127.0.0.1:${PORT} is configured."
|
|
@@ -348,8 +459,13 @@ stop_chrome() {
|
|
|
348
459
|
}
|
|
349
460
|
|
|
350
461
|
start_chrome() {
|
|
462
|
+
local headless_mode="${1:-0}"
|
|
463
|
+
local headless_args=""
|
|
464
|
+
if [[ "$headless_mode" -eq 1 ]]; then
|
|
465
|
+
headless_args=", \"--headless=new\""
|
|
466
|
+
fi
|
|
351
467
|
local chrome_cmd
|
|
352
|
-
chrome_cmd="\$args = @(\"--remote-debugging-port=${PORT}\", \"--no-first-run\", \"--no-default-browser-check\", \"--user-data-dir=\$env:TEMP\\chrome-profile-stable\"); \$p = Start-Process -FilePath \"${WINDOWS_CHROME_PATH}\" -ArgumentList \$args -PassThru; Write-Output \$p.Id"
|
|
468
|
+
chrome_cmd="\$args = @(\"--remote-debugging-port=${PORT}\", \"--no-first-run\", \"--no-default-browser-check\", \"--user-data-dir=\$env:TEMP\\chrome-profile-stable\"${headless_args}); \$p = Start-Process -FilePath \"${WINDOWS_CHROME_PATH}\" -ArgumentList \$args -PassThru; Write-Output \$p.Id"
|
|
353
469
|
|
|
354
470
|
if [[ -n "$(get_pid "CHROME_PID")" ]]; then
|
|
355
471
|
if port_listening_by_chrome; then
|
|
@@ -388,12 +504,14 @@ ensure_wsl() {
|
|
|
388
504
|
}
|
|
389
505
|
|
|
390
506
|
main() {
|
|
391
|
-
local container="" uninstall_flag=0 stop_flag=0
|
|
507
|
+
local container="" uninstall_flag=0 stop_flag=0 debug_flag=0 headless_flag=0
|
|
392
508
|
for arg in "$@"; do
|
|
393
509
|
case "$arg" in
|
|
394
510
|
--container=*) container=${arg#*=} ;;
|
|
395
511
|
--uninstall) uninstall_flag=1 ;;
|
|
396
512
|
--stop) stop_flag=1 ;;
|
|
513
|
+
--debug) debug_flag=1 ;;
|
|
514
|
+
--headless) headless_flag=1 ;;
|
|
397
515
|
esac
|
|
398
516
|
done
|
|
399
517
|
|
|
@@ -408,6 +526,11 @@ main() {
|
|
|
408
526
|
fi
|
|
409
527
|
|
|
410
528
|
if is_docker; then
|
|
529
|
+
if [[ $debug_flag -eq 1 ]]; then
|
|
530
|
+
debug_socat_info
|
|
531
|
+
exit 0
|
|
532
|
+
fi
|
|
533
|
+
|
|
411
534
|
if [[ $uninstall_flag -eq 1 ]]; then
|
|
412
535
|
uninstall_socat
|
|
413
536
|
exit 0
|
|
@@ -431,6 +554,17 @@ main() {
|
|
|
431
554
|
|
|
432
555
|
ensure_wsl
|
|
433
556
|
|
|
557
|
+
if [[ $debug_flag -eq 1 ]]; then
|
|
558
|
+
debug_windows_rules
|
|
559
|
+
debug_windows_listeners
|
|
560
|
+
debug_linux_listeners
|
|
561
|
+
debug_socat_info
|
|
562
|
+
if [[ -n "$container" ]]; then
|
|
563
|
+
container_exec "$container" --debug
|
|
564
|
+
fi
|
|
565
|
+
exit 0
|
|
566
|
+
fi
|
|
567
|
+
|
|
434
568
|
if [[ $uninstall_flag -eq 1 ]]; then
|
|
435
569
|
uninstall_portproxy
|
|
436
570
|
uninstall_firewall_rule
|
|
@@ -456,13 +590,14 @@ main() {
|
|
|
456
590
|
|
|
457
591
|
check_portproxy "$host_ip" || exit 1
|
|
458
592
|
check_firewall_rule || exit 1
|
|
593
|
+
start_chrome "$headless_flag"
|
|
459
594
|
ensure_socat
|
|
460
595
|
start_socat "$host_ip"
|
|
461
|
-
|
|
596
|
+
check_devtools_access_wsl || true
|
|
462
597
|
|
|
463
598
|
if [[ -n "$container" ]]; then
|
|
464
599
|
container_exec "$container"
|
|
465
600
|
fi
|
|
466
601
|
}
|
|
467
602
|
|
|
468
|
-
main "$@"
|
|
603
|
+
main "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbalabka/chrome-wsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "WSL helper to start Windows Chrome with remote debugging and socat port forwarding.",
|
|
5
5
|
"bin": "./chrome-wsl",
|
|
6
6
|
"files": [
|
|
@@ -28,4 +28,4 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|