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
2 changes: 2 additions & 0 deletions packages/app/src/docker-git/cli/usage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Container runtime env (set via .orch/env/project.env):
DOCKER_GIT_ZSH_AUTOSUGGEST_STRATEGY=... Suggestion sources (default: history completion)
MCP_PLAYWRIGHT_ISOLATED=1|0 Isolated browser contexts (recommended for many Codex; default: 1)
MCP_PLAYWRIGHT_CDP_ENDPOINT=http://... Override CDP endpoint (default: http://dg-<repo>-browser:9223)
MCP_PLAYWRIGHT_RETRY_ATTEMPTS=<n> Retry attempts for browser sidecar startup wait (default: 10)
MCP_PLAYWRIGHT_RETRY_DELAY=<seconds> Delay between retry attempts (default: 2)

Auth providers:
github, gh GitHub CLI auth (tokens saved to env file)
Expand Down
33 changes: 32 additions & 1 deletion packages/lib/src/core/templates/dockerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,39 @@ if [[ -z "$CDP_ENDPOINT" ]]; then
CDP_ENDPOINT="http://__SERVICE_NAME__-browser:9223"
fi

# CHANGE: add retry logic for browser sidecar startup wait
# WHY: the browser container may take time to initialize, causing MCP server to fail on first attempt
# QUOTE(issue-123): "Почему MCP сервер лежит с ошибкой?"
# REF: issue-123
# SOURCE: n/a
# FORMAT THEOREM: forall t in [1..max_attempts]: retry(t) -> eventually(cdp_ready) OR timeout_error
# PURITY: SHELL
# INVARIANT: script exits only after cdp_ready OR all retries exhausted
# COMPLEXITY: O(max_attempts * timeout_per_attempt)
MCP_PLAYWRIGHT_RETRY_ATTEMPTS="\${MCP_PLAYWRIGHT_RETRY_ATTEMPTS:-10}"
MCP_PLAYWRIGHT_RETRY_DELAY="\${MCP_PLAYWRIGHT_RETRY_DELAY:-2}"

fetch_cdp_version() {
curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version" 2>/dev/null
}

JSON=""
for attempt in $(seq 1 "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS"); do
if JSON="$(fetch_cdp_version)"; then
break
fi
if [[ "$attempt" -lt "$MCP_PLAYWRIGHT_RETRY_ATTEMPTS" ]]; then
echo "docker-git-playwright-mcp: waiting for browser sidecar (attempt $attempt/$MCP_PLAYWRIGHT_RETRY_ATTEMPTS)..." >&2
sleep "$MCP_PLAYWRIGHT_RETRY_DELAY"
fi
done

if [[ -z "$JSON" ]]; then
echo "docker-git-playwright-mcp: failed to connect to CDP endpoint $CDP_ENDPOINT after $MCP_PLAYWRIGHT_RETRY_ATTEMPTS attempts" >&2
exit 1
fi

# kechangdev/browser-vnc binds Chromium CDP on 127.0.0.1:9222; it also host-checks HTTP requests.
JSON="$(curl -sSf --connect-timeout 3 --max-time 10 -H 'Host: 127.0.0.1:9222' "\${CDP_ENDPOINT%/}/json/version")"
WS_URL="$(printf "%s" "$JSON" | node -e 'const fs=require("fs"); const j=JSON.parse(fs.readFileSync(0,"utf8")); process.stdout.write(j.webSocketDebuggerUrl || "")')"
if [[ -z "$WS_URL" ]]; then
echo "docker-git-playwright-mcp: webSocketDebuggerUrl missing" >&2
Expand Down
9 changes: 9 additions & 0 deletions packages/lib/tests/usecases/mcp-playwright.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@ describe("enableMcpPlaywrightProjectFiles", () => {
const dockerfileAfter = yield* _(fs.readFileString(path.join(outDir, "Dockerfile")))
expect(dockerfileAfter).toContain("@playwright/mcp")

// CHANGE: verify retry logic is included in docker-git-playwright-mcp wrapper
// WHY: issue-123 requires retry mechanism to handle browser sidecar startup delays
// QUOTE(issue-123): "Почему MCP сервер лежит с ошибкой?"
// REF: issue-123
expect(dockerfileAfter).toContain("MCP_PLAYWRIGHT_RETRY_ATTEMPTS")
expect(dockerfileAfter).toContain("MCP_PLAYWRIGHT_RETRY_DELAY")
expect(dockerfileAfter).toContain("fetch_cdp_version()")
expect(dockerfileAfter).toContain("waiting for browser sidecar")

const browserDockerfileExists = yield* _(fs.exists(path.join(outDir, "Dockerfile.browser")))
const startExtraExists = yield* _(fs.exists(path.join(outDir, "mcp-playwright-start-extra.sh")))
expect(browserDockerfileExists).toBe(true)
Expand Down
Loading