Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/app/src/docker-git/cli/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,6 @@ Container runtime env (set via .orch/env/project.env):
MCP_PLAYWRIGHT_ISOLATED=1|0 Isolated browser contexts; default 0 shares the VNC session
MCP_PLAYWRIGHT_CDP_GUARD=1|0 Guard CDP so MCP cannot close/crash shared Chromium (default: 1)
MCP_PLAYWRIGHT_BLOCK_BROWSER_CLOSE=1|0 Block destructive Browser.close/crash CDP methods (default: 1)
MCP_PLAYWRIGHT_CDP_ENDPOINT=http://... Override CDP endpoint (default: http://127.0.0.1:9223)
MCP_PLAYWRIGHT_CDP_TIMEOUT=<ms> CDP connect timeout passed to Playwright MCP (default: 60000)
MCP_PLAYWRIGHT_READY_ATTEMPTS=<n> Startup readiness attempts before disabling broken MCP (default: 60)
MCP_PLAYWRIGHT_READY_DELAY=<seconds> Delay between startup readiness attempts (default: 1)
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/lib/core/templates-entrypoint/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ CODEX_AUTO_UPDATE="\${CODEX_AUTO_UPDATE:-1}"
AGENT_MODE="\${AGENT_MODE:-}"
AGENT_AUTO="\${AGENT_AUTO:-}"
MCP_PLAYWRIGHT_ENABLE="\${MCP_PLAYWRIGHT_ENABLE:-${config.enableMcpPlaywright ? "1" : "0"}}"
MCP_PLAYWRIGHT_CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
MCP_PLAYWRIGHT_ISOLATED="\${MCP_PLAYWRIGHT_ISOLATED:-0}"
MCP_PLAYWRIGHT_CDP_GUARD="\${MCP_PLAYWRIGHT_CDP_GUARD:-1}"
MCP_PLAYWRIGHT_BLOCK_BROWSER_CLOSE="\${MCP_PLAYWRIGHT_BLOCK_BROWSER_CLOSE:-1}"
Expand Down
4 changes: 0 additions & 4 deletions packages/app/src/lib/core/templates-entrypoint/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,6 @@ EOF
chown 1000:1000 "$CODEX_CONFIG_FILE" || true
fi

if [[ -z "$MCP_PLAYWRIGHT_CDP_ENDPOINT" ]]; then
MCP_PLAYWRIGHT_CDP_ENDPOINT="http://127.0.0.1:9223"
fi

# Replace the docker-git Playwright block to allow upgrades via --force without manual edits.
if grep -q "^\[mcp_servers\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
awk '
Expand Down
12 changes: 11 additions & 1 deletion packages/app/src/lib/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { renderEntrypoint } from "./templates-entrypoint.js"
import { type ComposeResourceLimits, renderDockerCompose } from "./templates/docker-compose.js"
import { renderDockerfile } from "./templates/dockerfile.js"
import { renderPlaywrightBrowserRuntime } from "./templates/playwright-browser-runtime.js"
import { renderPlaywrightBrowserDockerfile, renderPlaywrightStartExtra } from "./templates/playwright.js"
import {
renderPlaywrightBrowserDockerfile,
renderPlaywrightCdpGuard,
renderPlaywrightStartExtra
} from "./templates/playwright.js"

export type FileSpec =
| { readonly _tag: "File"; readonly relativePath: string; readonly contents: string; readonly mode?: number }
Expand Down Expand Up @@ -52,6 +56,12 @@ export const planFiles = (
const maybePlaywrightFiles = config.enableMcpPlaywright
? ([
{ _tag: "File", relativePath: "Dockerfile.browser", contents: renderPlaywrightBrowserDockerfile() },
{
_tag: "File",
relativePath: "docker-git-cdp-guard",
contents: renderPlaywrightCdpGuard(),
mode: 0o755
},
{
_tag: "File",
relativePath: "mcp-playwright-start-extra.sh",
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/lib/core/templates/docker-compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const buildPlaywrightFragments = (
maybeDependsOn: "",
maybeDockerSocketMount: renderOptionalDockerSocketMount(options.enableLocalDockerSocket),
maybePlaywrightEnv:
` MCP_PLAYWRIGHT_ENABLE: "1"\n MCP_PLAYWRIGHT_CDP_ENDPOINT: "http://127.0.0.1:9223"\n DOCKER_GIT_PROJECT_CONTAINER_NAME: "${config.containerName}"\n DOCKER_GIT_BROWSER_CONTAINER_NAME: "${browserContainerName}"\n DOCKER_GIT_BROWSER_IMAGE_NAME: "${browserImageName}"\n DOCKER_GIT_BROWSER_VOLUME_NAME: "${browserVolumeName}"\n${
` MCP_PLAYWRIGHT_ENABLE: "1"\n DOCKER_GIT_PROJECT_CONTAINER_NAME: "${config.containerName}"\n DOCKER_GIT_BROWSER_CONTAINER_NAME: "${browserContainerName}"\n DOCKER_GIT_BROWSER_IMAGE_NAME: "${browserImageName}"\n DOCKER_GIT_BROWSER_VOLUME_NAME: "${browserVolumeName}"\n${
renderBrowserLimitEnv("DOCKER_GIT_BROWSER_CPU_LIMIT", resourceLimits?.cpuLimit)
}${renderBrowserLimitEnv("DOCKER_GIT_BROWSER_RAM_LIMIT", resourceLimits?.ramLimit)}`,
maybeBrowserVolume: ` ${browserVolumeName}:`
Expand Down
14 changes: 6 additions & 8 deletions packages/app/src/lib/core/templates/dockerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,14 @@ for arg in "$@"; do
esac
done

CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
if [[ -z "$CDP_ENDPOINT" ]]; then
CDP_ENDPOINT="http://127.0.0.1:9223"
fi
CDP_ENDPOINT="http://127.0.0.1:9223"

# CHANGE: keep MCP initialize independent from nested browser readiness
# WHY: Codex starts MCP servers during boot; blocking here closes stdio before initialize when CDP is slow.
# QUOTE(issue-319): "handshaking with MCP server failed: connection closed: initialize response"
# REF: issue-319
# SOURCE: https://playwright.dev/mcp/configuration/options
# FORMAT THEOREM: guarded_cdp(endpoint) -> mcp_stdio_ready_before_browser_connection
# FORMAT THEOREM: guarded_cdp(fixed_nested_browser_endpoint) -> mcp_stdio_ready_before_browser_connection
# PURITY: SHELL
# INVARIANT: guarded mode never exits before handing stdio to playwright-mcp
# COMPLEXITY: O(1)
Expand All @@ -194,7 +191,8 @@ if [[ "\${MCP_PLAYWRIGHT_ISOLATED:-0}" == "1" ]]; then
EXTRA_ARGS+=(--isolated)
fi

# Guarded endpoints are stable HTTP CDP endpoints. Passing the HTTP URL lets Playwright MCP
# The guarded endpoint is the nested browser opened by docker-git Open browser.
# Passing the fixed HTTP URL lets Playwright MCP
# re-resolve /json/version instead of pinning itself to one stale /devtools/browser/<id>.
if [[ "$MCP_PLAYWRIGHT_CDP_GUARD" == "1" ]]; then
exec playwright-mcp --cdp-endpoint "$CDP_ENDPOINT" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
Expand Down Expand Up @@ -239,8 +237,8 @@ RUN chmod +x /usr/local/bin/docker-git-playwright-mcp`
const renderDockerfilePlaywrightRuntime = (config: TemplateConfig): string =>
config.enableMcpPlaywright
? `# docker-git nested Playwright browser runtime context
COPY Dockerfile.browser mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
RUN chmod +x /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
COPY Dockerfile.browser docker-git-cdp-guard mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
RUN chmod +x /opt/docker-git/browser/docker-git-cdp-guard /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
: ""

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ docker_git_disable_playwright_mcp() {
}

docker_git_playwright_cdp_endpoint() {
printf '%s\\n' "\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-http://127.0.0.1:9223}"
printf '%s\\n' "http://127.0.0.1:9223"
}

docker_git_fetch_playwright_cdp_version() {
Expand Down
27 changes: 19 additions & 8 deletions packages/app/src/lib/core/templates/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ export const renderPlaywrightBrowserDockerfile = (): string =>
RUN apk add --no-cache bash procps socat nodejs npm python3 net-tools
RUN npm install --omit=dev --prefix /opt/docker-git-cdp-guard ws@8.18.3

RUN cat <<'EOF' > /usr/local/bin/docker-git-cdp-guard
${cdpGuardScript}
EOF
COPY docker-git-cdp-guard /usr/local/bin/docker-git-cdp-guard
RUN chmod +x /usr/local/bin/docker-git-cdp-guard

COPY mcp-playwright-start-extra.sh /usr/local/bin/mcp-playwright-start-extra.sh
Expand All @@ -26,10 +24,10 @@ const http = require("node:http");
const { URL } = require("node:url");
const { WebSocket, WebSocketServer } = require("/opt/docker-git-cdp-guard/node_modules/ws");

const upstreamHost = process.env.MCP_PLAYWRIGHT_UPSTREAM_CDP_HOST || "127.0.0.1";
const upstreamPort = Number.parseInt(process.env.MCP_PLAYWRIGHT_UPSTREAM_CDP_PORT || "9222", 10);
const listenHost = process.env.MCP_PLAYWRIGHT_CDP_GUARD_HOST || "0.0.0.0";
const listenPort = Number.parseInt(process.env.MCP_PLAYWRIGHT_CDP_GUARD_PORT || "9223", 10);
const upstreamHost = "127.0.0.1";
const upstreamPort = 9222;
const listenHost = "0.0.0.0";
const listenPort = 9223;
const blockedMethods = new Set(["Browser.close", "Browser.crash", "Browser.crashGpuProcess"]);

const log = (message) => process.stderr.write("[docker-git-cdp-guard] " + message + "\n");
Expand Down Expand Up @@ -244,6 +242,8 @@ server.listen(listenPort, listenHost, () => {
});
`

export const renderPlaywrightCdpGuard = (): string => cdpGuardScript

export const renderPlaywrightStartExtra = (): string =>
`#!/bin/sh
set -eu
Expand All @@ -254,11 +254,22 @@ rm -f /data/SingletonLock /data/SingletonCookie /data/SingletonSocket || true
# Wait for chromium/x11vnc/noVNC to come up
sleep 2

start_cdp_fallback() {
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 >/var/log/socat-9223.log 2>&1 &
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# CDP guard: expose 9223 on the docker network and block browser-level destructive CDP methods
if [ "\${MCP_PLAYWRIGHT_CDP_GUARD:-1}" = "1" ]; then
docker-git-cdp-guard >/var/log/docker-git-cdp-guard.log 2>&1 &
guard_pid="$!"
sleep 1
if ! kill -0 "$guard_pid" 2>/dev/null; then
echo "docker-git-cdp-guard exited during startup; falling back to socat" >&2
sed -n '1,120p' /var/log/docker-git-cdp-guard.log 2>/dev/null >&2 || true
start_cdp_fallback
fi
else
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 >/var/log/socat-9223.log 2>&1 &
start_cdp_fallback
Comment on lines +257 to +272
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Startup check с sleep 1 может пропустить медленный запуск guard.

Если docker-git-cdp-guard стартует дольше 1 секунды (например, при cold start Node.js или медленном диске), kill -0 сработает, но guard может упасть позже. MCP-клиент получит connection refused.

Рекомендация: добавить активную проверку готовности (curl к /json/version на 9223) вместо или в дополнение к sleep 1.

♻️ Альтернатива с активной проверкой готовности
 if [ "\${MCP_PLAYWRIGHT_CDP_GUARD:-1}" = "1" ]; then
   docker-git-cdp-guard >/var/log/docker-git-cdp-guard.log 2>&1 &
   guard_pid="$!"
-  sleep 1
-  if ! kill -0 "$guard_pid" 2>/dev/null; then
+  # Wait up to 5s for guard to become ready
+  for i in 1 2 3 4 5; do
+    sleep 1
+    if ! kill -0 "$guard_pid" 2>/dev/null; then
+      break
+    fi
+    if curl -sf http://127.0.0.1:9223/json/version >/dev/null 2>&1; then
+      break
+    fi
+  done
+  if ! kill -0 "$guard_pid" 2>/dev/null || ! curl -sf http://127.0.0.1:9223/json/version >/dev/null 2>&1; then
     echo "docker-git-cdp-guard exited during startup; falling back to socat" >&2
     sed -n '1,120p' /var/log/docker-git-cdp-guard.log 2>/dev/null >&2 || true
     start_cdp_fallback
   fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
start_cdp_fallback() {
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 >/var/log/socat-9223.log 2>&1 &
}
# CDP guard: expose 9223 on the docker network and block browser-level destructive CDP methods
if [ "\${MCP_PLAYWRIGHT_CDP_GUARD:-1}" = "1" ]; then
docker-git-cdp-guard >/var/log/docker-git-cdp-guard.log 2>&1 &
guard_pid="$!"
sleep 1
if ! kill -0 "$guard_pid" 2>/dev/null; then
echo "docker-git-cdp-guard exited during startup; falling back to socat" >&2
sed -n '1,120p' /var/log/docker-git-cdp-guard.log 2>/dev/null >&2 || true
start_cdp_fallback
fi
else
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 >/var/log/socat-9223.log 2>&1 &
start_cdp_fallback
start_cdp_fallback() {
socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222 >/var/log/socat-9223.log 2>&1 &
}
# CDP guard: expose 9223 on the docker network and block browser-level destructive CDP methods
if [ "\${MCP_PLAYWRIGHT_CDP_GUARD:-1}" = "1" ]; then
docker-git-cdp-guard >/var/log/docker-git-cdp-guard.log 2>&1 &
guard_pid="$!"
# Wait up to 5s for guard to become ready
for i in 1 2 3 4 5; do
sleep 1
if ! kill -0 "$guard_pid" 2>/dev/null; then
break
fi
if curl -sf http://127.0.0.1:9223/json/version >/dev/null 2>&1; then
break
fi
done
if ! kill -0 "$guard_pid" 2>/dev/null || ! curl -sf http://127.0.0.1:9223/json/version >/dev/null 2>&1; then
echo "docker-git-cdp-guard exited during startup; falling back to socat" >&2
sed -n '1,120p' /var/log/docker-git-cdp-guard.log 2>/dev/null >&2 || true
start_cdp_fallback
fi
else
start_cdp_fallback
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/app/src/lib/core/templates/playwright.ts` around lines 257 - 272,
The startup currently only sleeps 1s then checks the guard process, which can
miss slow startups; modify the guard startup block around docker-git-cdp-guard
(and the start_cdp_fallback call) to perform an active readiness probe instead
of a fixed sleep: after launching docker-git-cdp-guard and capturing guard_pid,
loop with a short sleep retrying a curl (e.g.
http://127.0.0.1:9223/json/version) until it returns a successful HTTP response
or a configurable timeout/retry count is reached, also break early if kill -0
reports the process died; if the probe fails or the process exited, log the
guard log and call start_cdp_fallback. Keep references to start_cdp_fallback and
docker-git-cdp-guard/guard_pid so the existing fallback behavior is preserved.

fi

# Optional VNC password disabling (useful if you publish VNC/noVNC ports)
Expand Down
24 changes: 23 additions & 1 deletion packages/app/tests/docker-git/core-templates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,43 @@ describe("app planFiles", () => {
const files = planFiles(makeTemplateConfig({ enableMcpPlaywright: true }))
const filePaths = getGeneratedFilePaths(files)
const runtime = getGeneratedFile(files, "docker-git-browser-runtime.sh")
const cdpGuard = getGeneratedFile(files, "docker-git-cdp-guard")
const browserDockerfile = getGeneratedFile(files, "Dockerfile.browser")
const startExtra = getGeneratedFile(files, "mcp-playwright-start-extra.sh")
const dockerfile = getGeneratedFile(files, "Dockerfile")

expect(filePaths).toContain("Dockerfile.browser")
expect(filePaths).toContain("docker-git-cdp-guard")
expect(filePaths).toContain("mcp-playwright-start-extra.sh")
expect(filePaths).toContain("docker-git-browser-runtime.sh")
expect(cdpGuard.mode).toBe(0o755)
expect(cdpGuard.contents).toContain("#!/usr/bin/env node")
expect(cdpGuard.contents).toContain("const upstreamHost = \"127.0.0.1\";")
expect(cdpGuard.contents).toContain("const upstreamPort = 9222;")
expect(cdpGuard.contents).toContain("const listenHost = \"0.0.0.0\";")
expect(cdpGuard.contents).toContain("const listenPort = 9223;")
expect(cdpGuard.contents).not.toContain("MCP_PLAYWRIGHT_UPSTREAM_CDP_HOST")
expect(cdpGuard.contents).not.toContain("MCP_PLAYWRIGHT_CDP_GUARD_PORT")
expect(cdpGuard.contents).toContain("Browser.close")
expect(browserDockerfile.contents).toContain("COPY docker-git-cdp-guard /usr/local/bin/docker-git-cdp-guard")
expect(browserDockerfile.contents).not.toContain("RUN cat <<'EOF' > /usr/local/bin/docker-git-cdp-guard")
expect(startExtra.contents).toContain("guard_pid=\"$!\"")
expect(startExtra.contents).toContain("falling back to socat")
expect(startExtra.contents).toContain("socat TCP-LISTEN:9223,fork,reuseaddr TCP:127.0.0.1:9222")
expect(runtime.mode).toBe(0o755)
expect(runtime.contents).toContain("if [[ \"${MCP_PLAYWRIGHT_ENABLE:-0}\" != \"1\" ]]; then")
expect(runtime.contents).toContain(String.raw`printf '%s\n' "http://127.0.0.1:9223"`)
expect(runtime.contents).not.toContain("printf '%s\\n' \"${MCP_PLAYWRIGHT_CDP_ENDPOINT:-http://127.0.0.1:9223}\"")
expect(runtime.contents).toContain("docker_git_wait_for_playwright_cdp()")
expect(runtime.contents).toContain("MCP_PLAYWRIGHT_ENABLE=0")
expect(runtime.contents).not.toContain("\\${MCP_PLAYWRIGHT_ENABLE:-0}")
expect(dockerfile.contents).toContain(
"COPY Dockerfile.browser mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/"
"COPY Dockerfile.browser docker-git-cdp-guard mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/"
)
expect(dockerfile.contents).toContain("ARG PLAYWRIGHT_MCP_VERSION=0.0.75")
expect(dockerfile.contents).toContain("RUN npm install -g \"@playwright/mcp@${PLAYWRIGHT_MCP_VERSION}\"")
expect(dockerfile.contents).toContain("CDP_ENDPOINT=\"http://127.0.0.1:9223\"")
expect(dockerfile.contents).not.toContain("CDP_ENDPOINT=\"${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}\"")
expect(dockerfile.contents).toContain("MCP_PLAYWRIGHT_CDP_TIMEOUT=\"${MCP_PLAYWRIGHT_CDP_TIMEOUT:-60000}\"")
expect(runtime.contents).toContain("invalid MCP_PLAYWRIGHT_READY_ATTEMPTS")
expect(runtime.contents).toContain("while (( attempt <= attempts )); do")
Expand Down
1 change: 0 additions & 1 deletion packages/lib/src/core/templates-entrypoint/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ CODEX_AUTO_UPDATE="\${CODEX_AUTO_UPDATE:-1}"
AGENT_MODE="\${AGENT_MODE:-}"
AGENT_AUTO="\${AGENT_AUTO:-}"
MCP_PLAYWRIGHT_ENABLE="\${MCP_PLAYWRIGHT_ENABLE:-${config.enableMcpPlaywright ? "1" : "0"}}"
MCP_PLAYWRIGHT_CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
MCP_PLAYWRIGHT_ISOLATED="\${MCP_PLAYWRIGHT_ISOLATED:-0}"
MCP_PLAYWRIGHT_CDP_GUARD="\${MCP_PLAYWRIGHT_CDP_GUARD:-1}"
MCP_PLAYWRIGHT_BLOCK_BROWSER_CLOSE="\${MCP_PLAYWRIGHT_BLOCK_BROWSER_CLOSE:-1}"
Expand Down
4 changes: 0 additions & 4 deletions packages/lib/src/core/templates-entrypoint/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ EOF
chown 1000:1000 "$CODEX_CONFIG_FILE" || true
fi

if [[ -z "$MCP_PLAYWRIGHT_CDP_ENDPOINT" ]]; then
MCP_PLAYWRIGHT_CDP_ENDPOINT="http://127.0.0.1:9223"
fi

# Replace the docker-git Playwright block to allow upgrades via --force without manual edits.
if grep -q "^\[mcp_servers\.playwright" "$CODEX_CONFIG_FILE" 2>/dev/null; then
awk '
Expand Down
12 changes: 11 additions & 1 deletion packages/lib/src/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import {
} from "./templates/docker-compose.js"
import { renderDockerfile } from "./templates/dockerfile.js"
import { renderPlaywrightBrowserRuntime } from "./templates/playwright-browser-runtime.js"
import { renderPlaywrightBrowserDockerfile, renderPlaywrightStartExtra } from "./templates/playwright.js"
import {
renderPlaywrightBrowserDockerfile,
renderPlaywrightCdpGuard,
renderPlaywrightStartExtra
} from "./templates/playwright.js"

export type FileSpec =
| { readonly _tag: "File"; readonly relativePath: string; readonly contents: string; readonly mode?: number }
Expand Down Expand Up @@ -64,6 +68,12 @@ export const planFiles = (
const maybePlaywrightFiles = config.enableMcpPlaywright
? ([
{ _tag: "File", relativePath: "Dockerfile.browser", contents: renderPlaywrightBrowserDockerfile() },
{
_tag: "File",
relativePath: "docker-git-cdp-guard",
contents: renderPlaywrightCdpGuard(),
mode: 0o755
},
{
_tag: "File",
relativePath: "mcp-playwright-start-extra.sh",
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/core/templates/docker-compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ const buildPlaywrightFragments = (
maybeDependsOn: "",
maybeDockerSocketMount: renderOptionalDockerSocketMount(options.enableLocalDockerSocket),
maybePlaywrightEnv:
` MCP_PLAYWRIGHT_ENABLE: "1"\n MCP_PLAYWRIGHT_CDP_ENDPOINT: "http://127.0.0.1:9223"\n DOCKER_GIT_PROJECT_CONTAINER_NAME: "${config.containerName}"\n DOCKER_GIT_BROWSER_CONTAINER_NAME: "${browserContainerName}"\n DOCKER_GIT_BROWSER_IMAGE_NAME: "${browserImageName}"\n DOCKER_GIT_BROWSER_VOLUME_NAME: "${browserVolumeName}"\n${
` MCP_PLAYWRIGHT_ENABLE: "1"\n DOCKER_GIT_PROJECT_CONTAINER_NAME: "${config.containerName}"\n DOCKER_GIT_BROWSER_CONTAINER_NAME: "${browserContainerName}"\n DOCKER_GIT_BROWSER_IMAGE_NAME: "${browserImageName}"\n DOCKER_GIT_BROWSER_VOLUME_NAME: "${browserVolumeName}"\n${
renderBrowserLimitEnv("DOCKER_GIT_BROWSER_CPU_LIMIT", resourceLimits?.cpuLimit)
}${renderBrowserLimitEnv("DOCKER_GIT_BROWSER_RAM_LIMIT", resourceLimits?.ramLimit)}`,
maybeBrowserVolume: ` ${browserVolumeName}:`
Expand Down
14 changes: 6 additions & 8 deletions packages/lib/src/core/templates/dockerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,14 @@ for arg in "$@"; do
esac
done

CDP_ENDPOINT="\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-}"
if [[ -z "$CDP_ENDPOINT" ]]; then
CDP_ENDPOINT="http://127.0.0.1:9223"
fi
CDP_ENDPOINT="http://127.0.0.1:9223"

# CHANGE: keep MCP initialize independent from nested browser readiness
# WHY: Codex starts MCP servers during boot; blocking here closes stdio before initialize when CDP is slow.
# QUOTE(issue-319): "handshaking with MCP server failed: connection closed: initialize response"
# REF: issue-319
# SOURCE: https://playwright.dev/mcp/configuration/options
# FORMAT THEOREM: guarded_cdp(endpoint) -> mcp_stdio_ready_before_browser_connection
# FORMAT THEOREM: guarded_cdp(fixed_nested_browser_endpoint) -> mcp_stdio_ready_before_browser_connection
# PURITY: SHELL
# INVARIANT: guarded mode never exits before handing stdio to playwright-mcp
# COMPLEXITY: O(1)
Expand All @@ -194,7 +191,8 @@ if [[ "\${MCP_PLAYWRIGHT_ISOLATED:-0}" == "1" ]]; then
EXTRA_ARGS+=(--isolated)
fi

# Guarded endpoints are stable HTTP CDP endpoints. Passing the HTTP URL lets Playwright MCP
# The guarded endpoint is the nested browser opened by docker-git Open browser.
# Passing the fixed HTTP URL lets Playwright MCP
# re-resolve /json/version instead of pinning itself to one stale /devtools/browser/<id>.
if [[ "$MCP_PLAYWRIGHT_CDP_GUARD" == "1" ]]; then
exec playwright-mcp --cdp-endpoint "$CDP_ENDPOINT" --cdp-timeout "$MCP_PLAYWRIGHT_CDP_TIMEOUT" "\${EXTRA_ARGS[@]}" "$@"
Expand Down Expand Up @@ -239,8 +237,8 @@ RUN chmod +x /usr/local/bin/docker-git-playwright-mcp`
const renderDockerfilePlaywrightRuntime = (config: TemplateConfig): string =>
config.enableMcpPlaywright
? `# docker-git nested Playwright browser runtime context
COPY Dockerfile.browser mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
RUN chmod +x /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
COPY Dockerfile.browser docker-git-cdp-guard mcp-playwright-start-extra.sh docker-git-browser-runtime.sh /opt/docker-git/browser/
RUN chmod +x /opt/docker-git/browser/docker-git-cdp-guard /opt/docker-git/browser/mcp-playwright-start-extra.sh /opt/docker-git/browser/docker-git-browser-runtime.sh`
: ""

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ docker_git_disable_playwright_mcp() {
}

docker_git_playwright_cdp_endpoint() {
printf '%s\\n' "\${MCP_PLAYWRIGHT_CDP_ENDPOINT:-http://127.0.0.1:9223}"
printf '%s\\n' "http://127.0.0.1:9223"
}

docker_git_fetch_playwright_cdp_version() {
Expand Down
Loading
Loading