Repository navigation
Description
Short summary
MCP App views render in an <iframe> with sandbox="allow-scripts allow-popups allow-forms". Because allow-same-origin is missing, every MCP App, and every frame it embeds, runs with an opaque null origin. Browser storage throws, and cross-origin requests send Origin: null, which origin-checking services reject. The MCP Apps spec requires the sandbox to have allow-scripts and allow-same-origin.
Affected version or release
1.1.23 (Windows)
Installation context
Windows 11 Enterprise 25H2 (build 26200.9448), WebView2 Runtime 154.0.4258.37, desktop installation. Local session with a custom MCP server that exposes an MCP App resource (text/html;profile=mcp-app), shown as an inline MCP App view.
What happened?
- The MCP App
<iframe>loads fromhttp://mcp-app-guest.localhost/<token>withsandbox="allow-scripts allow-popups allow-forms". - Inside the app,
window.originis"null"andlocalStoragethrows:
SecurityError: Failed to read the 'localStorage' property from 'Window': The document is sandboxed and lacks the 'allow-same-origin' flag. - Sandbox flags are inherited, so a cross-origin page the app embeds (allowed via
_meta.ui.csp.frameDomains) is also opaque, even if its own<iframe>includesallow-same-origin. Its API calls sendOrigin: null. In our case, the embedded service rejects the CORS preflight with HTTP 400 and the embed never renders. - App authors can't work around this. The flags are set on the host's
<iframe>, a frame can't relax its parent's sandbox, and MCP Apps resource metadata has no field for sandbox flags.
As a local check, adding allow-same-origin to the MCP App <iframe> at runtime through DevTools (only for mcp-app-guest.localhost frames, no other change) makes the same app and its embed load and render normally. The same app also works in a host that uses the MCP Apps reference host's sandbox (allow-scripts allow-same-origin allow-forms).
Steps to reproduce
- Start the Copilot app with WebView2 DevTools enabled. For example, set
WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS=--remote-debugging-port=9223before launching, then openedge://inspect. - Call any tool that renders an MCP App.
- Inspect the MCP App
<iframe>. Itssandboxisallow-scripts allow-popups allow-forms. - In the console, switch to the MCP App frame and run
window.origin(returns"null") andlocalStorage.length(throwsSecurityError).
Or add this probe to any MCP App's HTML:
<pre id="probe"></pre>
<script>
const lines = [`window.origin: ${window.origin}`];
try { localStorage.setItem('probe', '1'); lines.push('localStorage: ok'); }
catch (e) { lines.push(`localStorage: ${e.name}`); }
document.getElementById('probe').textContent = lines.join('\n');
</script>In Copilot it prints window.origin: null and localStorage: SecurityError.
Expected behavior
MCP App views get a real, non-opaque origin, as the MCP Apps spec requires (2026-01-26, Sandbox proxy):
- The Host and the Sandbox MUST have different origins.
- The Sandbox MUST have the following permissions:
allow-scripts,allow-same-origin.
The reference host uses allow-scripts allow-same-origin allow-forms (basic-host sandbox.ts), and the proposed conformance tests assert a non-opaque app origin (modelcontextprotocol/ext-apps#743).
Copilot already meets the first rule: apps load from mcp-app-guest.localhost, not the app shell's origin. It also honors _meta.ui.csp.frameDomains by adding those origins to the app's frame-src, so embedding cross-origin pages is supported. But those pages inherit the opaque origin, so embeds that call their own APIs or use storage fail.
Additional context
Likely cause: This looks like a deliberate isolation choice rather than an oversight. Every MCP App loads from the same origin (mcp-app-guest.localhost), so adding allow-same-origin alone would let apps from different servers share storage and script each other's frames. Leaving it out prevents that, but breaks spec conformance and any app that needs a real origin.
Suggested fix: Give each MCP server (or each app) its own origin, for example <server-id>.mcp-app-guest.localhost, then add allow-same-origin. See the discussion on unique sandbox origins in modelcontextprotocol/ext-apps#213.
Who is affected: Self-contained apps that only render tool data work today. Apps that use browser storage, call origin-checked or cookie-based APIs, or embed authenticated third-party content fail. The MCP Apps docs recommend localStorage for view-state persistence (patterns), which fails in Copilot.
Possibly related: #781 (MCP app canvas does not render), unverified.
Logs: Debug logs aren't attached because they include details of a private MCP server. The behavior reproduces with any MCP App using the steps above. I can share sanitized logs if needed.
Activity
roneetshaw commentedon Oct 8, 2026
I was also able to reproduce this with our Power BI Report Authoring MCP App on
Windows in the GitHub Copilot desktop app.
Our component exposes
open_report_view, which returns an MCP App resource(
text/html;profile=mcp-app). The app usespowerbi-clientto render apublished Power BI report inside a nested iframe. Its MCP resource metadata
declares the required Power BI domains through
_meta.ui.csp(frameDomains,connectDomains, andresourceDomains), and sensitive embed credentials aredelivered through private MCP result metadata/resources rather than included in
the shared HTML or model-facing response.
Before isolating the sandbox behavior, we verified all of the following
independently:
powerbi-clientflow renderedsuccessfully in standalone Microsoft Edge.
Inside the generic Copilot MCP App view, Power BI failed with:
We then reduced the scenario to a controlled iframe A/B test using the same
viewer and report:
Result: the Power BI embed failed with the same
NetworkConnectionIssue: Get report failedbehavior.Result: the report rendered successfully.
This confirms the same root cause described in this issue: without
allow-same-origin, the outer sandbox gives the MCP App an opaque origin, andthat restriction is inherited by its embedded Power BI frame. Adding
allow-same-originonly to the nested iframe cannot relax the parent sandbox.We also confirmed that the MCP transport was not the cause. The MCP was
reachable through a Dev Tunnel, tool discovery and resource delivery worked,
and the report credentials were valid. A tunnel can transport MCP requests, but
it cannot change the sandbox flags applied by the Copilot host. Likewise, the
MCP App's CSP metadata can authorize domains but cannot request a different
iframe sandbox policy.
As a product-specific workaround, we built/used a separately installed GitHub
Copilot client canvas extension. That extension reads the same MCP private
report-credential resource through the connected MCP API and renders the report
through a dedicated client canvas rather than the generic MCP App iframe. The
report works through that canvas path. However, this is not a workaround
available to a normal MCP App author and does not fix
open_report_view; itrequires a host-specific client extension.
So our observations are consistent with the report here:
valid.
allow-same-origin.the parent sandbox.
allow-same-originin the controlled host configuration is the singlechange that made the same embed render.