ui:// runner resource.App (MCP-app) communication.CapabilityRequest and awaits the result.window.__mcpConformance (listTests / start / poll / resolve). The Runner polls it (via frame.evaluate), dispatches each request to the active Host, and resolves it back — the suite pulls, the Runner polls.BrowserHost today; a desktop host later). It automates the real host — clicks, dialogs, conversation checks — and returns each result.report.ts renders the results matrix (Results); see also the architecture.The durable seam is the window.__mcpConformance protocol. The fragile part is the per-host DOM the driver must click — see the last section.
Asserted directly from inside the iframe — read a host value, proxy a tool call, trigger a CSP violation. No human, runs headless.
| lifecycle/initialize-capabilities | MUST | 2026-01-26 · L624 |
| lifecycle/tool-input | MUST | 2026-01-26 · L1106 |
| lifecycle/tool-input-partial-stop | MUST | 2026-01-26 · L1136 |
| lifecycle/tool-result | MUST | 2026-01-26 · L1155 |
| tools/proxy-call | MUST | 2026-01-26 · L487 |
| visibility/app-tool-call-guard | MUST | 2026-01-26 · L401 |
| display/return-resulting-mode | MUST | 2026-01-26 · L787 |
| dimensions/listen-size-changed | MUST | 2026-01-26 · L718 |
| security/sandbox-distinct-origin | MUST | 2026-01-26 · L474 |
| security/sandbox-permissions | MUST | 2026-01-26 · L475 |
| security/csp-construct-from-domains | MUST | 2026-01-26 · L479 |
| security/csp-allow-declared | MUST | 2026-01-26 · L479 |
| display/no-undeclared-mode | MUST NOT | 2026-01-26 · L786 |
| security/csp-no-loosening | MUST NOT | 2026-01-26 · L286 |
| context/initialize-hostcontext | SHOULD | 2026-01-26 · L533 |
| context/light-dark | SHOULD | 2026-01-26 · L895 |
| display/unavailable-returns-current | SHOULD | 2026-01-26 · L788 |
| capabilities/server-passthrough | SHOULD | 2026-01-26 · L487 |
| context/theme-variables | MAY | 2026-01-26 · L793 |
| context/theme-fonts | MAY | 2026-01-26 · L922 |
| capabilities/content-modalities | MAY | draft · L663 |
| security/sandbox-proxy-required | MUST | 2026-01-26 · L472 |
The app triggers an action, then a host notification or callback settles the test automatically — no verdict to give.
| context/context-changed | MAY | 2026-01-26 · L1229 |
| app-tools/call ask-agent | MAY | draft · L1243 |
The effect happens outside the iframe (a permission dialog, a new tab, a conversation turn); a human or the Playwright driver confirms it from the host surface.
| visibility/app-tool-hidden ask-agent | MUST NOT | 2026-01-26 · L400 |
| links/open-external | SHOULD | 2026-01-26 · L996 |
| messages/add-to-conversation | SHOULD | 2026-01-26 · L1033 |
| model-context/provide-future-turns ask-agent | SHOULD | 2026-01-26 · L1097 |
| sampling/create-message | SHOULD | draft · L534 |
| download-file/confirm | SHOULD | draft · L1128 |
| security/iframe-sandboxed | MUST | 2026-01-26 · L1698 |
| security/csp-audit-log | SHOULD | 2026-01-26 · L287 |
| model-context/last-wins ask-agent | SHOULD | 2026-01-26 · L1101 |
ask-agent is a cross-cutting tag: the app sends a ui/message asking the model to act (call a tool, list tools, recall context), so the result depends on the agent — not just the host bridge.
The driver clicks real product UIs, not an API, so it hard-codes host-specific selectors and flows. These are brittle to host redesigns.
| Concern | chatgpt | claude | alpic-playground |
|---|---|---|---|
| Launch prompt | #prompt-textarea, “run @app” (mention picker, 2× Enter) | ProseMirror div[contenteditable], “run app”, 1 Enter | textarea[name="message"], Enter |
| Widget iframe | iframe[src*="oaiusercontent"] | iframe[src*="claudemcpcontent"] | iframe (same-origin) |
| Dismiss host chrome | “Got it” modal | “Accept All Cookies” banner | — |
| Permission dialog | “Open link” (as <a>), “Download” | “Open link” / “Download” (as <button>), “Allow” | — |
| Send a ui/message | sent directly | drafts into composer → “Replace current text?” + button[aria-label="Send message"] | — |
| Verify a conversation turn | /backend-api/conversation/<id> API | scrape document.body.innerText | scrape innerText |
| Reset display mode | real-click “Reset to inline” (gesture-gated) | programmatic requestDisplayMode works | — |