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