{
  "video_id": "c-2eEv2ou7Y",
  "channel_slug": "aidotengineer",
  "channel_handle": "aidotengineer",
  "title": "Why MCP and ChatGPT Apps Use Double Iframes — Frédéric Barthelet, Alpic",
  "duration_seconds": 1211.0,
  "url": "https://www.youtube.com/watch?v=c-2eEv2ou7Y",
  "upload_date": "",
  "transcript": "[music]\n>> Hi everyone.\nMy name is Fred. I'm the CTO and\nco-founder of Alpic, the MCP hosting\ncompany.\nAnd today I would like to share with you\nan adventure deep diving into the double\niFrame mechanism that we have on ChatGPT\nand MCP app and what it matters when we\nbuild apps.\nFirst thing first, if you haven't had\nthe chance to listen to either Ido and\nLeat talks just before about MCP apps, a\nquick sum up of what those MCP and\nChatGPT apps are.\nThat's a new surface area for your\nbusiness to expose product and services\nwith new acquisition channel\nthat has two main criteria. First one\nbeing discoverability, so you will have\necosystems of connectors and apps\navailable in a consumer generalistic\nagent like ChatGPT and Claude. So\nChatGPT app store and Claude connectors.\nThose apps\nbrowsable inside the store, but they are\nalso discoverable in chat. So if you're\nhaving a conversation that's relevant\nfor an app to be brought into to add\nadditional context and feature some nice\nadditional actions, they will be brought\ninto the conversation.\nAnd the second part, which is the\nbiggest part and what we will be\nfocusing on in this talk, which is the\naddition of interactive UI inside those\nconversational agents where you used to\nadd text only. Apps add a new layer of\nUI that could be provided by the MCP\nserver,\nbut could be generated generative UI as\nwell.\nThey were first thought of using MCP UI\nthat was developed by Leat and Ido just\nbefore, then released by Open AI with an\napp SDK back in October last year and\nstandardized across multiple clients on\nthe first official extension of MCP\ncalled the app extension.\nHow does it work under the hood? If we\ntake a little bit\ncloser look at\nhow this UI is brought into the\nconversation,\nthose are brought using views. Views are\nthe name that we use for those small\nsnippet of UI that appears inside the\nconversation.\nViews are always rendered as a result of\na tool call. So if your server exposed\nmultiple tool to be used, you can\nactually add metadata on some of them to\nsay this tool is best used when\nresults will be displayed using a\nspecific UI. And if the host supports\nMCP apps, it will use\nthe relevant view corresponding to this\ntool call to display the results.\nViews are simple HTML document. You can\ninclude JS and CSS inside. Nothing new\nunder the sun here. It's just a way to\npackage those small snippets of\napplication and\nthey are discoverable ahead of time\nbecause all views are described on the\ntool list calls that happens at the\nbeginning of the conversation between\nthe host and your MCP server or MCP app.\nSo each tools that supports UI will\nadvertise the resource that's needed to\ndisplay the UI. It can be cached ahead\nof time or it can be served and\ndownloaded and served right away when\nthe tool call that needs UI to be\nrendered is made.\nThe conversational agent on the host\nwill create this new iFrame where the\nview will be displayed and it will\ninject the tool results inside so that\nyou have dynamic content rendered to the\nuser.\nIf you take a closer look at what is\ninside the\nDOM of the host when you take a\nI was a bit curious and I wanted to know\nhow it was working or how ChatGPT was\nactually rendering third-party UI inside\nthe conversation.\nI was a bit surprised and I was met with\nnot so much expectation about having a\ndouble iFrame, having an iFrame nested\nin inside another iFrame. And this gave\nme the idea for this talk. I want to\nbring you today with me deep diving into\nwhy the decision was made to do this\nkind of inception nesting of iFrames and\nwhat are the benefits, what was it put\nin place and what are the implications\nwhen you build apps, what should be\npaying attention to and how to make sure\nthat your experience is very nice.\nBefore we go into that, let's take a\nclose look at what ChatGPT was before\nMCP app were implemented. We'll be using\nChatGPT as the example throughout this\ndeep dive, but the exact same happened\non Claude AI if you want to take another\nlook by yourself. The initial thing to\ntake a look at that is very important is\nsomething called content security\npolicy. Those are directives returned by\nserver as response header to document\ncall. So when you load ChatGPT inside\nyour browser, ChatGPT will respond with\na document plus security policy\ndirective on what the browser should be\nallowed to load and execute and what it\nshouldn't be allowed to load and\nexecute. You've got multiple directives\nincluded inside content security policy.\nSome about which scripts you can run,\nwhich CSS style sheet you can download,\nwhich image you can download, which API\nyou can connect to and ask question to.\nI will not go into the details, but two\nare very important to remember here.\nFrame SRC, which basically is a\ndirective to allow\nspecific website to render iFrame inside\nthe document and script SRC, which\nbasically allow specific sites\nscript to be run inside the browser.\nTo be able to run\nexternal UI inside ChatGPT, we will use\na dedicated HTML element that has been\nmade specifically for this purpose,\nwhich is the inline frame element or\niFrame\nthat is made to basically spawn up\nnested browsing context inside your\nbrowser window. So those small pieces of\nviews will be rendered as almost\nseparately completely isolated browsing\ncontext. They are very convenient and\nthey have two ways to be used. First one\nis to provide a source for the iFrame\nthat you want to render. So basically a\nURL of another page to be loaded by your\nbrowser and executed locally and\nrendered inside the space it's made of\nit's made for. And the source doc, which\nis another attribute which allows you to\npush inside the iFrame content that you\nwant to render as is without having the\nbrowser to load another content.\nSo if you want to build this marketplace\nof app and have third-party UI rendered\ninside ChatGPT, why not use straight\naway source doc as the attribute for\nas the attribute for uh\nfor injecting context into. And I'm\nrealizing now that it's a little bit\nsmall, but I think I can zoom in a bit.\nNo, I cannot. Okay, sorry about that.\nYou'll have to trust me about what is\nwritten inside here. So\nI will just put inside an iFrame\ninjecting context into the iFrame.\nContext being the content being\nbasically the resource that is being\nexposed by the MCP server. So pure HTML\nloading inside. If I do that, uh\nit's not going to work mostly because\nwhen you load up an iFrame with source\ndoc attribute\nspecified, the iFrame that you are\nspawning up is sharing the same origin\nand sharing the same therefore CSP as\nthe host that is responsible for\nrendering it. So\nany script that would be part of your\napplication that would be completely\nblocked by existing ChatGPT CSP on\nscript SRC directive, which basically\nrequire every script in ChatGPT to be\nsigned with a specific nonce produced\nahead of time at each request, which is\na cool security feature to put in, but\nit prevents any app to be able to\nexecute JS.\nSo in order to do that,\nwhat if we relax a little bit the\ncontent security policy of ChatGPT and\nmake it\nso that it can execute any line of code?\nI would not suggest doing that into\nproduction, just an experimental thought\nhere.\nBut if you do that, you are faced a new\nproblem.\nBasically, you are sharing same origin\nas your parent DOM. So the loaded iFrame\nscripts would be able to access local\nstorage or cookies that are indexed by\norigin. So you would be able as an app\nto\nfor example get the existing local\nstorage of ChatGPT and send it to your\nback-end server, which if you are Open\nAI, you would not want people to be able\nto do.\nSo let's roll back, put back the CSP as\nit was before and instead\nsandbox the iFrame. Sandbox is another\nattribute that you can use on iFrames\nallowing iFrames to be rendered in what\nwe call the no pack origin. It would\nmean that basically the iFrame will not\nshare anymore the parent origin. It\nwould be something equivalent to null\nmaking sure that they don't share the\nsame origin and won't have the same\nproblem of script being able to access\nthe parent DOM.\nHowever, doing so,\nyou lack any capabilities that are\ndependent on origin indexing. Because\nall content, all scripts that are\nrendered inside your iFrame will now be\npointing towards a null origin. You\ncannot use local storage. You cannot use\nlocal index DB. You cannot use cookies\nbecause those are indexed by origin. And\nthe only way to actually provide an\norigin to a sandboxed iFrame is to put\nallow same origin, which is an\nadditional attribute that\nbrings back the exact same origin as the\nparent back into the iFrame and you're\nback to square one where you have an\niFrame with exactly the right condition\nto escape its sandboxing and access\nparent DOM, access parent local storage,\naccess parent cookies.\nOkay, so\niFrame source doc is not the way to go\nforward. Let's move on to the next best\nsolution that we have using the source\nattribute.\nSource attribute basically allows me to\nreference an\nan endpoint that will be the content\nloaded by the browser inside this\niFrame.\nI'm a developer of the ChatGPT app and\nMCP app.\nWhy not expose my view, my small HTML\napplication\nas a normal endpoint like on the view\nendpoint for example of my own server?\nThat would be a nice way to do it.\nHowever, it would require\nopen AI to modify\nthe another CSP directive, which is\nbasically the frame source directive\nlisting all domains that are allowed to\nactually render iframe on chat GPT to\ninclude an infinite list of all the MCP\napplication that will be developed by\nvarious companies and brought into the\nstore. So, every time a new app would\ncome out,\nchat GPT would have to update CSP to\ninclude the new domain so that the frame\ncan be rendered on the specific domain.\nThis is not doable full scale. So, what\nwe can do instead is\nprovide kind of a proxy\ncontrolled domain, single one that will\nbe owned by chat GPT in that case. For\nexample, open AI open AI user\ncontent.com. That's an actual domain\nthat they're using for user content that\nthey want to expose on their own domain.\nAnd use this domain as a reference\ninside the frame source to make sure\nthat the directive does not block\nrendering any iframes that are loaded on\nthere.\nAnd you need to provide a server on the\nopen AI user content that's able to\ndownload the resource content from the\nMCP server, the HTML, and expose it so\nthat it can be rendered for example on\nany subdomain and use the first part of\nthe subdomain as the routing key to the\nright application.\nDoing so, you effectively needs to put\nin motion an infrastructure where your\ndomain\nhosts\nexternal third-party UI from all apps\nthat will be submitted on your store.\nWhich is not a very good position to be\ninto because once again, you will be\nresponsible for code that you don't know\nwhat it's doing and you will be exposing\nit on your own domain.\nIn addition to that, if you're not open\nAI or if you're not Anthropic, you might\nnot have the resource as a host to put\nin place the infrastructure required to\nserve this kind of dynamic serving of\ncontent.\nSo, what you can do instead\nis\ngo through the double iframe mechanism.\nAnd what you will do with that is\nbasically load the same script for\neverybody, which will be a simple script\nresponsible to recover the resources and\ninitiate an iframe with the source doc\nattribute. So, we put the content\ninside, but this iframe will not be\nserved at top level because it shares\nthe same origin and it has the escaping\nproblem we are mentioning before. It\nwill be served inside an iframe with\ndedicated domain that is different from\nchat GPT to make sure the isolation stay\nthere.\nYou won't don't want to stop there.\nActually, you want to\nput subdomains for this exact script\nloader.\nIt will be the exact same content that\nwill be served every time, but you want\nto put it on various subdomains so that\nif your app uses any\nAPIs that requires origin indexing like\nlocal storage or cookie, you don't have\ncollision in between your app. So, you\nwant app ABC123 not to be able to access\nlocal storage from app ABC456 for\nexample.\nThe infrastructure for that is much\nless intensive because you're serving\nthe exact same script content of the\nfirst iframe for every subdomain that is\nexist on this specific endpoint.\nLast but not least,\nyou want to be able to provide the same\nkind of content security policy\ndefinition to the app itself so that it\ncan prevent execution of any such script\nor rendering of iframe\ndirectly inside the view.\nThere is a way to provide this into the\nMCP spec and the way you will render it\nis using a specific meta tag inside the\nfirst iframe.\nThis is the actual solution that I\nimplemented into production.\nAnd it's not a new solution. It's been\naround for a long time and actually the\nfirst time this solution was implemented\nwas back in Facebook days when they\nreleased their app marketplace, which is\nexactly the same problem that you have\nto run and render third-party UI inside\nthe context of your own application.\nWhat's important for you as an app\ndeveloper is to make sure what are the\nspecs available for you to be able to\ncontrol the behavior that results from\nthis double iframe nesting. And the\nthing that you will have to make sure to\ndo is every time you build an\napplication, declare all domains your\napplication depends upon inside the\nprovided metadata in the MCP app spec so\nthat\nyou are sure that they will be\nrewritten correctly inside the nested\niframe. For example, if you are from\nyour app connecting to an external API\nto fetch data, you need to reference\nthis domain inside the connect SRC\ndirective of the\nmetadata.\nSame thing for the script, image,\nframe and base UI not so much used, but\nthe two first one are very important.\nAnd\nit's reminded me of a very old problem\nthat I had when I started my developer's\ndays back in 2016 I think it was.\nAs a new\ndeveloper in the space, I was\nexperiencing a trouble getting\ncross-origin resource\nsecurity right calls. I had trouble\ngetting it right. CSP reminded me of\nthose ugly day of not getting right the\nfirst time with calls. So, there has\nbeen effort in the ecosystem to make\nbuilders' life better,\nespecially for example on open AI side\nwhere they activated they added an\noption in developer mode. So, if you're\na developer and developing app, they\nhave a\nspecific mode which is developer mode\nwhich allows you to have access to\nadditional features. Up to today,\nwhen you were in developer mode, all CSP\nwere removed. So, you were discovering\nwhen you were going into production if\nsome of your servers could not be\nreached because of missing domains\ninside your CSP, which was not ideal.\nThey are not the only one doing a bunch\nof work to make builders' life much\neasier.\nAt Alpic, we build an open source\nframework called Skybridge.\nSkybridge is a super set of feature on\ntop of the official app SDK either and\nthe app we are mentioning. It brings a\nfew things to the table, end-to-end type\nsafety between your MCP server and your\napp widgets and views.\nYou have a lot of APIs that provides\npolyfill for features that are not part\nof the common specification and specific\nto some of the hosts, some of code and\nsome of chat GPT APIs. And\nwe provide a bunch of modern development\nfeatures especially in the on dev\nenvironment features. And I wanted to\nshow you one just specifically made for\nCSP, which we call the CSP inspector.\nTime for a small demo.\nI do have a few minutes left. Perfect.\nLet me quickly switch to\nmy screen.\nUp.\nUh\nup.\nOkay.\nSo, this is\nUm this is an example code base that\nwill be generated when you create a new\nSkybridge application.\nThis example Skybridge application comes\nwith\na small application. It's a eight ball\nthat you can ask any question to that\nwill respond one of the 25 predefined\nanswer.\nIt has\nMCP tool that serves as\ngenerating the answer and a view that is\nthere to display the question and the\nanswer to the question that you ask.\nWhen I start the server of Skybridge, I\nhave access in my browser to a dev tool,\nwhich is basically a small app that will\ngive me inspection tooling to work with\nmy app before I bring it to chat GPT.\nFor example, on the left I have a list\nof tools that are exposed by my app.\nI can if I want ask\nexecute any of the tool. So, for example\nhere, I'm executing the magic eight ball\ntool. And if there are\nviews associated with this tool,\nthey will be rendered inside the\ninspector for me to have a closer look\nat it and make changes and see those\nchanges reflected live in the UI.\nThe neat thing I wanted to demonstrate\nis the CSP part of the inspector that we\nbuilt, which basically looks at all the\ndomains that you listed inside your\nmetadata and all the domains that are\nactually accessed by calls made by your\nview and compare them to make sure that\nnone of them are not listed yet.\nSo, for example here, everything's\ngreen, but if I go back to my actual\ncode base and for example\nfetch some API to get\ninfo about my IP location,\nthis\nwill be reflected straight away in the\ninspector because the component has been\nre-rendered and I know I have the exact\ndomain that I want\nthat I just called listed as missing\nfrom the metadata. And I can go back\ninside my application and add the\nmissing domain and now it should appear\ngreen if I reload it.\nYeah, everything good.\nSo, neat little tool. There are a lot of\nother features that are packed inside\nSkybridge, but that's one of them that\nwe made sure to be available to builders\nbecause we've seen a lot of rejection\ncoming from chat GPT app store\nsubmission because of missing CSP and\napp not working in production because of\nmissing CSP domains.\nUm\nJust to finish up quickly if I can.\nUp.\nUh up. This one. Yeah.\nUm yeah, that's all. Thank you again for\nyour time. If you want to grab the\nslides and take a look later on,\nfeel free to scan the first QR code.\nIf you want to give a try to Skybridge,\nfeel free to scan the second one.\nI will run right now a small lottery. We\nhave\nski goggles to win if you star Skybridge\nrepo in the next minute or so.\nI will draw name at random and you will\nwin a ski ball mask. Thank you very much\nfor your attention.\n>> [applause]\n[music]",
  "transcript_chars": 18107,
  "ingested_at": "2026-06-16T22:30:11.983006+00:00",
  "source": "channel",
  "yt_meta": {
    "view_count": null,
    "like_count": null,
    "channel_id": null,
    "categories": null,
    "tags": null
  }
}