[{"data":1,"prerenderedAt":1696},["ShallowReactive",2],{"navigation_docs":3,"-advanced-mcp-apps-internals":272,"-advanced-mcp-apps-internals-surround":1691},[4,40,70,99,122,151,184,243],{"title":5,"path":6,"stem":7,"children":8,"page":39},"Getting Started","\u002Fgetting-started","1.getting-started",[9,14,19,24,29,34],{"title":10,"path":11,"stem":12,"icon":13},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-book-open",{"title":15,"path":16,"stem":17,"icon":18},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":20,"path":21,"stem":22,"icon":23},"Configuration","\u002Fgetting-started\u002Fconfiguration","1.getting-started\u002F3.configuration","i-lucide-settings",{"title":25,"path":26,"stem":27,"icon":28},"MCP Inspector","\u002Fgetting-started\u002Finspector","1.getting-started\u002F4.inspector","i-lucide-circuit-board",{"title":30,"path":31,"stem":32,"icon":33},"Connection","\u002Fgetting-started\u002Fconnection","1.getting-started\u002F5.connection","i-lucide-plug",{"title":35,"path":36,"stem":37,"icon":38},"Agent Skills","\u002Fgetting-started\u002Fagent-skills","1.getting-started\u002F6.agent-skills","i-lucide-sparkles",false,{"title":41,"path":42,"stem":43,"children":44,"page":39},"Tools","\u002Ftools","2.tools",[45,50,55,60,65],{"title":46,"path":47,"stem":48,"icon":49},"Overview","\u002Ftools\u002Foverview","2.tools\u002F0.overview","i-lucide-wrench",{"title":51,"path":52,"stem":53,"icon":54},"Schema, handler & returns","\u002Ftools\u002Fschema-handler","2.tools\u002F1.schema-handler","i-lucide-braces",{"title":56,"path":57,"stem":58,"icon":59},"Annotations & input examples","\u002Ftools\u002Fannotations","2.tools\u002F2.annotations","i-lucide-badge-info",{"title":61,"path":62,"stem":63,"icon":64},"Errors & caching","\u002Ftools\u002Ferrors-caching","2.tools\u002F3.errors-caching","i-lucide-shield",{"title":66,"path":67,"stem":68,"icon":69},"Groups, files & dynamic registration","\u002Ftools\u002Fgroups-organization","2.tools\u002F4.groups-organization","i-lucide-tags",{"title":71,"path":72,"stem":73,"children":74,"page":39},"Resources","\u002Fresources","3.resources",[75,79,84,89,94],{"title":46,"path":76,"stem":77,"icon":78},"\u002Fresources\u002Foverview","3.resources\u002F0.overview","i-lucide-package",{"title":80,"path":81,"stem":82,"icon":83},"Static resources & structure","\u002Fresources\u002Fstatic-and-structure","3.resources\u002F1.static-and-structure","i-lucide-file-stack",{"title":85,"path":86,"stem":87,"icon":88},"Templates & handlers","\u002Fresources\u002Ftemplates-and-handlers","3.resources\u002F2.templates-and-handlers","i-lucide-git-branch",{"title":90,"path":91,"stem":92,"icon":93},"Metadata, content & errors","\u002Fresources\u002Fcontent-metadata-errors","3.resources\u002F3.content-metadata-errors","i-lucide-layers",{"title":95,"path":96,"stem":97,"icon":98},"Groups & organization","\u002Fresources\u002Forganization","3.resources\u002F4.organization","i-lucide-folder-tree",{"title":100,"path":101,"stem":102,"children":103,"page":39},"Prompts","\u002Fprompts","4.prompts",[104,108,113,117],{"title":46,"path":105,"stem":106,"icon":107},"\u002Fprompts\u002Foverview","4.prompts\u002F0.overview","i-lucide-message-square",{"title":109,"path":110,"stem":111,"icon":112},"Authoring & structure","\u002Fprompts\u002Fauthoring","4.prompts\u002F1.authoring","i-lucide-pen-line",{"title":114,"path":115,"stem":116,"icon":93},"Input, handler & messages","\u002Fprompts\u002Finput-handler-messages","4.prompts\u002F2.input-handler-messages",{"title":118,"path":119,"stem":120,"icon":121},"Patterns & advanced","\u002Fprompts\u002Fpatterns-advanced","4.prompts\u002F3.patterns-advanced","i-lucide-line-chart",{"title":123,"path":124,"stem":125,"children":126,"page":39},"Handlers","\u002Fhandlers","5.handlers",[127,131,136,141,146],{"title":46,"path":128,"stem":129,"icon":130},"\u002Fhandlers\u002Foverview","5.handlers\u002F0.overview","i-lucide-server",{"title":132,"path":133,"stem":134,"icon":135},"Default & custom handlers","\u002Fhandlers\u002Fdefault-and-custom","5.handlers\u002F1.default-and-custom","i-lucide-toggle-left",{"title":137,"path":138,"stem":139,"icon":140},"Structure & options","\u002Fhandlers\u002Fstructure-and-options","5.handlers\u002F2.structure-and-options","i-lucide-sliders-horizontal",{"title":142,"path":143,"stem":144,"icon":145},"Examples & routing","\u002Fhandlers\u002Fexamples-routing","5.handlers\u002F3.examples-routing","i-lucide-route",{"title":147,"path":148,"stem":149,"icon":150},"Sharing & practices","\u002Fhandlers\u002Fsharing-practices","5.handlers\u002F4.sharing-practices","i-lucide-share-2",{"title":152,"path":153,"stem":154,"children":155,"page":39},"Apps","\u002Fapps","6.apps",[156,160,165,170,174,179],{"title":46,"path":157,"stem":158,"icon":159},"\u002Fapps\u002Foverview","6.apps\u002F0.overview","i-lucide-app-window",{"title":161,"path":162,"stem":163,"icon":164},"Authoring & defineMcpApp","\u002Fapps\u002Fauthoring","6.apps\u002F1.authoring","i-lucide-code-2",{"title":166,"path":167,"stem":168,"icon":169},"useMcpApp() bridge","\u002Fapps\u002Fuse-mcp-app","6.apps\u002F2.use-mcp-app","i-lucide-message-circle",{"title":171,"path":172,"stem":173,"icon":64},"CSP & build pipeline","\u002Fapps\u002Fcsp-and-wiring","6.apps\u002F3.csp-and-wiring",{"title":175,"path":176,"stem":177,"icon":178},"Testing & publishing","\u002Fapps\u002Ftesting-publishing","6.apps\u002F4.testing-publishing","i-lucide-rocket",{"title":180,"path":181,"stem":182,"icon":183},"Patterns & limits","\u002Fapps\u002Fpatterns-reference","6.apps\u002F5.patterns-reference","i-lucide-book-marked",{"title":185,"path":186,"stem":187,"children":188,"page":39},"Advanced Topics","\u002Fadvanced","7.advanced",[189,194,199,204,208,213,218,223,228,233,238],{"title":190,"path":191,"stem":192,"icon":193},"Custom Paths","\u002Fadvanced\u002Fcustom-paths","7.advanced\u002F1.custom-paths","i-lucide-folder",{"title":195,"path":196,"stem":197,"icon":198},"Logging","\u002Fadvanced\u002Flogging","7.advanced\u002F10.logging","i-lucide-scroll-text",{"title":200,"path":201,"stem":202,"icon":203},"MCP Apps Internals","\u002Fadvanced\u002Fmcp-apps-internals","7.advanced\u002F11.mcp-apps-internals","i-lucide-cog",{"title":205,"path":206,"stem":207,"icon":64},"Middleware","\u002Fadvanced\u002Fmiddleware","7.advanced\u002F2.middleware",{"title":209,"path":210,"stem":211,"icon":212},"TypeScript","\u002Fadvanced\u002Ftypescript","7.advanced\u002F3.typescript","i-lucide-type",{"title":214,"path":215,"stem":216,"icon":217},"Hooks","\u002Fadvanced\u002Fhooks","7.advanced\u002F4.hooks","i-lucide-webhook",{"title":219,"path":220,"stem":221,"icon":222},"MCP Evals","\u002Fadvanced\u002Fevals","7.advanced\u002F5.evals","i-lucide-flask-conical",{"title":224,"path":225,"stem":226,"icon":227},"Sessions","\u002Fadvanced\u002Fsessions","7.advanced\u002F6.sessions","i-lucide-database",{"title":229,"path":230,"stem":231,"icon":232},"Dynamic Definitions","\u002Fadvanced\u002Fdynamic-definitions","7.advanced\u002F7.dynamic-definitions","i-lucide-toggle-right",{"title":234,"path":235,"stem":236,"icon":237},"Code Mode","\u002Fadvanced\u002Fcode-mode","7.advanced\u002F8.code-mode","i-lucide-code",{"title":239,"path":240,"stem":241,"icon":242},"Elicitation","\u002Fadvanced\u002Felicitation","7.advanced\u002F9.elicitation","i-lucide-message-square-quote",{"title":244,"path":245,"stem":246,"children":247,"page":39},"Examples","\u002Fexamples","8.examples",[248,253,258,263,268],{"title":249,"path":250,"stem":251,"icon":252},"Authentication","\u002Fexamples\u002Fauthentication","8.examples\u002F1.authentication","i-lucide-shield-check",{"title":254,"path":255,"stem":256,"icon":257},"API Integration","\u002Fexamples\u002Fapi-integration","8.examples\u002F2.api-integration","i-lucide-globe",{"title":259,"path":260,"stem":261,"icon":262},"Common Patterns","\u002Fexamples\u002Fcommon-patterns","8.examples\u002F3.common-patterns","i-lucide-lightbulb",{"title":264,"path":265,"stem":266,"icon":267},"File Operations","\u002Fexamples\u002Ffile-operations","8.examples\u002F4.file-operations","i-lucide-file",{"title":269,"path":270,"stem":271,"icon":107},"Prompt Examples","\u002Fexamples\u002Fprompt-examples","8.examples\u002F5.prompt-examples",{"id":273,"title":200,"body":274,"description":1682,"extension":1683,"links":1684,"meta":1685,"navigation":1686,"path":201,"seo":1687,"stem":202,"__hash__":1690},"docs\u002F7.advanced\u002F11.mcp-apps-internals.md",{"type":275,"value":276,"toc":1662},"minimark",[277,286,291,304,370,373,416,430,435,438,533,552,556,567,626,632,639,754,758,761,765,772,801,804,903,914,918,921,939,942,946,957,965,972,981,1116,1129,1133,1137,1151,1188,1203,1207,1210,1445,1458,1462,1469,1562,1565,1569,1583,1598,1610,1614,1658],[278,279,280,281,285],"p",{},"This page covers the moving parts behind ",[282,283,284],"a",{"href":157},"MCP Apps",": the build pipeline, the host bridge, the security model, and patterns you can compose on top.",[287,288,290],"h2",{"id":289},"build-pipeline","Build Pipeline",[278,292,293,294,298,299,303],{},"For each ",[295,296,297],"code",{},"app\u002Fmcp\u002F*.vue"," file, the Nuxt module emits ",[300,301,302],"strong",{},"three artifacts"," at build time and registers them on the configured handler:",[305,306,311],"pre",{"className":307,"code":308,"language":309,"meta":310,"style":310},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight",".nuxt\u002Fmcp-apps\u002F\n├── color-picker.app.ts       # McpAppDefinition (the parsed defineMcpApp call)\n├── color-picker.tool.ts      # McpToolDefinition wrapping the app\n├── color-picker.resource.ts  # McpResourceDefinition serving the HTML\n└── color-picker.html         # Single-file Vue bundle (vite-plugin-singlefile)\n","bash","",[295,312,313,322,336,347,358],{"__ignoreMap":310},[314,315,318],"span",{"class":316,"line":317},"line",1,[314,319,321],{"class":320},"sBMFI",".nuxt\u002Fmcp-apps\u002F\n",[314,323,325,328,332],{"class":316,"line":324},2,[314,326,327],{"class":320},"├──",[314,329,331],{"class":330},"sfazB"," color-picker.app.ts",[314,333,335],{"class":334},"sHwdD","       # McpAppDefinition (the parsed defineMcpApp call)\n",[314,337,339,341,344],{"class":316,"line":338},3,[314,340,327],{"class":320},[314,342,343],{"class":330}," color-picker.tool.ts",[314,345,346],{"class":334},"      # McpToolDefinition wrapping the app\n",[314,348,350,352,355],{"class":316,"line":349},4,[314,351,327],{"class":320},[314,353,354],{"class":330}," color-picker.resource.ts",[314,356,357],{"class":334},"  # McpResourceDefinition serving the HTML\n",[314,359,361,364,367],{"class":316,"line":360},5,[314,362,363],{"class":320},"└──",[314,365,366],{"class":330}," color-picker.html",[314,368,369],{"class":334},"         # Single-file Vue bundle (vite-plugin-singlefile)\n",[278,371,372],{},"The pipeline runs in three phases:",[374,375,376,395,410],"ol",{},[377,378,379,382,383,386,387,390,391,394],"li",{},[300,380,381],{},"Parse"," — extract the ",[295,384,385],{},"defineMcpApp({ … })"," call from ",[295,388,389],{},"\u003Cscript setup>"," and pull out only the imports that are referenced inside the macro arguments. The macro is then ",[300,392,393],{},"stripped"," from the browser bundle.",[377,396,397,400,401,409],{},[300,398,399],{},"Bundle"," — call Vite programmatically with ",[282,402,406],{"href":403,"rel":404},"https:\u002F\u002Fgithub.com\u002Frichardtallent\u002Fvite-plugin-singlefile",[405],"nofollow",[295,407,408],{},"vite-plugin-singlefile"," to produce one self-contained HTML file (Vue runtime, your code, scoped CSS, assets) per SFC.",[377,411,412,415],{},[300,413,414],{},"Emit"," — write the three TypeScript files plus the HTML, then add them to Nuxt's auto-import + handler registration so they behave like any other tool \u002F resource.",[417,418,421,422,425,426,429],"callout",{"color":419,"icon":420},"info","i-lucide-info","Output lives under ",[295,423,424],{},"\u003CbuildDir>\u002Fmcp-apps\u002F",". It's regenerated on every build, and the dev server watches ",[295,427,428],{},"app\u002Fmcp\u002F**"," so changes hot-reload.",[431,432,434],"h3",{"id":433},"what-gets-inlined-into-the-html","What Gets Inlined Into The HTML",[278,436,437],{},"When the LLM calls the tool, the toolkit takes the bundled HTML and injects:",[305,439,443],{"className":440,"code":441,"language":442,"meta":310,"style":310},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cmeta http-equiv=\"Content-Security-Policy\" content=\"…\">\n\u003Cscript type=\"application\u002Fjson\" id=\"__mcp_app_data__\">\n  { \"base\": \"#2563eb\", \"swatches\": [ … ] }\n\u003C\u002Fscript>\n","html",[295,444,445,485,518,524],{"__ignoreMap":310},[314,446,447,451,455,459,462,465,468,470,473,475,477,480,482],{"class":316,"line":317},[314,448,450],{"class":449},"sMK4o","\u003C",[314,452,454],{"class":453},"swJcz","meta",[314,456,458],{"class":457},"spNyl"," http-equiv",[314,460,461],{"class":449},"=",[314,463,464],{"class":449},"\"",[314,466,467],{"class":330},"Content-Security-Policy",[314,469,464],{"class":449},[314,471,472],{"class":457}," content",[314,474,461],{"class":449},[314,476,464],{"class":449},[314,478,479],{"class":330},"…",[314,481,464],{"class":449},[314,483,484],{"class":449},">\n",[314,486,487,489,492,495,497,499,502,504,507,509,511,514,516],{"class":316,"line":324},[314,488,450],{"class":449},[314,490,491],{"class":453},"script",[314,493,494],{"class":457}," type",[314,496,461],{"class":449},[314,498,464],{"class":449},[314,500,501],{"class":330},"application\u002Fjson",[314,503,464],{"class":449},[314,505,506],{"class":457}," id",[314,508,461],{"class":449},[314,510,464],{"class":449},[314,512,513],{"class":330},"__mcp_app_data__",[314,515,464],{"class":449},[314,517,484],{"class":449},[314,519,520],{"class":316,"line":338},[314,521,523],{"class":522},"sTEyZ","  { \"base\": \"#2563eb\", \"swatches\": [ … ] }\n",[314,525,526,529,531],{"class":316,"line":349},[314,527,528],{"class":449},"\u003C\u002F",[314,530,491],{"class":453},[314,532,484],{"class":449},[278,534,535,536,539,540,543,544,547,548,551],{},"The first ",[295,537,538],{},"useMcpApp()"," call reads ",[295,541,542],{},"#__mcp_app_data__"," synchronously, so ",[295,545,546],{},"data.value"," is ",[300,549,550],{},"already populated on the first paint"," — no fetch, no waterfall.",[287,553,555],{"id":554},"the-host-bridge","The Host Bridge",[278,557,558,559,562,563,566],{},"The iframe and the host communicate over ",[295,560,561],{},"postMessage"," using a JSON-RPC 2.0 envelope. The toolkit ships a singleton ",[295,564,565],{},"useHostBridge()"," (internal) that:",[374,568,569,580,590,604,615],{},[377,570,571,572,575,576,579],{},"Performs the ",[295,573,574],{},"ui\u002Finitialize"," handshake to negotiate capabilities and receive ",[295,577,578],{},"HostContext",".",[377,581,582,583,586,587,579],{},"Routes incoming ",[295,584,585],{},"tool-result"," messages back into ",[295,588,589],{},"data",[377,591,592,593,596,597,596,600,603],{},"Dispatches outbound ",[295,594,595],{},"callTool",", ",[295,598,599],{},"prompt",[295,601,602],{},"openLink"," requests to the host.",[377,605,606,607,610,611,614],{},"Falls back to the legacy ",[295,608,609],{},"mcp-ui"," envelope (",[295,612,613],{},"{ type, payload }",") when talking to older hosts.",[377,616,617,618,621,622,625],{},"Detects the ",[300,619,620],{},"ChatGPT Apps SDK"," (",[295,623,624],{},"window.openai",") and uses its native APIs when available.",[278,627,628,629,631],{},"You don't talk to it directly — ",[295,630,538],{}," composes the public surface.",[278,633,634,635,638],{},"The full round-trip when the LLM calls ",[295,636,637],{},"color-picker",":",[374,640,641,650,663,673,679,693,702,719,732,740],{},[377,642,643,646,647,579],{},[300,644,645],{},"Host → Server"," — ",[295,648,649],{},"tools\u002Fcall color-picker { base }",[377,651,652,655,656,659,660,579],{},[300,653,654],{},"Server"," — runs ",[295,657,658],{},"handler()"," and produces ",[295,661,662],{},"structuredContent",[377,664,665,668,669,672],{},[300,666,667],{},"Server → Host"," — returns the bundled HTML with the data inlined and a ",[295,670,671],{},"ui:\u002F\u002F"," resource reference.",[377,674,675,678],{},[300,676,677],{},"Host → Iframe"," — mounts the iframe inline, sandboxed.",[377,680,681,646,684,686,687,543,690,692],{},[300,682,683],{},"Iframe",[295,685,538],{}," reads the inline ",[295,688,689],{},"\u003Cscript id=\"__mcp_app_data__\">",[295,691,589],{}," is populated on first paint.",[377,694,695,698,699,701],{},[300,696,697],{},"Iframe → Host"," — sends ",[295,700,574],{}," to negotiate capabilities.",[377,703,704,706,707,621,709,596,712,596,715,718],{},[300,705,677],{}," — replies with ",[295,708,578],{},[295,710,711],{},"theme",[295,713,714],{},"displayMode",[295,716,717],{},"containerDimensions",", …).",[377,720,721,723,724,646,728,731],{},[300,722,697],{}," ",[725,726,727],"em",{},"(optional)",[295,729,730],{},"ui\u002FcallTool { name, params }"," for in-place refreshes.",[377,733,734,736,737,579],{},[300,735,645],{}," — forwards the call as ",[295,738,739],{},"tools\u002Fcall name { params }",[377,741,742,745,746,748,749,751,752,579],{},[300,743,744],{},"Server → Host → Iframe"," — new ",[295,747,662],{}," flows back as a ",[295,750,585],{}," and replaces ",[295,753,589],{},[287,755,757],{"id":756},"security-model","Security Model",[278,759,760],{},"MCP Apps run in a sandboxed iframe loaded from the same origin as your MCP endpoint. The toolkit hardens the surface in three layers.",[431,762,764],{"id":763},"_1-default-csp","1. Default CSP",[278,766,767,768,771],{},"Every app HTML gets a CSP ",[295,769,770],{},"\u003Cmeta>"," that:",[773,774,775,778,785],"ul",{},[377,776,777],{},"Blocks all third-party scripts. Only the inline bundle script may execute.",[377,779,780,781,784],{},"Blocks ",[295,782,783],{},"\u003Cform>"," action targets.",[377,786,787,788,596,791,596,794,596,797,800],{},"Disallows ",[295,789,790],{},"connect-src",[295,792,793],{},"img-src",[295,795,796],{},"style-src",[295,798,799],{},"font-src"," external origins until you explicitly allow them.",[278,802,803],{},"You opt into external resources per app:",[305,805,809],{"className":806,"code":807,"language":808,"meta":310,"style":310},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","defineMcpApp({\n  csp: {\n    resourceDomains: ['https:\u002F\u002Fimages.example.com'], \u002F\u002F img \u002F style \u002F font \u002F link\n    connectDomains: ['https:\u002F\u002Fapi.example.com'],     \u002F\u002F fetch \u002F XHR \u002F WebSocket\n  },\n  \u002F\u002F …\n})\n","ts",[295,810,811,823,833,860,883,888,894],{"__ignoreMap":310},[314,812,813,817,820],{"class":316,"line":317},[314,814,816],{"class":815},"s2Zo4","defineMcpApp",[314,818,819],{"class":522},"(",[314,821,822],{"class":449},"{\n",[314,824,825,828,830],{"class":316,"line":324},[314,826,827],{"class":453},"  csp",[314,829,638],{"class":449},[314,831,832],{"class":449}," {\n",[314,834,835,838,840,843,846,849,851,854,857],{"class":316,"line":338},[314,836,837],{"class":453},"    resourceDomains",[314,839,638],{"class":449},[314,841,842],{"class":522}," [",[314,844,845],{"class":449},"'",[314,847,848],{"class":330},"https:\u002F\u002Fimages.example.com",[314,850,845],{"class":449},[314,852,853],{"class":522},"]",[314,855,856],{"class":449},",",[314,858,859],{"class":334}," \u002F\u002F img \u002F style \u002F font \u002F link\n",[314,861,862,865,867,869,871,874,876,878,880],{"class":316,"line":349},[314,863,864],{"class":453},"    connectDomains",[314,866,638],{"class":449},[314,868,842],{"class":522},[314,870,845],{"class":449},[314,872,873],{"class":330},"https:\u002F\u002Fapi.example.com",[314,875,845],{"class":449},[314,877,853],{"class":522},[314,879,856],{"class":449},[314,881,882],{"class":334},"     \u002F\u002F fetch \u002F XHR \u002F WebSocket\n",[314,884,885],{"class":316,"line":360},[314,886,887],{"class":449},"  },\n",[314,889,891],{"class":316,"line":890},6,[314,892,893],{"class":334},"  \u002F\u002F …\n",[314,895,897,900],{"class":316,"line":896},7,[314,898,899],{"class":449},"}",[314,901,902],{"class":522},")\n",[278,904,905,906,909,910,913],{},"The same allow-list is mirrored into ",[295,907,908],{},"_meta.ui.csp"," and ",[295,911,912],{},"_meta['openai\u002FwidgetCSP']"," for hosts that enforce CSP themselves.",[431,915,917],{"id":916},"_2-domain-validation","2. Domain Validation",[278,919,920],{},"CSP origins are validated at build time. The toolkit rejects:",[773,922,923,926,936],{},[377,924,925],{},"Non-string or empty values.",[377,927,928,929,932,933,579],{},"URL schemes other than ",[295,930,931],{},"http(s):\u002F\u002F"," or ",[295,934,935],{},"ws(s):\u002F\u002F",[377,937,938],{},"Strings that contain quotes, semicolons, or whitespace.",[278,940,941],{},"If a domain looks suspicious, the build fails — you can't accidentally ship an injection vector via misconfiguration.",[431,943,945],{"id":944},"_3-iframe-isolation","3. Iframe Isolation",[278,947,948,949,952,953,956],{},"The iframe runs as if it were a third-party page on your origin: no cookies, no ",[295,950,951],{},"localStorage"," from the parent app, no shared module graph. This is ",[300,954,955],{},"by design"," — apps must declare what they need, and they cannot reach into the parent Nuxt runtime.",[417,958,960,961,964],{"color":959,"icon":64},"warning","Pass ",[295,962,963],{},"csp: false"," only when you fully control every byte the iframe loads. Stripping the CSP turns off the only line of defense against compromised dependencies.",[287,966,968,969],{"id":967},"custom-_meta","Custom ",[295,970,971],{},"_meta",[278,973,974,975,978,979,638],{},"The handler returns a regular MCP ",[295,976,977],{},"CallToolResult",", so you can attach any host-specific metadata via ",[295,980,971],{},[305,982,984],{"className":806,"code":983,"language":808,"meta":310,"style":310},"defineMcpApp({\n  _meta: {\n    'openai\u002FwidgetAccessible': true,\n    'openai\u002FtoolInvocation\u002Finvoking': 'Loading stays…',\n    'openai\u002FtoolInvocation\u002Finvoked': 'Stays loaded',\n  },\n  handler: async () => ({ structuredContent: { … } }),\n})\n",[295,985,986,994,1003,1022,1043,1063,1067,1109],{"__ignoreMap":310},[314,987,988,990,992],{"class":316,"line":317},[314,989,816],{"class":815},[314,991,819],{"class":522},[314,993,822],{"class":449},[314,995,996,999,1001],{"class":316,"line":324},[314,997,998],{"class":453},"  _meta",[314,1000,638],{"class":449},[314,1002,832],{"class":449},[314,1004,1005,1008,1011,1013,1015,1019],{"class":316,"line":338},[314,1006,1007],{"class":449},"    '",[314,1009,1010],{"class":453},"openai\u002FwidgetAccessible",[314,1012,845],{"class":449},[314,1014,638],{"class":449},[314,1016,1018],{"class":1017},"sfNiH"," true",[314,1020,1021],{"class":449},",\n",[314,1023,1024,1026,1029,1031,1033,1036,1039,1041],{"class":316,"line":349},[314,1025,1007],{"class":449},[314,1027,1028],{"class":453},"openai\u002FtoolInvocation\u002Finvoking",[314,1030,845],{"class":449},[314,1032,638],{"class":449},[314,1034,1035],{"class":449}," '",[314,1037,1038],{"class":330},"Loading stays…",[314,1040,845],{"class":449},[314,1042,1021],{"class":449},[314,1044,1045,1047,1050,1052,1054,1056,1059,1061],{"class":316,"line":360},[314,1046,1007],{"class":449},[314,1048,1049],{"class":453},"openai\u002FtoolInvocation\u002Finvoked",[314,1051,845],{"class":449},[314,1053,638],{"class":449},[314,1055,1035],{"class":449},[314,1057,1058],{"class":330},"Stays loaded",[314,1060,845],{"class":449},[314,1062,1021],{"class":449},[314,1064,1065],{"class":316,"line":890},[314,1066,887],{"class":449},[314,1068,1069,1072,1074,1077,1080,1083,1085,1088,1091,1093,1096,1099,1101,1104,1107],{"class":316,"line":896},[314,1070,1071],{"class":815},"  handler",[314,1073,638],{"class":449},[314,1075,1076],{"class":457}," async",[314,1078,1079],{"class":449}," ()",[314,1081,1082],{"class":457}," =>",[314,1084,621],{"class":522},[314,1086,1087],{"class":449},"{",[314,1089,1090],{"class":453}," structuredContent",[314,1092,638],{"class":449},[314,1094,1095],{"class":449}," {",[314,1097,1098],{"class":522}," … ",[314,1100,899],{"class":449},[314,1102,1103],{"class":449}," }",[314,1105,1106],{"class":522},")",[314,1108,1021],{"class":449},[314,1110,1112,1114],{"class":316,"line":1111},8,[314,1113,899],{"class":449},[314,1115,902],{"class":522},[278,1117,1118,1119,1122,1123,1125,1126,1128],{},"The toolkit auto-fills ",[295,1120,1121],{},"_meta.ui.resourceUri"," (so hosts can re-fetch the HTML on demand) and ",[295,1124,908],{},". Anything you put in ",[295,1127,971],{}," is merged on top.",[287,1130,1132],{"id":1131},"advanced-patterns","Advanced Patterns",[431,1134,1136],{"id":1135},"re-using-server-logic","Re-using server logic",[278,1138,1139,1140,596,1143,1146,1147,1150],{},"Apps share ",[295,1141,1142],{},"server\u002Fapi\u002F",[295,1144,1145],{},"server\u002Futils\u002F",", and ",[295,1148,1149],{},"shared\u002F"," with the rest of your Nuxt app. A typical layout:",[305,1152,1154],{"className":307,"code":1153,"language":309,"meta":310,"style":310},"app\u002Fmcp\u002Fcolor-picker.vue          # UI + handler that calls $fetch('\u002Fapi\u002Fpalette')\nserver\u002Fapi\u002Fpalette.get.ts         # The actual data endpoint (callable by humans + tools)\nserver\u002Futils\u002Fpalette.ts           # Shared generators \u002F helpers\nshared\u002Ftypes\u002Fpalette.ts           # Types auto-imported by both the SFC and the endpoint\n",[295,1155,1156,1164,1172,1180],{"__ignoreMap":310},[314,1157,1158,1161],{"class":316,"line":317},[314,1159,1160],{"class":320},"app\u002Fmcp\u002Fcolor-picker.vue",[314,1162,1163],{"class":334},"          # UI + handler that calls $fetch('\u002Fapi\u002Fpalette')\n",[314,1165,1166,1169],{"class":316,"line":324},[314,1167,1168],{"class":320},"server\u002Fapi\u002Fpalette.get.ts",[314,1170,1171],{"class":334},"         # The actual data endpoint (callable by humans + tools)\n",[314,1173,1174,1177],{"class":316,"line":338},[314,1175,1176],{"class":320},"server\u002Futils\u002Fpalette.ts",[314,1178,1179],{"class":334},"           # Shared generators \u002F helpers\n",[314,1181,1182,1185],{"class":316,"line":349},[314,1183,1184],{"class":320},"shared\u002Ftypes\u002Fpalette.ts",[314,1186,1187],{"class":334},"           # Types auto-imported by both the SFC and the endpoint\n",[278,1189,1190,1191,1194,1195,1198,1199,1202],{},"A regular Nuxt page, an external client, or the MCP App handler all hit ",[295,1192,1193],{},"\u002Fapi\u002Fpalette"," with the exact same contract — and types under ",[295,1196,1197],{},"shared\u002Ftypes\u002F"," resolve globally without an ",[295,1200,1201],{},"import"," statement.",[431,1204,1206],{"id":1205},"multiple-handlers","Multiple handlers",[278,1208,1209],{},"Isolate apps from your other tools by giving them their own MCP endpoint:",[305,1211,1214],{"className":806,"code":1212,"filename":1213,"language":808,"meta":310,"style":310},"import { defineMcpHandler } from '@nuxtjs\u002Fmcp-toolkit\u002Fserver'\nimport { tools as allTools } from '#nuxt-mcp-toolkit\u002Ftools.mjs'\nimport { resources as allResources } from '#nuxt-mcp-toolkit\u002Fresources.mjs'\n\nconst isAppDef = (def: { _meta?: Record\u003Cstring, unknown> }) => def._meta?.group === 'apps'\n\nexport default defineMcpHandler({\n  route: '\u002Fmcp\u002Fapps',\n  tools: allTools.filter(isAppDef),\n  resources: allResources.filter(isAppDef),\n})\n","server\u002Fmcp\u002Fapps.ts",[295,1215,1216,1239,1265,1290,1296,1366,1370,1384,1400,1420,1438],{"__ignoreMap":310},[314,1217,1218,1221,1223,1226,1228,1231,1233,1236],{"class":316,"line":317},[314,1219,1201],{"class":1220},"s7zQu",[314,1222,1095],{"class":449},[314,1224,1225],{"class":522}," defineMcpHandler",[314,1227,1103],{"class":449},[314,1229,1230],{"class":1220}," from",[314,1232,1035],{"class":449},[314,1234,1235],{"class":330},"@nuxtjs\u002Fmcp-toolkit\u002Fserver",[314,1237,1238],{"class":449},"'\n",[314,1240,1241,1243,1245,1248,1251,1254,1256,1258,1260,1263],{"class":316,"line":324},[314,1242,1201],{"class":1220},[314,1244,1095],{"class":449},[314,1246,1247],{"class":522}," tools",[314,1249,1250],{"class":1220}," as",[314,1252,1253],{"class":522}," allTools",[314,1255,1103],{"class":449},[314,1257,1230],{"class":1220},[314,1259,1035],{"class":449},[314,1261,1262],{"class":330},"#nuxt-mcp-toolkit\u002Ftools.mjs",[314,1264,1238],{"class":449},[314,1266,1267,1269,1271,1274,1276,1279,1281,1283,1285,1288],{"class":316,"line":338},[314,1268,1201],{"class":1220},[314,1270,1095],{"class":449},[314,1272,1273],{"class":522}," resources",[314,1275,1250],{"class":1220},[314,1277,1278],{"class":522}," allResources",[314,1280,1103],{"class":449},[314,1282,1230],{"class":1220},[314,1284,1035],{"class":449},[314,1286,1287],{"class":330},"#nuxt-mcp-toolkit\u002Fresources.mjs",[314,1289,1238],{"class":449},[314,1291,1292],{"class":316,"line":349},[314,1293,1295],{"emptyLinePlaceholder":1294},true,"\n",[314,1297,1298,1301,1304,1306,1308,1312,1314,1316,1319,1322,1325,1327,1330,1332,1335,1338,1341,1343,1346,1348,1350,1353,1356,1359,1361,1364],{"class":316,"line":360},[314,1299,1300],{"class":457},"const",[314,1302,1303],{"class":522}," isAppDef ",[314,1305,461],{"class":449},[314,1307,621],{"class":449},[314,1309,1311],{"class":1310},"sHdIc","def",[314,1313,638],{"class":449},[314,1315,1095],{"class":449},[314,1317,1318],{"class":453}," _meta",[314,1320,1321],{"class":449},"?:",[314,1323,1324],{"class":320}," Record",[314,1326,450],{"class":449},[314,1328,1329],{"class":320},"string",[314,1331,856],{"class":449},[314,1333,1334],{"class":320}," unknown",[314,1336,1337],{"class":449},">",[314,1339,1340],{"class":449}," })",[314,1342,1082],{"class":457},[314,1344,1345],{"class":522}," def",[314,1347,579],{"class":449},[314,1349,971],{"class":522},[314,1351,1352],{"class":449},"?.",[314,1354,1355],{"class":522},"group ",[314,1357,1358],{"class":449},"===",[314,1360,1035],{"class":449},[314,1362,1363],{"class":330},"apps",[314,1365,1238],{"class":449},[314,1367,1368],{"class":316,"line":890},[314,1369,1295],{"emptyLinePlaceholder":1294},[314,1371,1372,1375,1378,1380,1382],{"class":316,"line":896},[314,1373,1374],{"class":1220},"export",[314,1376,1377],{"class":1220}," default",[314,1379,1225],{"class":815},[314,1381,819],{"class":522},[314,1383,822],{"class":449},[314,1385,1386,1389,1391,1393,1396,1398],{"class":316,"line":1111},[314,1387,1388],{"class":453},"  route",[314,1390,638],{"class":449},[314,1392,1035],{"class":449},[314,1394,1395],{"class":330},"\u002Fmcp\u002Fapps",[314,1397,845],{"class":449},[314,1399,1021],{"class":449},[314,1401,1403,1406,1408,1410,1412,1415,1418],{"class":316,"line":1402},9,[314,1404,1405],{"class":453},"  tools",[314,1407,638],{"class":449},[314,1409,1253],{"class":522},[314,1411,579],{"class":449},[314,1413,1414],{"class":815},"filter",[314,1416,1417],{"class":522},"(isAppDef)",[314,1419,1021],{"class":449},[314,1421,1423,1426,1428,1430,1432,1434,1436],{"class":316,"line":1422},10,[314,1424,1425],{"class":453},"  resources",[314,1427,638],{"class":449},[314,1429,1278],{"class":522},[314,1431,579],{"class":449},[314,1433,1414],{"class":815},[314,1435,1417],{"class":522},[314,1437,1021],{"class":449},[314,1439,1441,1443],{"class":316,"line":1440},11,[314,1442,899],{"class":449},[314,1444,902],{"class":522},[278,1446,1447,1448,1451,1452,1455,1456,579],{},"Connect the host to ",[295,1449,1450],{},"https:\u002F\u002Fyour-app\u002Fmcp\u002Fapps"," to expose ",[300,1453,1454],{},"only"," the apps surface, separate from your back-office tools. See ",[282,1457,123],{"href":128},[431,1459,1461],{"id":1460},"per-host-adaptation","Per-host adaptation",[278,1463,1464,1465,1468],{},"Use ",[295,1466,1467],{},"hostContext"," to opt into host-specific affordances:",[305,1470,1472],{"className":806,"code":1471,"language":808,"meta":310,"style":310},"const isChatGpt = computed(() => typeof window !== 'undefined' && 'openai' in window)\nconst supportsFullscreen = computed(() => hostContext.value?.displayMode !== undefined)\n",[295,1473,1474,1525],{"__ignoreMap":310},[314,1475,1476,1478,1481,1483,1486,1488,1491,1493,1496,1499,1502,1504,1507,1509,1512,1514,1517,1519,1522],{"class":316,"line":317},[314,1477,1300],{"class":457},[314,1479,1480],{"class":522}," isChatGpt ",[314,1482,461],{"class":449},[314,1484,1485],{"class":815}," computed",[314,1487,819],{"class":522},[314,1489,1490],{"class":449},"()",[314,1492,1082],{"class":457},[314,1494,1495],{"class":449}," typeof",[314,1497,1498],{"class":522}," window ",[314,1500,1501],{"class":449},"!==",[314,1503,1035],{"class":449},[314,1505,1506],{"class":330},"undefined",[314,1508,845],{"class":449},[314,1510,1511],{"class":449}," &&",[314,1513,1035],{"class":449},[314,1515,1516],{"class":330},"openai",[314,1518,845],{"class":449},[314,1520,1521],{"class":449}," in",[314,1523,1524],{"class":522}," window)\n",[314,1526,1527,1529,1532,1534,1536,1538,1540,1542,1545,1547,1550,1552,1555,1557,1560],{"class":316,"line":324},[314,1528,1300],{"class":457},[314,1530,1531],{"class":522}," supportsFullscreen ",[314,1533,461],{"class":449},[314,1535,1485],{"class":815},[314,1537,819],{"class":522},[314,1539,1490],{"class":449},[314,1541,1082],{"class":457},[314,1543,1544],{"class":522}," hostContext",[314,1546,579],{"class":449},[314,1548,1549],{"class":522},"value",[314,1551,1352],{"class":449},[314,1553,1554],{"class":522},"displayMode ",[314,1556,1501],{"class":449},[314,1558,1559],{"class":449}," undefined",[314,1561,902],{"class":522},[278,1563,1564],{},"Avoid hard-coding behaviours per host whenever you can — the bridge already smooths over the major differences.",[431,1566,1568],{"id":1567},"testing-apps","Testing apps",[278,1570,1571,1572,1575,1576,1579,1580,1582],{},"Server-side: the ",[295,1573,1574],{},"handler"," is a plain async function. Import the parsed app definition from ",[295,1577,1578],{},".nuxt\u002Fmcp-apps\u002F\u003Cname>.app.ts"," (or import the SFC's ",[295,1581,816],{}," arguments via the parser) and call the handler directly with mock input.",[278,1584,1585,1586,1589,1590,1593,1594,1597],{},"Iframe-side: render the SFC with ",[295,1587,1588],{},"@vue\u002Ftest-utils"," and stub the host bridge by injecting ",[295,1591,1592],{},"window.parent.postMessage"," listeners. The toolkit's own test suite (",[295,1595,1596],{},"packages\u002Fnuxt-mcp-toolkit\u002Ftest\u002Fapps-handshake.test.ts",") shows the pattern.",[417,1599,1600,1601,1606,1607,1609],{"color":419,"icon":420},"Most regressions in MCP Apps come from forgetting that ",[300,1602,1603,1605],{},[295,1604,1574],{}," runs server-side and the template runs client-side",". Treat them as two halves of an API: one produces a contract (",[295,1608,662],{},"), the other consumes it.",[287,1611,1613],{"id":1612},"limits-footguns","Limits & Footguns",[773,1615,1616,1625,1633,1643,1649],{},[377,1617,1618,1621,1622,579],{},[300,1619,1620],{},"One handler per app."," If you need a second tool from the same UI, declare it elsewhere and call it via ",[295,1623,1624],{},"callTool('other-tool', …)",[377,1626,1627,1632],{},[300,1628,1629,1630],{},"No top-level await in ",[295,1631,389],{}," of an app — the macro must be statically analysable.",[377,1634,1635,1642],{},[300,1636,1637,1638,1641],{},"Only relative imports + auto-imports + the ",[295,1639,1640],{},"#shared"," alias"," in the SFC. Anything that pulls in the Nuxt runtime won't bundle.",[377,1644,1645,1648],{},[300,1646,1647],{},"Keep payloads small."," The data is inlined into the HTML; large payloads (>1 MB) noticeably slow first paint.",[377,1650,1651,1657],{},[300,1652,1653,1654,579],{},"Style with ",[295,1655,1656],{},"scoped"," Global styles leak across apps because every app loads its own copy of Vue's style runtime.",[1659,1660,1661],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":310,"searchDepth":324,"depth":324,"links":1663},[1664,1667,1668,1673,1675,1681],{"id":289,"depth":324,"text":290,"children":1665},[1666],{"id":433,"depth":338,"text":434},{"id":554,"depth":324,"text":555},{"id":756,"depth":324,"text":757,"children":1669},[1670,1671,1672],{"id":763,"depth":338,"text":764},{"id":916,"depth":338,"text":917},{"id":944,"depth":338,"text":945},{"id":967,"depth":324,"text":1674},"Custom _meta",{"id":1131,"depth":324,"text":1132,"children":1676},[1677,1678,1679,1680],{"id":1135,"depth":338,"text":1136},{"id":1205,"depth":338,"text":1206},{"id":1460,"depth":338,"text":1461},{"id":1567,"depth":338,"text":1568},{"id":1612,"depth":324,"text":1613},"How the toolkit bundles, serves, and connects MCP Apps — and the patterns you can build on top.","md",null,{},{"icon":203},{"title":1688,"description":1689},"MCP Apps Internals & Advanced Patterns","Understand the build pipeline, the host bridge protocol, security defaults, and the advanced patterns possible with MCP Apps in @nuxtjs\u002Fmcp-toolkit.","q8XUvtvY8yCDuwtHHmijCYkN1nzFVuxtsOmhEoSlUwY",[1692,1694],{"title":195,"path":196,"stem":197,"description":1693,"icon":198,"children":-1},"Stream logs to MCP clients and capture structured wide events with useMcpLogger().",{"title":205,"path":206,"stem":207,"description":1695,"icon":64,"children":-1},"Intercept MCP requests to add authentication, logging, analytics, and more.",1777306543558]