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