The fastest way to diagnose any of the issues below is to load your App in the reference host, which logs all protocol traffic to the browser console. See Test with basic-host.
The most common causes, in the order you should check them:
connect() was never called. The host waits for the App to send ui/initialize before giving the iframe a non-zero size, so an App that constructs new App(...) but never calls app.connect(transport) renders as an empty sliver. Confirm connect() runs on page load and that its promise resolves.
Uncaught JavaScript error. Open browser developer tools inside the iframe: right-click the App area, choose Inspect, then switch the console context dropdown (top-left of the Console tab) from top to the sandboxed frame. An uncaught error stops the App before it paints.
CSP violation. Look for Refused to connect to… or Refused to load… in the console. Any network request, including to localhost during development, must be declared in _meta.ui.csp.connectDomains or resourceDomains. See the CSP & CORS guide.
Resource URI mismatch. The _meta.ui.resourceUri on the tool must match the URI passed to registerAppResource exactly. A trailing slash or case difference prevents the host from finding the HTML.
Wrong MIME type. The resource's mimeType must be text/html;profile=mcp-app (exported as RESOURCE_MIME_TYPE). Plain text/html is not recognized as an App resource.
toolinput / toolresult events never fireapp.addEventListener("toolresult", …) before calling connect(). If the listener is attached after connect() resolves, the notification may have already been delivered and discarded. The React useApp hook handles this ordering automatically.@modelcontextprotocol/ext-apps than the host expects, the initialize handshake can fail silently. Keep the SDK version current.MCP Apps are portable only if they use the SDK exclusively. Common portability mistakes:
window.openai. Use the App class from this SDK, which speaks the standard protocol to any compliant host.resourceDomains._meta.ui.domain to request a stable origin rather than hardcoding one in CORS allowlists. See CSP & CORS.See Controlling App height. The most common cause is height: 100vh combined with the default autoResize: true.
CSP and CORS are separate controls with different error messages and different fixes:
Refused to connect): The browser blocked the request because the domain is not in connectDomains. Add the domain to _meta.ui.csp on the MCP server.No 'Access-Control-Allow-Origin' header): The API server rejected the request because it does not recognize the sandbox origin. Add the origin to the API server's allowlist, or use _meta.ui.domain to get a predictable origin that can be allowlisted.See the CSP & CORS guide for configuration examples.
If the App declares color-scheme: light dark (or color-scheme: dark) and the host document does not, browsers insert an opaque backdrop behind the iframe to prevent cross-scheme bleed-through. Remove the color-scheme declaration and use the [data-theme] attribute pattern from the host context guide.
npm start in this repository to serve examples/basic-host at http://localhost:8080. It logs all protocol traffic to the console.