@@ -155,12 +155,12 @@ const dockerGitSessionSyncPackage = "@prover-coder-ai/docker-git-session-sync@la
155155
156156const dockerfilePlaywrightMcpBlock = String . raw `RUN npm install -g @playwright/mcp@latest
157157
158- # docker-git: wrapper that waits for the guarded CDP endpoint before launching Playwright MCP .
158+ # docker-git: wrapper that launches the MCP stdio server without blocking initialize on CDP readiness .
159159RUN cat <<'EOF' > /usr/local/bin/docker-git-playwright-mcp
160160#!/usr/bin/env bash
161161set -euo pipefail
162162
163- # Fast-path for help/version (avoid waiting for the browser sidecar ).
163+ # Fast-path for help/version (avoid waiting for nested browser startup ).
164164for arg in "$@"; do
165165 case "$arg" in
166166 -h|--help|-V|--version)
@@ -171,22 +171,36 @@ done
171171
172172CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
173173if [[ -z "$CDP_ENDPOINT" ]]; then
174- CDP_ENDPOINT="http://__SERVICE_NAME__-browser :9223"
174+ CDP_ENDPOINT="http://127.0.0.1 :9223"
175175fi
176176
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
177+ # CHANGE: keep MCP initialize independent from nested browser readiness
178+ # WHY: Codex starts MCP servers during boot; blocking here closes stdio before initialize when CDP is slow.
179+ # QUOTE(issue-319 ): "handshaking with MCP server failed: connection closed: initialize response "
180+ # REF: issue-319
181+ # SOURCE: https://playwright.dev/mcp/configuration/options
182+ # FORMAT THEOREM: guarded_cdp(endpoint ) -> mcp_stdio_ready_before_browser_connection
183183# PURITY: SHELL
184- # INVARIANT: script exits only after cdp_ready OR all retries exhausted
185- # COMPLEXITY: O(max_attempts * timeout_per_attempt )
184+ # INVARIANT: guarded mode never exits before handing stdio to playwright-mcp
185+ # COMPLEXITY: O(1 )
186186MCP_PLAYWRIGHT_RETRY_ATTEMPTS="\${MCP_PLAYWRIGHT_RETRY_ATTEMPTS:-10}"
187187MCP_PLAYWRIGHT_RETRY_DELAY="\${MCP_PLAYWRIGHT_RETRY_DELAY:-2}"
188188MCP_PLAYWRIGHT_CDP_GUARD="\${MCP_PLAYWRIGHT_CDP_GUARD:-1}"
189+ MCP_PLAYWRIGHT_CDP_TIMEOUT="\${MCP_PLAYWRIGHT_CDP_TIMEOUT:-60000}"
189190
191+ EXTRA_ARGS=()
192+ if [[ "\${MCP_PLAYWRIGHT_ISOLATED:-0}" == "1" ]]; then
193+ EXTRA_ARGS+=(--isolated)
194+ fi
195+
196+ # Guarded endpoints are stable HTTP CDP endpoints. Passing the HTTP URL lets Playwright MCP
197+ # re-resolve /json/version instead of pinning itself to one stale /devtools/browser/<id>.
198+ if [[ "$MCP_PLAYWRIGHT_CDP_GUARD" == "1" ]]; then
199+ exec playwright-mcp --cdp-endpoint "$CDP_ENDPOINT" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
200+ fi
201+
202+ # kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
203+ # When the guard is disabled, preserve the old behavior by converting the HTTP endpoint to WS.
190204fetch_cdp_version() {
191205 curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version" 2>/dev/null
192206}
@@ -197,7 +211,7 @@ for attempt in $(seq 1 "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS"); do
197211 break
198212 fi
199213 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
214+ echo "docker-git-playwright-mcp: waiting for nested browser runtime (attempt $attempt/$MCP_PLAYWRIGHT_RETRY_ATTEMPTS)..." >&2
201215 sleep "$MCP_PLAYWRIGHT_RETRY_DELAY"
202216 fi
203217done
@@ -207,19 +221,6 @@ if [[ -z "$JSON" ]]; then
207221 exit 1
208222fi
209223
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.
223224WS_URL="$(printf "%s" "$JSON" | node -e 'const fs=require("fs"); const j=JSON.parse(fs.readFileSync(0,"utf8")); process.stdout.write(j.webSocketDebuggerUrl || "")')"
224225if [[ -z "$WS_URL" ]]; then
225226 echo "docker-git-playwright-mcp: webSocketDebuggerUrl missing" >&2
230231BASE_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)')"
231232WS_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())')"
232233
233- exec playwright-mcp --cdp-endpoint "$WS_REWRITTEN" "\${EXTRA_ARGS[@]}" "$@"
234+ exec playwright-mcp --cdp-endpoint "$WS_REWRITTEN" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
234235EOF
235236RUN chmod +x /usr/local/bin/docker-git-playwright-mcp`
236237
238+ const renderDockerfilePlaywrightRuntime = ( config : TemplateConfig ) : string =>
239+ config . enableMcpPlaywright
240+ ? `# docker-git nested Playwright browser runtime context
241+ COPY Dockerfile.browser mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
242+ RUN chmod +x /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
243+ : ""
244+
237245/**
238246 * Renders /etc/profile.d/bun.sh with a runtime-relative PATH extension.
239247 *
@@ -391,6 +399,7 @@ export const renderDockerfile = (config: TemplateConfig): string =>
391399 renderDockerfilePrompt ( ) ,
392400 renderDockerfileNode ( ) ,
393401 renderDockerfileBun ( config ) ,
402+ renderDockerfilePlaywrightRuntime ( config ) ,
394403 renderDockerfileRtk ( ) ,
395404 renderDockerfileOpenCode ( ) ,
396405 renderDockerfileGitleaks ( ) ,
0 commit comments