AI Engineer Europe 2026

Why MCP and ChatGPT Apps Use Double Iframes — Frédéric Barthelet, Alpic

Read the talk

Why MCP and ChatGPT Apps Use Two Iframes

Embedding an interactive app inside a conversation requires more than rendering HTML: the browser must isolate host data, preserve app storage, and enforce the right security policy.

From a talk by Frédéric Barthelet

Before you start: Familiarity with HTML, JavaScript, browser origins, and the basic idea of an MCP tool will help you follow the walkthrough.

Bringing an app into a conversation

How can a conversational agent display another company’s interactive interface without giving that interface access to the host application? That is the browser-security problem behind this walkthrough by Frédéric Barthelet, who introduces himself as Alpic’s CTO and co-founder. Alpic provides MCP hosting, and its applications need a place to run inside someone else’s product.

MCP and ChatGPT Apps combine two capabilities. Discovery brings products and services into ChatGPT’s app store and Claude’s connectors, both through browsing and through relevant conversations. Interactive UI lets those services display interfaces where previously the agent could only return text. A view might come from an MCP server or be generated dynamically; either way, the host must render it safely.

Slide titled “What are MCP & ChatGPT Apps?” with introductory bullets and a phone demo showing a property card over a map.Open full source frame
MCP and ChatGPT Apps combine discoverability with interactive UI inside chats.

Barthelet traces this development through Ido and Liad’s MCP UI, OpenAI’s Apps SDK preview, launched on October 6, 2025, and then the MCP Apps extension, which gives multiple clients a shared way to support interactive interfaces.

0:160:45
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

0:16 · section reference included

From a tool call to an HTML view

The UI unit is a view: an HTML document that can include JavaScript and CSS. A tool advertises metadata associating it with a particular UI resource. When a supporting host invokes that tool, it uses the associated view to present the interaction. The application remains ordinary web content; the protocol supplies the connection between the tool and its interface.

The lifecycle separates discovery, retrieval, and data delivery:

  1. During tools/list, the host discovers which tools advertise UI resources.
  2. The host retrieves the relevant HTML, either ahead of time for caching or when the tool is invoked.
  3. The host creates the frame for the view and delivers tool data so it can render dynamic content.

The current documented lifecycle also permits preloading and initializing the view while the tool executes; the UI need not wait for a completed result before it exists. Inputs and results can arrive through notifications.

2:022:12
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

2:02 · section reference included

The unexpected second iframe

Inspecting ChatGPT’s DOM, Barthelet found an iframe nested inside another iframe. Why would a host need both? He uses ChatGPT to investigate the design and says the same pattern appears in Claude. The first constraint becomes clear before adding any third-party UI: the host already has a security policy for its own page.

Content Security Policy, or CSP, tells the browser which resources a document may load and which scripts it may execute. A server can deliver it in response headers alongside the HTML document. Its directives cover scripts, stylesheets, images, API connections, and other resource types. Two directives drive the initial embedding problem:

DirectiveRelevant restriction
script-srcWhich scripts may execute
frame-srcWhich sources may load in nested frames

The host cannot simply accept arbitrary app HTML without considering both restrictions.

“Before AI Apps — the host page” slide with a Content Security Policy example and a table including script-src and frame-src.Open full source frame
The host page’s Content Security Policy lists directives and allowed sources.
3:313:48
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

3:31 · section reference included

Direct HTML injection inherits the wrong environment

An iframe creates a nested browsing context. It has two useful ways to receive a document:

AttributeHow the document arrives
srcThe browser loads a document URL
srcdocThe caller supplies HTML directly

Because the MCP server already exposes an HTML resource, srcdoc looks like the shortest route from that resource to a visible view. It avoids a separate browser fetch for the document.

The attempted embedding can be expressed with ordinary browser JavaScript:

javascript

function mountView(container, html) {
  const frame = document.createElement("iframe");
  frame.srcdoc = html;
  container.append(frame);
  return frame;
}

Here, html is the resource obtained from the MCP server. In this unsandboxed setup, the srcdoc document shares the parent’s origin and inherits the parent document’s CSP. Barthelet describes ChatGPT’s script-src policy as requiring a nonce generated for each request. Arbitrary JavaScript bundled with an app does not have that authorization, so the interface’s scripts are blocked.

Relaxing the host’s CSP would make another problem visible. This is a thought experiment, not a production fix: once the third-party scripts can run in that same-origin environment, they can access host-origin data such as localStorage and script-accessible cookies. Barthelet’s example is an app reading ChatGPT’s local storage and sending it to the app’s backend. Allowing the code to execute does not establish a safe boundary for it.

5:295:48
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

5:29 · section reference included

Sandboxing removes more than host access

Restore the original CSP, then try the iframe’s sandbox attribute. Without allow-same-origin, sandboxing gives the document an opaque origin, commonly represented as null. It no longer has the host’s origin identity, so it cannot use that identity to access the parent DOM.

That isolation also removes capabilities the app may need. In the illustrated setup, localStorage, IndexedDB, and cookies are unavailable because they depend on an origin under which data can be stored and retrieved. The app is isolated, but it has lost ordinary browser persistence.

Adding allow-same-origin preserves the embedded document’s real origin; it does not generally assign the parent’s origin to any iframe. But for this srcdoc example, the real origin is precisely the parent origin that caused the problem. The HTML standard’s sandbox warning applies when scripts are also allowed and the embedded document is same-origin with its parent: the child can remove the sandbox attribute and reload to escape those restrictions. The missing ingredient is a useful origin that belongs neither to the host nor to another app.

8:228:37
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

8:22 · section reference included

