@@ -153,14 +153,15 @@ RUN set -eu; \
153153
154154const dockerGitSessionSyncPackage = "@prover-coder-ai/docker-git-session-sync@latest"
155155
156- const dockerfilePlaywrightMcpBlock = String . raw `RUN npm install -g @playwright/mcp@latest
156+ const dockerfilePlaywrightMcpBlock = String . raw `ARG PLAYWRIGHT_MCP_VERSION=0.0.75
157+ RUN npm install -g "@playwright/mcp@${ "$" } {PLAYWRIGHT_MCP_VERSION}"
157158
158- # docker-git: wrapper that waits for the guarded CDP endpoint before launching Playwright MCP .
159+ # docker-git: wrapper that launches the MCP stdio server without blocking initialize on CDP readiness .
159160RUN cat <<'EOF' > /usr/local/bin/docker-git-playwright-mcp
160161#!/usr/bin/env bash
161162set -euo pipefail
162163
163- # Fast-path for help/version (avoid waiting for the browser sidecar ).
164+ # Fast-path for help/version (avoid waiting for nested browser startup ).
164165for arg in "$@"; do
165166 case "$arg" in
166167 -h|--help|-V|--version)
@@ -171,22 +172,36 @@ done
171172
172173CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
173174if [[ -z "$CDP_ENDPOINT" ]]; then
174- CDP_ENDPOINT="http://__SERVICE_NAME__-browser :9223"
175+ CDP_ENDPOINT="http://127.0.0.1 :9223"
175176fi
176177
177- # CHANGE: add retry logic for browser sidecar startup wait
178- # WHY: the browser container may take time to initialize, causing MCP server to fail on first attempt
179- # QUOTE(issue-123 ): "Почему MCP сервер лежит с ошибкой? "
180- # REF: issue-123
181- # SOURCE: n/a
182- # FORMAT THEOREM: forall t in [1..max_attempts]: retry(t ) -> eventually(cdp_ready) OR timeout_error
178+ # CHANGE: keep MCP initialize independent from nested browser readiness
179+ # WHY: Codex starts MCP servers during boot; blocking here closes stdio before initialize when CDP is slow.
180+ # QUOTE(issue-319 ): "handshaking with MCP server failed: connection closed: initialize response "
181+ # REF: issue-319
182+ # SOURCE: https://playwright.dev/mcp/configuration/options
183+ # FORMAT THEOREM: guarded_cdp(endpoint ) -> mcp_stdio_ready_before_browser_connection
183184# PURITY: SHELL
184- # INVARIANT: script exits only after cdp_ready OR all retries exhausted
185- # COMPLEXITY: O(max_attempts * timeout_per_attempt )
185+ # INVARIANT: guarded mode never exits before handing stdio to playwright-mcp
186+ # COMPLEXITY: O(1 )
186187MCP_PLAYWRIGHT_RETRY_ATTEMPTS="\${MCP_PLAYWRIGHT_RETRY_ATTEMPTS:-10}"
187188MCP_PLAYWRIGHT_RETRY_DELAY="\${MCP_PLAYWRIGHT_RETRY_DELAY:-2}"
188189MCP_PLAYWRIGHT_CDP_GUARD="\${MCP_PLAYWRIGHT_CDP_GUARD:-1}"
190+ MCP_PLAYWRIGHT_CDP_TIMEOUT="\${MCP_PLAYWRIGHT_CDP_TIMEOUT:-60000}"
189191
192+ EXTRA_ARGS=()
193+ if [[ "\${MCP_PLAYWRIGHT_ISOLATED:-0}" == "1" ]]; then
194+ EXTRA_ARGS+=(--isolated)
195+ fi
196+
197+ # Guarded endpoints are stable HTTP CDP endpoints. Passing the HTTP URL lets Playwright MCP
198+ # re-resolve /json/version instead of pinning itself to one stale /devtools/browser/<id>.
199+ if [[ "$MCP_PLAYWRIGHT_CDP_GUARD" == "1" ]]; then
200+ exec playwright-mcp --cdp-endpoint "$CDP_ENDPOINT" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
201+ fi
202+
203+ # kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
204+ # When the guard is disabled, preserve the old behavior by converting the HTTP endpoint to WS.
190205fetch_cdp_version() {
191206 curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version" 2>/dev/null
192207}
@@ -197,7 +212,7 @@ for attempt in $(seq 1 "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS"); do
197212 break
198213 fi
199214 if [[ "$attempt" -lt "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS" ]]; then
200- echo "docker-git-playwright-mcp: waiting for browser sidecar (attempt $attempt/$MCP_PLAYWRIGHT_RETRY_ATTEMPTS)..." >&2
215+ echo "docker-git-playwright-mcp: waiting for nested browser runtime (attempt $attempt/$MCP_PLAYWRIGHT_RETRY_ATTEMPTS)..." >&2
201216 sleep "$MCP_PLAYWRIGHT_RETRY_DELAY"
202217 fi
203218done
@@ -207,19 +222,6 @@ if [[ -z "$JSON" ]]; then
207222 exit 1
208223fi
209224
210- EXTRA_ARGS=()
211- if [[ "\${MCP_PLAYWRIGHT_ISOLATED:-0}" == "1" ]]; then
212- EXTRA_ARGS+=(--isolated)
213- fi
214-
215- # Guarded endpoints are stable HTTP CDP endpoints. Passing the HTTP URL lets Playwright MCP
216- # re-resolve /json/version instead of pinning itself to one stale /devtools/browser/<id>.
217- if [[ "$MCP_PLAYWRIGHT_CDP_GUARD" == "1" ]]; then
218- exec playwright-mcp --cdp-endpoint "$CDP_ENDPOINT" "\${EXTRA_ARGS[@]}" "$@"
219- fi
220-
221- # kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
222- # When the guard is disabled, preserve the old behavior by converting the HTTP endpoint to WS.
223225WS_URL="$(printf "%s" "$JSON" | node -e 'const fs=require("fs"); const j=JSON.parse(fs.readFileSync(0,"utf8")); process.stdout.write(j.webSocketDebuggerUrl || "")')"
224226if [[ -z "$WS_URL" ]]; then
225227 echo "docker-git-playwright-mcp: webSocketDebuggerUrl missing" >&2
230232BASE_WS="$(CDP_ENDPOINT="$CDP_ENDPOINT" node -e 'const { URL } = require("url"); const u=new URL(process.env.CDP_ENDPOINT); const proto=u.protocol==="https:"?"wss:":"ws:"; process.stdout.write(proto + "//" + u.host)')"
231233WS_REWRITTEN="$(BASE_WS="$BASE_WS" WS_URL="$WS_URL" node -e 'const { URL } = require("url"); const base=new URL(process.env.BASE_WS); const ws=new URL(process.env.WS_URL); ws.protocol=base.protocol; ws.host=base.host; process.stdout.write(ws.toString())')"
232234
233- exec playwright-mcp --cdp-endpoint "$WS_REWRITTEN" "\${EXTRA_ARGS[@]}" "$@"
235+ exec playwright-mcp --cdp-endpoint "$WS_REWRITTEN" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
234236EOF
235237RUN chmod +x /usr/local/bin/docker-git-playwright-mcp`
236238
239+ const renderDockerfilePlaywrightRuntime = ( config : TemplateConfig ) : string =>
240+ config . enableMcpPlaywright
241+ ? `# docker-git nested Playwright browser runtime context
242+ COPY Dockerfile.browser mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
243+ RUN chmod +x /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
244+ : ""
245+
237246/**
238247 * Renders /etc/profile.d/bun.sh with a runtime-relative PATH extension.
239248 *
@@ -391,6 +400,7 @@ export const renderDockerfile = (config: TemplateConfig): string =>
391400 renderDockerfilePrompt ( ) ,
392401 renderDockerfileNode ( ) ,
393402 renderDockerfileBun ( config ) ,
403+ renderDockerfilePlaywrightRuntime ( config ) ,
394404 renderDockerfileRtk ( ) ,
395405 renderDockerfileOpenCode ( ) ,
396406 renderDockerfileGitleaks ( ) ,
0 commit comments