SPEAKER_00
Hi everyone, my name is Fred. I'm the CTO and co-founder of Alpik, the MCP hosting company. And today I would like to share with you an adventure deep diving into the double iframe mechanism that we have on ChatGPT and MCP app and why it matters when we build apps.
SPEAKER_00
First thing first, if you haven't had the chance to listen to Ido and Léa talks just before about MCP apps, a quick sum up of what those MCP and ChatGPT apps are. That's a new surface area for your business to expose product and services with a new acquisition channel that has two main criteria. First one being discoverability. So you will have ecosystems of connectors and apps available in consumer generalistic agents like ChatGPT and Claude. So ChatGPT app store and Claude connectors. Those apps are browsable inside the store but they are also discoverable in chat. So if you're having a conversation that's relevant for an app to be brought into to add additional context and feature some nice additional actions, they will be brought into the conversation. And the second part, which is the biggest part and what we will be focusing on in this talk, which is the addition of interactive UI inside those conversational agents where you used to add text only. Apps adds a new layer of UI that could be provided by the MCP server but could be generated or generative UI as well. They were first using MCP UI that was developed by Lea Danido just before, then released by OpenAI with an apps SDK back in October last year and standardized across multiple clients on the first official extension of MCP called the app extension.
SPEAKER_00
How does it work down the hood? If we take a little bit closer look at how this UI is brought into the conversation, those are brought using views. Views are the name that we use for those small snippets of UI that appears inside the conversation. Views are always rendered as a result of a tool call. So if your server exposed multiple tools to be used, you can actually add metadata on some of them to say this tool is best used when the results will be displayed using a specific UI. And if the host supports MCP apps, it will use the relevant view corresponding to this tool call to display the results. Views are simple HTML documents. You can include JS and CSS inside. Nothing new under the sun here. It's just a way to package those small snippets of application. And they are discoverable ahead of time because all views are described on the tool list calls that happens at the beginning of the conversation between the host and your MCP server or MCP app. So each tool that supports UI will advertise the resource that's needed to display the UI. It can be cached ahead of time or it can be served and downloaded and served right away when the tool calls that needs UI to be rendered is made. The conversational agent on the host will create this new iframe where the view will be displayed and it will inject the tool results inside so that you have dynamic content rendered to the user.
SPEAKER_00
If you take a closer look at what is inside the DOM of the host when you take a... I was a bit curious. I wanted to know how it was working or how ChatGPT was actually rendering third-party UI inside the conversation. I was a bit surprised and I was met with the idea of what is not so much expectation about having a double iframe, having an iframe nested inside another iframe. And this gave me the idea for these talks. I want to bring you today with me, deep diving into why the decision was made to do this kind of inception nesting of iframes and what are the benefits, what was it put in place and what are the implications when you build apps, what you should be paying attention to and how to make sure that your experience is very nice.
SPEAKER_00
Before we go into that, let's take a close look at what ChatGPT was before MCP apps were implemented. We'll be using ChatGPT as the example through our deep dive, but the exact same happened on Claude AI if you want to take another look by yourself. The initial thing to take a look at that is very important is something called content security policy. Those are directives returned by a server as response header to document calls. So when you load ChatGPT inside your browser, ChatGPT will respond with a document plus security policy, directive on what the browser should be allowed to load and execute and what it shouldn't be able to load and execute. You've got multiple directives including inside content security policy, some about which scripts you can run, which CSS style sheet you can download, which image you can download, which API you can connect to and ask questions to. I will not go into the details, but two are very important to remember here. FrameSrc, which is the directive to allow a specific website to render iframe inside the document, and ScriptSrc, which allows specific site scripts to be run inside the browser.
SPEAKER_00
To be able to run external UI inside ChatGPT, we will use a dedicated HTML element that has been made specifically for this purpose, which is the inline frame element or iframe, that is made to spawn up nested browsing context inside your browser window. So those small pieces of views will be rendered as almost separately, completely isolated browsing context. They are very convenient, and they have two ways to be used. First one is to provide a source for the iframe that you want to render, so a URL of another page to be loaded by your browser and executed locally and rendered inside the space it's made for. And the source doc, which is another attribute which allows you to push inside the iframe content that you want to render as is, without having the browser to load another content.
SPEAKER_00
So if we want to build this marketplace of app and have third-party UI rendered inside ChatGPT, why not use straight away source doc as the attribute for injecting context into. And I'm realizing now that it's a little bit small, but I think I can zoom in a bit. No, I cannot. Okay, sorry about that. You'll have to trust me about what is written inside here. So I will just put inside an iframe, injecting context into the iframe, context being the content being the resource that is being exposed by the MCP server. So pure HTML loaded inside.
SPEAKER_00
If I do that, it's not going to work, mostly because when you load up an iframe with source doc attribute specified, the iframe that you are spawning up is sharing the same origin and sharing the same, therefore, CSP as the host that is responsible for rendering it. So any script that would be part of your application that would be completely blocked by existing ChatGPT CSP on script src directive, which basically require every script in ChatGPT to be signed with a specific nonce produced ahead of time at each request, which is a cool security feature to put in, but it prevents any app to be able to execute JS. So in order to do that, what if we relax a little bit the content security policy of ChatGPT, and make it so that it can execute any line of code. I would not suggest doing that into production, just an experimental thought here. But if you do that, you face a new problem. Basically, you are sharing the same origin as your parent DOM. So the loaded iframe script would be able to access local storage or cookies that are indexed by origin. So you would be able as an app to, for example, get the existing local storage of ChatGPT and send it to your backend server,
SPEAKER_00
But it prevents any app from being able to execute JS. So in order to do that, what if we relax a little bit the content security policy of ChatGPT and make it so that it can execute any line of code? I would not suggest doing that into production, just an experimental thought here. But if you do that, you face a new problem. You are sharing the same origin as your parent DOM. So the loaded iframe script would be able to access local storage or cookies that are indexed by origin. So you would be able as an app to, for example, get the existing local storage of ChatGPT and send it to your backend server, which if you are OpenAI, you would not want people to be able to do.
SPEAKER_00
So let's roll back, put back the CSP as it was before, and instead sandbox the iframe. Sandbox is another attribute that you can use on iframes, allowing iframes to be rendered in what we call an opaque origin. It would mean that the iframe will not share anymore the parent origin. It will be something equivalent to null, making sure that they don't share the same origin and won't have the same problem of script being able to access the parent DOM. However, doing so, you lack any capabilities that are dependent on origin indexing. Because all content, all scripts that are rendered inside your iframe will not be pointing towards a null origin.
SPEAKER_00
You cannot use local storage, you cannot use local index DB, you cannot use cookies, because those are indexed by origin. And the only way to actually provide an origin to a sandboxed iframe is to put allow same origin, which is an additional attribute that brings back the exact same origin as the parent back into the iframe, and you're back to square one where you have an iframe with exactly the right condition to escape its sandboxing and access parent DOM, access parent local storage, access parent cookies. So iframe source doc is not the way to go forward. Let's move on to the next best solution that we have, using the source attribute.
SPEAKER_00
Source attribute basically allows me to reference an endpoint that will be the content loaded by the browser inside this iframe. I'm a developer of a TGPT app, an MCP app. Why not expose my view, my small HTML application, as a normal endpoint, for example on the view endpoint of my own server? That would be a nice way to do it. However, it would require OpenAI to modify another CSP directive, which is the frame source directive, listing all domains that are allowed to actually render iframe on ChatGPT, to include an infinite list of all the MCP applications that will be developed by various companies and brought into the store.
SPEAKER_00
So every time a new app would come out, ChatGPT would have to update CSP to include the new domain so that the frame can be rendered on this specific domain. This is not doable full scale. So what we can do instead is provide a proxy controlled domain, a single one that will be owned by ChatGPT in that case, for example openaiusercontent.com. That's an actual domain that they're using for user content that they want to expose on their own domain. And use this domain as a reference inside the frame source to make sure that the directive does not block rendering any iframe that are loaded on there.
SPEAKER_00
And you need to provide a server on the OpenAI user content that's able to download the resource content from the MCP server, the HTML, and expose it so that it can be rendered, for example on any subdomain and use the first part of the subdomain as the routing key to the right application. Doing so, you effectively need to put in motion an infrastructure where your domain hosts external third-party UI from all apps that will be submitted on your store. Which is not a very good position to be into because once again you will be responsible for code that you don't know what's doing and you will be exposing it on your own domain.
SPEAKER_00
In addition to that, if you're not OpenAI or if you're not Anthropic, you might not have the resources to host to put in place infrastructure required to serve this kind of dynamic serving of content. So what you can do instead is go to the double iframe mechanism. And what you will do with that is basically load the same script for everybody, which will be a simple script responsible to recover the resources and initiate an iframe with the source doc attribute. So we will put the content inside. But this iframe will not be served at top level because it shares the same origin and it has the escaping problem we were mentioning before.
SPEAKER_00
It will be served inside an iframe with a dedicated domain that is different from the GPT to make sure the isolation stays there. You don't want to stop there. Actually, you want to put subdomains for this exact script loader. It will be the exact same content that will be served every time, but you want to put it on various subdomains so that if your app uses any API that requires origin indexing, like local storage or cookie, you don't have collision between your app. So you want app abc123 not to be able to access local storage from app abc456, for example.
SPEAKER_00
The infrastructure for that is much less intensive because you're serving the exact same script content of the first iframe for every subdomain that is equal to the same. So you want to be able to provide the same content security policy definition to the app itself so that it can prevent execution of malicious script or rendering of iframe directly inside the view. There is a way to provide this into the mcpspec and the way you will render it is using a specific meta tag inside the first iframe. This is the actual solution that I implemented into production. And it's not a new solution.
SPEAKER_00
It's been around for a long time and the first time this solution was implemented was back in Facebook days when they released the app marketplace, which is exactly the same problem that you have to run and render a third party UI inside the context of your own application. What's important for you as an app developer is to make sure what are the specs available for you to be able to control the behavior that results from this double iframe nesting. And the thing that you will have to make sure to do is every time you build an application declare all domains your application depends upon inside the provided metadata in the mcpspec.
SPEAKER_00
So that you are sure that they will be rewritten correctly inside the nested iframe. For example, if you are from your app connecting to an external API to fetch data, you need to reference this domain inside the connect src directive of the metadata. Same thing for the script of image, frame and base UI, not so much used. But the two first ones are very important. And it reminded me of a very old problem that I had when I started my developer days back in 2016. As a new developer in the space I was experiencing trouble getting cross-origin resource security right calls. I had trouble getting it right.
SPEAKER_00
CSP reminded me of those ugly days of not getting right for the first time with calls. So there has been effort in the ecosystem to make builders' life better. Especially for example on OpenAI side where they activated, they added an option in developer mode. So if you are a developer and developing app, they have a specific mode which is developer mode which allows you to have access to additional features. Up to today, when you were in developer mode, all CSP were removed. So you were discovering when you were going into production if some of your servers could not be reached because of missing domains inside your CSP.
SPEAKER_00
As a new developer in the space, I was experiencing trouble getting cross-origin resource security right with calls. I had trouble getting it right. CSP reminded me of those ugly days of not getting it right for the first time with calls. So there has been effort in the ecosystem to make builders' lives better. Especially, for example, on the OpenAI side, where they activated—they added an option in developer mode. So if you are a developer developing an app, they have a specific mode, which is developer mode, which allows you to have access to additional features. Up to today, when you were in developer mode, all CSP were removed.
SPEAKER_00
So you were discovering when you were going into production if some of your servers could not be reached because of missing domains inside your CSP. Which was not ideal. They are not the only ones doing work to make builders' lives much easier. At Alpic, we built an open source framework called SkyBridge. SkyBridge is a superset of features on top of the official app SDK Ido and Lead were mentioning. It brings a few things to the table. End-to-end type safety between your MCP server and your app, widget, and views.
SPEAKER_00
You have a lot of APIs that provide polyfill for features that are not part of the command specification and specific to some of the hosts, some of Cloud and some of the ChatGPT APIs. And we provide a bunch of modern development features, especially in the on-dev environment features. And I wanted to show you one specifically made for CSP, which we call the CSP Inspector. Time for a small demo. I do have a few minutes left. Perfect. Let me quickly switch to my screen. Okay. So this is an example code base that will be generated when you create a new SkyBridge application. This example SkyBridge application comes with a small application.
SPEAKER_00
It's an eight ball that you can ask any question to that will respond with one of the 25 predefined answers. It has an MCP tool that serves as generating the answer and a view that's there to display the question and the answer to the questions that you ask. When I start the server of SkyBridge, I have access in my browser to a dev tool, which is a small app that will give me inspection tooling to work with my app before I bring it to the GPT. For example, on the left, I have a list of tools that are exposed by my app. I can, if I want, execute any of the tools. So, for example, here I'm executing the magic eight ball tool.
SPEAKER_00
And if there are views associated with this tool, that will be rendered inside the inspector for me to have a closer look at it and make changes and see those changes reflected live in the UI. The neat thing I wanted to demonstrate is the CSP part of the inspector that we built, which basically looks at all the domains that you listed inside your metadata and all the domains that are actually accessed by calls made by your view and compile them to make sure that none of them are not listed yet. So, for example, here, everything looks green.
SPEAKER_00
But if I go back to my actual code base and, for example, fetch some API to get info about my IP location, this will be affected straight away in the inspector because the component has been re-rendered. And I know I have the exact domain that I just called listed as missing from the metadata. And I can go back inside my application and add the missing domain. And now it should appear green if I reload it. Yeah, everything good. So, neat little tool. There are a lot of other features that are packed inside SkyBridge.
SPEAKER_00
But that's one of them that we made sure to be available to builders because we've seen a lot of rejection coming from the GPT App Store submission because of missing CSP. And apps not working in production because of missing CSP domains. Just to finish up quickly, if I can. Up. Up. This one, yeah. Yeah, that's all. Thank you again for your time. If you want to grab the slides and take a look later on, feel free to scan the first QR code.
SPEAKER_00
If you want to give a try to SkyBridge, feel free to scan the second one. I will run a small lottery right now. We have Skigoro to win. If you star the SkyBridge repo in the next minute or so, I will draw a name at random and you will win a Skigoro mask. Thank you very much for your attention.
SPEAKER_00
Thank you. And the thing that you will have to make sure to do is every time you build an application declare all domains your application depends upon inside the provided metadata in the mcpspec. So that you are sure that they will be rewritten correctly inside the nested iframe. For example, if you are from your app connecting to an external API to fetch data, you need to reference this domain inside the connect src directive of the metadata. Same thing for the script of image, frame and base UI, not so much used. But the two first ones are very important. And it reminded me of a very old problem that I had when I started my developer days back in 2016 I think it was.
SPEAKER_00
As a new developer in the space I was experiencing trouble getting cross-origin resource security right calls. I had trouble getting it right. CSP reminded me of those ugly days of not getting right for the first time with calls. So there has been effort in the ecosystem to make builders' life better. Especially for example on OpenAI side where they activated, they added an option in developer mode. So if you are a developer and developing app, they have a specific mode which is developer mode which allows you to have access to additional features. Up to today, when you were in developer mode, all CSP were removed.
SPEAKER_00
So you were discovering when you were going into production if some of your servers could not be reached because of missing domains inside your CSP. Which was not ideal. They are not the only ones doing a bunch of work to make builders' life much easier. At Alpic we build an open source framework called SkyBridge. SkyBridge is a super set of features on top of the official app SDK Ido and Lead were mentioning. It brings a few things to the table. End-to-end type safety between your MCP server and your app, widget and views. You have a lot of APIs that provide polyfill for features that are not part of the command specification and specific to some of the hosts,
SPEAKER_00
some of Cloud and some of the chat GPT APIs. And we provide a bunch of modern development features, especially in the on-dev environment features. And I wanted to show you one just specifically made for CSP, which we call the CSP Inspector. Time for a small demo. I do have a few minutes left. Perfect. Let me quickly switch to my screen.
SPEAKER_00
Okay. So this is an example code base that will be generated when you create a new SkyBridge application. This example SkyBridge application comes with a small application. It's an eight ball that you can ask any question to that will respond to one of the 25 predefined answers. It has an MCP tool that serves as generating the answer and a view that's just there to display the question and the answer to the questions that you ask. When I start the server of SkyBridge, I have access in my browser to a dev tool, which is basically a small app that will give me inspection tooling to work with my app before I bring it to the GPT.
SPEAKER_00
For example, on the left, I have a list of tools that are exposed by my app. I can, if I want, execute any of the tools. So for example, here I'm executing the magic eight ball tool. And if there are views associated with this tool, that will be rendered inside the inspector for me to have a closer look at it and make changes and see those changes reflected live in the UI. The neat thing I wanted to demonstrate is the CSP part of the inspector that we built, which basically looks at all the domains that you listed inside your metadata.
SPEAKER_00
And all the domains that are actually accessed by calls made by your view and compile them to make sure that none of them are not listed yet. So for example, here, everything looks green. But if I go back to my actual code base and, for example, fetch some API to get info about my IP location, this will be affected straight away in the inspector because the component has been re-rendered. And I know I have the exact domain that I just called listed as missing from the metadata. And I can go back inside my application and add the missing domain. And now it should appear green if I reload it. Yeah, everything good. So, neat little tool.
SPEAKER_00
There are a lot of other features that are packed inside SkyBridge. But that's one of them that we made sure to be available to builders because we've seen a lot of rejection coming from the GPT App Store submission because of missing CSP. And apps not working in production because of missing CSP domains.
SPEAKER_00
Just to finish up quickly, if I can. Up. Up. This one, yeah. Yeah, that's all. Thank you again for your time. If you want to grab the slides and take a look later on, feel free to scan the first QR code. If you want to give a try to SkyBridge, feel free to scan the second one. I will run right now a small lottery. We have Skigoro to win. If you start SkyBridge repo in the next minute or so, I will draw a name at random and you will win Skigoro mask. Thank you very much for your attention.
SPEAKER_00
Thank you.