A separate URL moves the problem

Using src offers an obvious alternative. The app developer could expose the HTML at a normal /view endpoint and let the browser load it from the developer’s server. That supplies a separate origin, but it runs into the host’s frame-src policy. With an explicit allowlist, ChatGPT would have to admit every app domain and update its policy whenever a new app entered the ecosystem. The browser mechanism works; maintaining the host’s policy becomes the scaling problem.

A host-controlled content domain makes the allowlist manageable. Barthelet uses openai-usercontent.com as his example: authorize the controlled domain space, then use an app-specific subdomain as a routing key. A server behind that domain retrieves the appropriate HTML resource from the MCP server and serves it to the browser.

Now the host must operate infrastructure that dynamically serves third-party application code on its own content domain. That creates responsibility for serving code the host did not write, as well as an infrastructure burden. A smaller MCP host may not have OpenAI’s or Anthropic’s resources to build and operate such a service. The next step retains the separate domain but removes the need to serve a different application document for every app.

9:459:58
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

9:45 · section reference included

One reusable loader, separate origins for each app

The double-iframe design serves a small, reusable loader in an outer iframe on a domain different from the conversational host. That loader obtains the view HTML and puts it into an inner iframe using srcdoc. The MCP Apps specification recommends this architecture for web hosts and describes the host sending HTML to the outer proxy; the loader does not need to fetch independently from the MCP server. The inner document can share the outer frame’s origin without sharing ChatGPT’s origin.

Serving the loader from one shared origin would still let different apps collide in origin-indexed storage. Instead, serve the same loader content from different app subdomains. Barthelet’s concrete distinction is app ABC123 versus app ABC456: ABC123 must not be able to read ABC456’s localStorage.

LayerResponsibility
Conversational hostRetain its own origin and security policy
Outer iframeLoad the common wrapper on an app-specific origin
Inner srcdoc iframeRender the app HTML inside that boundary

The bytes served for the outer loader can be identical across subdomains while the browser still treats those subdomains as different origins. This separates local storage between apps; cookies additionally require appropriate scoping rather than assuming subdomains alone guarantee isolation.

The app also needs its own CSP. Barthelet describes taking policy declarations from MCP metadata and applying them through a CSP meta tag in the outer frame. Those restrictions govern what the view can load or execute, including scripts and further embedded frames. The extra iframe separates the host’s security environment from the app’s security environment while keeping a stable origin for the app.

Barthelet says he has implemented this design in production and points to Facebook’s App Marketplace as an earlier use of the pattern. For an app developer, the practical question is now which metadata controls the environment inside those nested frames.

12:0312:22
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

12:03 · section reference included

Declare the domains the view actually uses

The host can construct the app’s policy only if the app declares its dependencies. List every domain needed by the view in the provided MCP Apps metadata:

  • Connections: External API requests need connection-domain declarations, which feed connect-src.
  • Resources: Script and image sources need resource declarations so the browser can load them.
  • Nested frames: A view embedding another frame needs the corresponding frame-domain declaration.

The declaration slide also includes baseUriDomains alongside connectDomains, resourceDomains, and frameDomains. These are categories of policy inputs, not permission to load arbitrary content.

Slide mapping connectDomains, resourceDomains, frameDomains, and baseUriDomains to CSP directives and the capabilities they unlock.Open full source frame
Declare application domains for connections, resources, nested frames, and base URIs.

Barthelet compares debugging CSP with his early difficulties getting CORS right around 2016: a browser security restriction can look like a broken application until the developer identifies the missing permission. At the time of the recording, he reports that ChatGPT developer mode had previously removed CSP enforcement, allowing missing domains to remain hidden until production. He also mentions a newly added developer-mode option intended to improve that workflow. The operational requirement is to test with the policy the deployed view will actually face.

14:1114:31
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

14:11 · section reference included

Catch a missing domain before submission

Alpic’s open-source Skybridge adds development features over the official Apps SDK. Barthelet highlights end-to-end type safety between the MCP server and its widgets or views, polyfills for host-specific Claude and ChatGPT APIs outside the common specification, and local development tools. The feature he demonstrates is the CSP Inspector.

The generated example is a Magic 8 Ball app. Barthelet describes it as choosing from 25 predefined answers: an MCP tool generates an answer, and a view displays the question and response. Starting the Skybridge server opens a browser-based development environment. A tool list on the left lets him invoke the Magic 8 Ball tool directly, render its associated view, and see code changes reflected live without first bringing the app into ChatGPT.

The inspector compares domains declared in metadata with domains the view actually accesses. The demonstration follows a short repair loop:

  1. Start with a matching policy. The inspector is green because the observed accesses are covered by the declarations.
  2. Add a dependency. Barthelet adds a fetch to an IP-location API. The component rerenders, and the inspector identifies the called domain as missing from the metadata.
  3. Repair the declaration. He adds that domain to the application metadata and reloads. The inspector returns to green.

The useful feedback is the exact missing domain, surfaced while changing the view rather than after shipping it.

Barthelet reports seeing ChatGPT App Store submission rejections and production failures caused by missing CSP domains. The inspector closes that specific gap between a working local interface and an interface permitted to make its required requests. He ends with QR codes for the slides and Skybridge, providing a route back to the architecture and the tooling used to exercise it.

“Thank you! Questions?” slide with QR codes labeled Slides and Skybridge, alongside Skybridge and Alpic branding.Open full source frame
Closing resources include QR codes for the slides and Skybridge.
15:5516:05
Suggest correction

This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.

15:55 · section reference included

Resources

From the talk

Transcript download
Read the transcript and explore slides

Loading transcript and slides…