Skip to content

Run the MCP Apps sandbox smokes locally in Firefox and WebKit, not just Chromium #2086

Description

@cliffhall

⚠️ Decision revised during implementation — read this first

This issue was filed asking for a GitHub CI matrix across Chromium, Firefox and WebKit. Two findings during implementation changed that, and @cliffhall made the call:

  1. WebKit fails the two App smokes for reasons nobody has identified. It does not reproduce in real Safari, and an isolated repro of the mechanism it was first blamed on did not reproduce it under Playwright's WebKit either — so it reads as a property of that build, not a browser bug, and chasing it was judged not worth the effort. (An earlier revision of this issue and of SSE stream tail is not delivered under Playwright's WebKit, blocking the WebKit CI matrix entry (plus a cross-chunk parseSSE bug) #2132 wrongly described it as a Safari bug; that was retracted.)
  2. A Firefox CI job was built, trialled, and removed. It was cheap — ~2 min, parallel with the 15-minute build job, so zero added wall-clock — but across a dozen runs Firefox never once disagreed with Chromium. It spent runner minutes on every push from every branch to re-confirm a result already in hand, and carried a real flake surface (playwright install --with-deps runs apt-get update, which fails whenever a third-party repo in the runner image breaks).

Firefox moved into the local pre-push gate (npm run ci) instead of GitHub CI. The check lands in front of a human about to push a change they can still reason about, and costs once per push rather than once per CI run.

The Acceptance criteria below are updated to match. The original CI-matrix wording is kept struck through, so the change is visible rather than quietly rewritten.

Problem

Every browser-based check in this repo runs in Chromium only:

Check Browser
smoke:web:browser chromium.launch(), npx playwright install chromium
smoke:web:app same
smoke:web:elicit same
Storybook play functions (ci:storybook) browser: "chromium" (clients/web/vite.config.ts)
GitHub workflow npx playwright install --with-deps chromium (.github/workflows/main.yml)

Firefox and WebKit appear nowhere — not in a script, not in the vitest browser config, not in the workflow.

That is fine for most of the web client, whose behavior is React and Mantine. It is not fine for the MCP Apps sandbox, which is the one part of the Inspector built out of the browser primitives that actually diverge between engines:

  • a CSP <meta> injected as the first <head> child of a document assigned to srcdoc — CSP inheritance into srcdoc / about:blank is historically divergent across engines;
  • a nested sandboxed iframe (trusted proxy → untrusted app) and the sandbox attribute's semantics;
  • a Permissions-Policy allow attribute built from the resource's permissions;
  • postMessage origin discipline across those two frames.

A regression in any of those is invisible to the current suite unless it also reproduces in Chromium.

Why the unit tests and Storybook do not cover this

sandbox-csp.test.ts tests string construction, not enforcement. It covers which sources survive approveCspSources and what policy text buildSandboxCspPolicy emits. That is environment-independent by construction — it would pass identically on an engine that ignores <meta> CSP entirely.

Storybook cannot cover it at all, in any browser. All three App stories (AppRenderer, AppsScreen, AppElicitationHost) point the iframe at PLACEHOLDER_SANDBOX = "data:text/html,…" and hand the renderer a mock bridge object. No sandbox proxy is served, no srcdoc assignment happens, no CSP is built or applied, and no ui/initialize handshake runs. sandbox-csp.ts is imported by exactly two things in the tree — its own unit test and createAppBridgeFactory.ts — so no story reaches it. Adding engines to the Storybook run would broaden coverage of modal chrome and focus behavior, which is worth something, but it is not this.

So the only place the sandbox is genuinely exercised is smoke:web:app and smoke:web:elicit, and both are Chromium-only.

Decision

Chromium, Firefox and WebKit. That is now the supported set; the work below is what makes it true rather than assumed.

Note Playwright's WebKit is a WebKit build, not Safari. It is close enough to catch engine-level CSP and iframe divergence, and not close enough to certify Safari specifically — worth saying so in whatever doc records the matrix, so nobody over-trusts a green run.

Scope

  • Parameterize the browser in the two smokes that actually load an app — scripts/smoke-web-app.mjs and scripts/smoke-web-elicitation.mjs. Both already isolate the launch in a loadChromium() helper, so this is a browser-name parameter (env var) plus a rename, not a restructure. Its error message names the missing-system-libraries fix and should name the right engine.
  • Decide whether smoke:web:browser joins them. It asserts a clean first paint of the bundle, which is a real cross-engine question (a syntax/API level the bundle targets) but not a CSP one — cheap to include, so probably yes.
  • Add the CI matrix in .github/workflows/main.yml, and install the engines it needs (npx playwright install --with-deps chromium firefox webkit). WebKit is the large download; expect the install step to dominate the added time.
  • Record the supported set somewhere a reader will find it — the root README's testing section is the natural home, next to the smoke table.
  • Consider adding firefox / webkit instances to the Storybook browser config as a separate decision. It covers a different (and much larger) surface, so it should be judged on its own cost rather than riding along with this.

Acceptance criteria

Revised — see the banner at the top.

  • smoke:web:app and smoke:web:elicit run in Chromium, Firefox and WebKit in CIsmoke:web:browser, smoke:web:app and smoke:web:elicit run in Chromium and Firefox in the local pre-push gate (npm run ci); GitHub CI stays Chromium-only; WebKit is reachable on demand. A failure names which engine failed.
  • Running them locally without a browser installed fails with an actionable message naming that engine's playwright install --with-deps <engine>. (unchanged)
  • The supported browser set is written down, with the Playwright-WebKit-is-not-Safari caveat — including that the caveat cuts both ways: a red WebKit run is no more a Safari indictment than a green one is a guarantee.
  • No change to what the smokes assert — this is an engine change, not new assertions. (unchanged)
  • The reasoning for keeping Firefox out of GitHub CI is recorded in AGENTS.md, aimed at how it would come back: not on the general argument that cross-engine coverage is good — that was accepted, and the pre-push gate serves it — but on evidence, i.e. a cross-engine regression that reached v2/main because someone skipped the gate.

Explicitly out of scope now

Notes

Filed out of the review of #2083 (app-rendered form elicitations), which added smoke:web:elicit and followed the existing Chromium-only convention rather than introducing a matrix in a feature PR. The gap is not specific to that feature: it is the MCP Apps sandbox path generally, shared with the Apps tab and the #1565 sandbox hardening, so fixing it narrowly for elicitation would leave the larger surface uncovered.

Metadata

Metadata

Assignees

Labels

enhancementNew feature requestmcp-appsMCP Apps relatedv2Issues and PRs for v2

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions