test(web): prove the dedicated app origin end to end

`frame-ancestors` is checked against every ancestor, not just the parent.
A published app document is framed by the sandbox proxy, which is framed
by the Inspector page — so admitting only the proxy's origin blocked the
frame outright (it rendered as a chrome-error frame that never reached
the bridge). `appDocumentEmbedders` now admits both.

That failure was invisible to every unit test, so `smoke:web:app` grows a
second phase: the same demo app declaring `_meta.ui.domain`, driven
through the same connect → open → ready chain, then asserting the two
facts that only hold on the dedicated path — the inner frame was
navigated to a published document, and its own `location.origin` is a
real http origin rather than "null". Both phases end at
`data-app-status="ready"`, so nothing short of reading the frame's origin
can tell them apart.

Adds `test-servers/configs/mcp-app-domain-http.json` (and a `domain`
param on the `mcp_app_demo_widget` preset) as the showcase server for
both the smoke and hand testing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-23 23:21:41 -04:00 • committed by GitHub
commit 8e8c1bbad9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 200 additions and 34 deletions

View file

@ -253,7 +253,7 @@ function resolveResourcePreset(
case "numbered_resources":
return createNumberedResources(Number(get("count")) || 3);
case "mcp_app_demo_widget":
return createMcpAppDemoResource();
return createMcpAppDemoResource(get("domain") as string | undefined);
case "choose_option_app":
return createAppElicitationResource();
case "rfc6570_base":

View file

@ -1617,8 +1617,15 @@ export function createMcpAppDemoTool(): ToolDefinition {
* `_meta.ui.csp` (no external connect/resource domains) and a sample
* `permissions` block so `--app-info` and the host's CSP enforcement both have
* something to read.
*
* `domain` is the spec field by which a server asks its host for a stable,
* dedicated origin (#2056). Omitted by default so the default opaque-origin
* render stays the thing this fixture exercises; pass one to drive the
* dedicated-origin path instead. Its *value* is not an address the Inspector
* serves — the spec makes the format host-dependent, and the Inspector reads
* any non-empty string as "give me a real origin".
*/
export function createMcpAppDemoResource(): ResourceDefinition {
export function createMcpAppDemoResource(domain?: string): ResourceDefinition {
return {
name: "mcp_app_demo_widget",
uri: MCP_APP_DEMO_URI,
@ -1630,6 +1637,7 @@ export function createMcpAppDemoResource(): ResourceDefinition {
csp: { connectDomains: [], resourceDomains: [] },
permissions: { clipboard: false },
prefersBorder: true,
...(domain ? { domain } : {}),
},
},
};