From eeaf0b9197a593c456de83363fd09c3fffbf5778 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:14:50 -0600 Subject: [PATCH 1/2] feat(images): provide MDN MCP server via managed settings Mozilla's MDN MCP server (mcp.mdn.mozilla.net) gives Claude current web platform docs and browser compatibility data instead of whatever was in its training set. Ship it from the image so consumers get it without per-project setup. managedMcpServers in /etc/claude-code/managed-settings.json is the only seam that works here. init-plugins.sh is a template consumers copy into their own repos, so editing it would reach new consumers only; the image flows to existing ones through the Renovate rebuild + image-pull channel. A user-scope config baked at build time would be shadowed by the volume mounted at /home/node/.claude, and a `claude mcp add` in postCreateCommand would race the extension's OAuth flow, which devcontainer.json warns against. claude-code already had the managed settings file for the Playwright SessionStart hook, so it gains the key only. ralphex-fe gains the file plus mkdir/COPY; it runs as root throughout and drops privileges via gosu, so it needs no USER guarding. Managed settings are read at session start, so ralphex's non-interactive `claude -p` runs get the server too. Both send X-Moz-1st-Party-Data-Opt-Out: 1, Mozilla's documented opt-out from the query logging they do while the server is experimental. Developers can still turn it off per-user in /mcp under Managed MCPs. The sandbox firewall is default-DROP, and the settings file is read locally, so without an allowlist entry the server would look configured and healthy while every tool call was dropped. Adds mcp.mdn.mozilla.net to the reference firewall script the claude-code README points at. CI asserts the URL in both images, following the jq | grep -qx idiom already used for the Playwright hook. Also refreshes ralphex-fe's tool table, which had drifted from its Dockerfile across several Renovate bumps (Bun, Hugo, Claude Code, rtk, ralphex). Requires Claude Code >= 2.1.259; both images pin 2.1.276. --- .devcontainer/claude-sandbox/init-firewall.sh | 3 ++- .github/workflows/build-claude-code.yml | 8 ++++---- .github/workflows/build-ralphex-fe.yml | 3 +++ claude-code/.devcontainer/Dockerfile | 3 ++- claude-code/.devcontainer/managed-settings.json | 9 +++++++++ claude-code/README.md | 8 +++++++- ralphex-fe/Dockerfile | 6 ++++++ ralphex-fe/README.md | 16 +++++++++++----- ralphex-fe/managed-settings.json | 11 +++++++++++ 9 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 ralphex-fe/managed-settings.json 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 1d4c6b1..440f307 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 && 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" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --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" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code - verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --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" + verify-command: "bun --version || true && claude --version && mise --version && zsh --version && gh --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" runner: ubuntu-24.04-arm arch: arm64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && zsh --version && gh --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" + verify-command: "claude --version && mise --version && zsh --version && gh --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" runner: ubuntu-24.04 arch: amd64 - image-suffix: claude-code-sandbox - verify-command: "claude --version && mise --version && zsh --version && gh --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" + verify-command: "claude --version && mise --version && zsh --version && gh --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" 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/claude-code/.devcontainer/Dockerfile b/claude-code/.devcontainer/Dockerfile index bac7289..ee9b630 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 fc00253..eddc464 100644 --- a/claude-code/README.md +++ b/claude-code/README.md @@ -28,6 +28,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: @@ -121,7 +127,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 4e6eda6..9931aaa 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -168,6 +168,12 @@ ARG CLAUDE_CODE_VERSION=2.1.276 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 588b702..781f0e7 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) | +| Claude Code CLI | 2.1.276 (pinned) | +| RTK | 0.49.0 (pinned) | +| Ralphex | 1.7.0 (pinned) | | Git, ripgrep, jq, curl, wget | 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 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" + } + } + } +} From d136f98ebe1523bea2a00cbc602c8702616dd155 Mon Sep 17 00:00:00 2001 From: Serge Gatezh <2880401+gatezh@users.noreply.github.com> Date: Fri, 18 Sep 2026 13:19:06 -0600 Subject: [PATCH 2/2] fix(ci): assert MDN server in PR-time verify; drop wget from ralphex-fe tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit added the MDN assertion only to the publish-time verify commands in build-claude-code.yml and build-ralphex-fe.yml. ci.yml runs its own verify at PR time and already carried the sibling managed-settings assertion for the Playwright hook, so a regression that dropped managedMcpServers would have gone green on the PR and failed only after merge. Adds the assertion to all three ci.yml entries. ralphex-fe's tool table listed wget, which the final image does not contain — the apt block installs curl but not wget, and the wget calls live in the alpine download stages. Verified against the published image. procps in the claude-code table was checked the same way and is present, so that row stands. --- .github/workflows/ci.yml | 6 +++--- ralphex-fe/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61a8355..31d3394 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 && 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" \ + "bun --version || true && claude --version && mise --version && zsh --version && gh --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" \ "default" add_image "claude-code-sandbox" \ "claude-code/.devcontainer" \ "claude-code/.devcontainer/Dockerfile" \ - "claude --version && mise --version && zsh --version && gh --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" \ + "claude --version && mise --version && zsh --version && gh --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" \ "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/ralphex-fe/README.md b/ralphex-fe/README.md index 781f0e7..8b12555 100644 --- a/ralphex-fe/README.md +++ b/ralphex-fe/README.md @@ -19,7 +19,7 @@ This is a standalone image, not a devcontainer. | Claude Code CLI | 2.1.276 (pinned) | | RTK | 0.49.0 (pinned) | | Ralphex | 1.7.0 (pinned) | -| Git, ripgrep, jq, curl, wget | system | +| 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