diff --git a/.devcontainer/claude-sandbox/init-firewall.sh b/.devcontainer/claude-sandbox/init-firewall.sh index 2cf6ce3..6f98f4d 100755 --- a/.devcontainer/claude-sandbox/init-firewall.sh +++ b/.devcontainer/claude-sandbox/init-firewall.sh @@ -69,7 +69,8 @@ for domain in \ "update.code.visualstudio.com" \ "auth.openai.com" \ "api.openai.com" \ - "chatgpt.com"; do + "chatgpt.com" \ + "mcp.mdn.mozilla.net"; do echo "Resolving $domain..." ips=$(dig +noall +answer A "$domain" | awk '$4 == "A" {print $5}') if [ -z "$ips" ]; then diff --git a/.github/workflows/build-claude-code.yml b/.github/workflows/build-claude-code.yml index b58ad01..3f3a829 100644 --- a/.github/workflows/build-claude-code.yml +++ b/.github/workflows/build-claude-code.yml @@ -75,19 +75,19 @@ jobs: matrix: include: - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" runner: ubuntu-24.04-arm arch: arm64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" + verify-command: "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" runner: ubuntu-24.04-arm arch: arm64 runs-on: ${{ matrix.runner }} diff --git a/.github/workflows/build-ralphex-fe.yml b/.github/workflows/build-ralphex-fe.yml index 4859849..78702d4 100644 --- a/.github/workflows/build-ralphex-fe.yml +++ b/.github/workflows/build-ralphex-fe.yml @@ -7,6 +7,7 @@ on: paths: - 'ralphex-fe/Dockerfile' - 'ralphex-fe/*.sh' + - 'ralphex-fe/managed-settings.json' workflow_dispatch: permissions: @@ -107,5 +108,7 @@ jobs: test -f /srv/init.sh && chromium --no-sandbox --version && test -x /usr/bin/chromium && + test -r /etc/claude-code/managed-settings.json && + jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && echo 'All checks passed' " diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cfeeb71..60ddb04 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,12 +117,12 @@ jobs: add_image "claude-code" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ + "bun --version || true && claude --version && mise --version && zsh --version && gh --version && gh stack --version && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && printenv AGENT_BROWSER_EXECUTABLE_PATH | grep -qx /usr/bin/chromium && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ "default" add_image "claude-code-sandbox" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ + "claude --version && mise --version && zsh --version && gh --version && gh stack --version && which iptables && rtk --version && ralphex --version && test -x /usr/local/bin/patch-playwright-mcp && test -r /etc/claude-code/managed-settings.json && jq -r '.hooks.SessionStart[0].hooks[0].command' /etc/claude-code/managed-settings.json | grep -qx /usr/local/bin/patch-playwright-mcp && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/ && zsh -ic 'typeset -p ZSH_THEME' | grep -q powerlevel10k/powerlevel10k && stat -c %U /home/node/.local/share | grep -qx node && stat -c %U /home/node/.config/gh | grep -qx node" \ "sandbox" fi @@ -144,7 +144,7 @@ jobs: add_image "ralphex-fe" \ "ralphex-fe" \ "ralphex-fe/Dockerfile" \ - "bun --version && hugo version && go version && docker --version && /srv/ralphex --version && rtk --version" + "bun --version && hugo version && go version && docker --version && /srv/ralphex --version && rtk --version && test -r /etc/claude-code/managed-settings.json && jq -r '.managedMcpServers.mdn.url' /etc/claude-code/managed-settings.json | grep -qx https://mcp.mdn.mozilla.net/" fi if [ "$INCLUDES" = "[]" ]; then diff --git a/claude-code/.devcontainer/Dockerfile b/claude-code/.devcontainer/Dockerfile index 322a2f8..6d098cf 100644 --- a/claude-code/.devcontainer/Dockerfile +++ b/claude-code/.devcontainer/Dockerfile @@ -186,7 +186,8 @@ COPY --chmod=0755 patch-playwright-mcp.sh /usr/local/bin/patch-playwright-mcp # ── Claude Code managed settings ───────────────────────────────────────────── # Image-policy settings at the Linux managed location (highest precedence, outside -# any volume mount). Wires patch-playwright-mcp as a SessionStart hook. See #98, #101. +# any volume mount). Wires patch-playwright-mcp as a SessionStart hook (#98, #101) +# and provides the MDN MCP server. managedMcpServers needs Claude Code >= 2.1.259. # # /etc/claude-code is created explicitly: BuildKit applies COPY --chmod to parent # dirs it auto-creates, leaving 0644 — not traversable. diff --git a/claude-code/.devcontainer/managed-settings.json b/claude-code/.devcontainer/managed-settings.json index e95097e..72c5d3c 100644 --- a/claude-code/.devcontainer/managed-settings.json +++ b/claude-code/.devcontainer/managed-settings.json @@ -10,5 +10,14 @@ ] } ] + }, + "managedMcpServers": { + "mdn": { + "type": "http", + "url": "https://mcp.mdn.mozilla.net/", + "headers": { + "X-Moz-1st-Party-Data-Opt-Out": "1" + } + } } } diff --git a/claude-code/README.md b/claude-code/README.md index 5050c6d..693ecdf 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -29,6 +29,12 @@ Projects consume these pre-built images and control their own tool versions via **Sandbox-only:** iptables, ipset, iproute2, dnsutils, aggregate, firewall sudo rule +### MDN MCP server + +The image provides Mozilla's [MDN MCP server](https://developer.mozilla.org/en-US/mcp) (web platform docs and browser compatibility data) to every session through `managedMcpServers` in `/etc/claude-code/managed-settings.json` — no per-project setup. It sends `X-Moz-1st-Party-Data-Opt-Out: 1`, Mozilla's documented opt-out from the query logging they do while the server is experimental. + +Requires Claude Code >= 2.1.259; earlier clients ignore the key. `claude mcp remove` refuses it, but each developer can turn it off for themselves in `/mcp` under **Managed MCPs**. Sandbox users must allowlist `mcp.mdn.mozilla.net` in their firewall script. + ## Multi-platform Support Both variants are built for: @@ -125,7 +131,7 @@ To remove a plugin in your project, delete its entry from the local `init-plugin Default-deny iptables firewall. The image provides the packages and sudo rule; the project provides this script via bind mount. Customize the domain allowlist for your project. -See the [repo's own sandbox firewall script](../.devcontainer/claude-sandbox/init-firewall.sh) for a complete example. The script should: preserve Docker internal DNS rules, allow DNS/SSH/localhost, fetch GitHub IP ranges via `curl -s https://api.github.com/meta`, resolve additional allowed domains (npm, Anthropic API, VS Code marketplace, etc.) via `dig`, set default DROP policies, allow established connections and the ipset allowlist, then verify by confirming `example.com` is blocked and `api.github.com` is reachable. +See the [repo's own sandbox firewall script](../.devcontainer/claude-sandbox/init-firewall.sh) for a complete example. The script should: preserve Docker internal DNS rules, allow DNS/SSH/localhost, fetch GitHub IP ranges via `curl -s https://api.github.com/meta`, resolve additional allowed domains (npm, Anthropic API, VS Code marketplace, `mcp.mdn.mozilla.net` for the MDN MCP server, etc.) via `dig`, set default DROP policies, allow established connections and the ipset allowlist, then verify by confirming `example.com` is blocked and `api.github.com` is reachable. Mark as executable and ensure git tracks the executable bit: diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index 336fc8c..c955c52 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -168,6 +168,12 @@ ARG CLAUDE_CODE_VERSION=2.1.280 RUN --mount=type=cache,target=/root/.npm \ npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION} +# ── Claude Code managed settings ───────────────────────────────────────────── +# Provides the MDN MCP server to every session, including `claude -p` runs. +# managedMcpServers needs Claude Code >= 2.1.259; earlier clients ignore it. +RUN mkdir -p /etc/claude-code +COPY --chown=root:root --chmod=0644 managed-settings.json /etc/claude-code/managed-settings.json + # ── Bun ────────────────────────────────────────────────────────────────────── ENV BUN_INSTALL=/usr/local/bun ENV PATH="$BUN_INSTALL/bin:$PATH" diff --git a/ralphex-fe/README.md b/ralphex-fe/README.md index 7f08c7b..c71876c 100644 --- a/ralphex-fe/README.md +++ b/ralphex-fe/README.md @@ -11,20 +11,26 @@ This is a standalone image, not a devcontainer. | Tool | Version | |------|---------| | Node.js | 24 (from base image) | -| Bun | 1.3.9 | -| Hugo Extended | 0.156.0 | +| Bun | 1.4.2 | +| Hugo Extended | 0.166.0 | | Go | for Hugo Modules | | Python 3 | system | | Playwright + Chromium | native Debian | -| Claude Code CLI | 2.1.216 (pinned) | -| RTK | 0.43.0 (pinned) | -| Ralphex | 1.6.0 (pinned) | -| Git, ripgrep, jq, curl, wget | system | +| Claude Code CLI | 2.1.280 (pinned) | +| RTK | 0.49.0 (pinned) | +| Ralphex | 1.7.0 (pinned) | +| Git, ripgrep, jq, curl | system | All pinned versions live as `ARG`s in the Dockerfile and are kept current by Renovate — see [Automatic Rebuilds](#automatic-rebuilds). This image has no `mise`, so its Bun and Hugo are image-level versions rather than per-project ones; Renovate tracks them like everything else. +### MDN MCP server + +The image provides Mozilla's [MDN MCP server](https://developer.mozilla.org/en-US/mcp) (web platform docs and browser compatibility data) through `managedMcpServers` in `/etc/claude-code/managed-settings.json`. Ralphex drives Claude non-interactively; a local managed settings file is read at session start, so `claude -p` runs get it too — the unattended runs where a hallucinated DOM or CSS API would otherwise land in a commit unreviewed. + +It sends `X-Moz-1st-Party-Data-Opt-Out: 1`, Mozilla's documented opt-out from the query logging they do while the server is experimental. Requires Claude Code >= 2.1.259; earlier clients ignore the key. + ## Usage ### Via ralphex docker-wrapper @@ -69,7 +75,7 @@ This image is built for multiple architectures: ## Image Tags - `latest` — most recent build -- `bun{VERSION}-hugo{VERSION}` — version-specific tag (e.g., `bun1.3.9-hugo0.156.0`) +- `bun{VERSION}-hugo{VERSION}` — version-specific tag (e.g., `bun1.4.2-hugo0.166.0`) Note: this image deviates from the standalone convention of a single primary version tag because it bundles multiple independently-versioned tools. diff --git a/ralphex-fe/managed-settings.json b/ralphex-fe/managed-settings.json new file mode 100644 index 0000000..8f4cffe --- /dev/null +++ b/ralphex-fe/managed-settings.json @@ -0,0 +1,11 @@ +{ + "managedMcpServers": { + "mdn": { + "type": "http", + "url": "https://mcp.mdn.mozilla.net/", + "headers": { + "X-Moz-1st-Party-Data-Opt-Out": "1" + } + } + } +}