Testing & publishing
Host compatibility
| Host | Render | sendPrompt | callTool | openLink | Notes |
|---|---|---|---|---|---|
| Cursor | ✅ | ✅ | ✅ | ✅ | Tested with the JSON-RPC bridge and legacy ready/resize messages. |
| ChatGPT (Apps SDK) | ✅ | ⚠️ | ✅ | ✅ | Uses window.openai when available. Follow-ups are sent, but the next tool is not always rendered inline. |
| MCP UI / Inspector-style hosts | ✅ | varies | varies | varies | The bridge emits spec JSON-RPC messages plus legacy mcp-ui envelopes where useful. Verify each host before documenting support. |
The bridge auto-detects the host on handshake and adapts to its protocol — modern JSON-RPC for MCP UI hosts, the legacy mcp-ui envelope for older clients, the ChatGPT Apps SDK globals when present. Your code just calls useMcpApp().
Local test loop
MCP Apps use the same streamable HTTP endpoint as your tools, resources, and prompts. The default route is /mcp unless you set mcp.route in nuxt.config.
Start your Nuxt app:
pnpm dev
npm run dev
yarn dev
bun dev
Your local MCP URL is:
http://localhost:3000/mcp
Use the MCP Inspector, npx @modelcontextprotocol/inspector@latest, or the MCP panel in Nuxt DevTools first. Confirm that:
- the app SFC appears as a tool, for example
color-pickerorrelease-control; - calling the tool returns
structuredContent; - the tool result includes a
text/html;profile=mcp-appresource; - the resource URI looks like
ui://mcp-app/<name>.
Some clients can call the tool but cannot render the iframe. Use a host with MCP Apps support when validating the UI itself.
Test in ChatGPT with ngrok
ChatGPT requires a public HTTPS endpoint, even for local development. Tunnel your local Nuxt server:
ngrok http 3000
ngrok prints a forwarding URL:
Forwarding https://your-subdomain.ngrok-free.dev -> http://localhost:3000
Use the /mcp route on that origin:
https://your-subdomain.ngrok-free.dev/mcp
In ChatGPT:
- Enable Developer Mode in Settings → Apps → Advanced settings.
- Open Settings → Apps.
- Click Create app.
- Enter your public MCP URL, including
/mcp. - Enable the app and start a new chat.
- Select your app from the composer before prompting the model.
Mention the app with @ so ChatGPT routes the prompt through your connector:
@Weather App What is the weather in London?
Then test the iframe behaviour:
- click a button that calls
callTool()and confirm the widget updates without a new chat turn; - click a button that calls
sendPrompt()and confirm a follow-up message appears in the conversation; - click a button that calls
openLink()and confirm ChatGPT opens or prompts for the external URL; - watch your Nuxt terminal and ngrok dashboard for POST requests to
/mcp.
If you expose local dev through ngrok, allow the tunnel host in Vite:
export default defineNuxtConfig({
vite: {
server: {
allowedHosts: ['.ngrok-free.dev', '.ngrok.app'],
},
},
})
For cross-origin hosts such as ChatGPT, configure MCP origin checks intentionally:
export default defineNuxtConfig({
mcp: {
security: {
allowedOrigins: '*',
},
},
})
Use '*' only for demos or public read-only surfaces. For production apps with private data or real side effects, use a list of trusted origins.
Deploy to Vercel or another cloud
MCP Apps do not use a second URL. Deploy the Nuxt app and connect ChatGPT to the same MCP route:
https://your-project.vercel.app/mcp
The toolkit auto-detects _meta.ui.domain from common hosting variables:
NUXT_PUBLIC_APP_URL;- Vercel:
VERCEL_PROJECT_PRODUCTION_URL,VERCEL_URL; - Netlify:
URL; - Render:
RENDER_EXTERNAL_URL; - Railway:
RAILWAY_PUBLIC_DOMAIN; - Fly:
FLY_APP_NAME.
If you need to override the detected value, set it explicitly:
<script setup lang="ts">
defineMcpApp({
_meta: {
ui: {
domain: 'https://my-app.example.com',
},
},
})
</script>
For Vercel, make sure your build includes the MCP Apps bundling dependencies from the toolkit version you deploy. If you test an unreleased package, use a fresh pkg.pr.new build or install the required build dependencies in the app until the package is published.
Prepare for ChatGPT submission
OpenAI reviews apps before broad distribution. Before submitting, check:
- your MCP server is publicly reachable over HTTPS;
- each app resource returns
text/html;profile=mcp-app; - each app tool has clear
description,inputSchema, and behavior annotations; _meta.ui.resourceUriis present (the toolkit generates it);_meta.ui.domainis set or auto-detected;- CSP is explicit for external images, scripts, fonts, APIs, or iframes;
structuredContentis concise and safe for the model to read;- large or widget-only data goes in
_meta, not instructuredContent; - OAuth, privacy policy, app name, screenshots, and test prompts are ready if your app needs public distribution.
When the host exposes the Apps SDK (window.openai) or enforces widget CSP, the toolkit mirrors your csp to _meta.ui.csp and openai/widgetCSP. Behaviour, especially sendPrompt and follow-up re-renders, can change between ChatGPT releases. Re-test your app before submitting or announcing support.
Troubleshooting
403 Forbidden from /mcp
The request reached your server but failed an origin or host check.
- For ChatGPT/ngrok demos, set
mcp.security.allowedOriginsto'*'or to a trusted origin list. - For Vite dev server tunnels, add the tunnel domain to
vite.server.allowedHosts. - Restart the Nuxt dev server after changing
nuxt.config.ts.
Widget does not render
Check the tool result:
- The resource MIME type must be
text/html;profile=mcp-app. - The resource URI should match
_meta.ui.resourceUri. - The host must support MCP Apps. Some MCP clients can call the tool but only display text.
No ui/* messages arrive
The iframe loaded, but the host did not enable the MCP Apps bridge.
- Confirm the host renders the HTML as an MCP App resource, not as a plain file.
- Check the browser console inside the host devtools for CSP or sandbox errors.
- Test in Cursor or ChatGPT to compare host behaviour.
CSP blocks images or fetch calls
Add explicit allow-lists:
defineMcpApp({
csp: {
resourceDomains: ['https://images.example.com'],
connectDomains: ['https://api.example.com'],
},
})
The toolkit mirrors these to _meta.ui.csp and openai/widgetCSP for hosts that enforce widget CSP.
Vercel cannot find generated MCP App files
Use a toolkit version that includes the MCP Apps build fixes. Older unreleased builds may import .nuxt/mcp-apps/gen/*.ts at runtime, which does not exist inside the deployed Vercel function.
vite-plugin-singlefile or @vitejs/plugin-vue is missing
Use a toolkit version that ships the MCP Apps bundling dependencies, or install them in the app while testing an unreleased build.
ChatGPT keeps showing an old widget
ChatGPT can cache widget templates by URI. If you make a breaking UI change, change the resource URI or file name so hosts load a fresh template.
Other clients
Claude Code, Claude Desktop (where remote MCP is enabled), Cursor, VS Code, and Codex-style clients connect to your server with the same MCP URL you use for tools. There is no separate “apps-only” endpoint.
Web and mobile ChatGPT/Claude apps may not render MCP UI widgets on every release channel. Always verify inline iframes on the product version you support.
Prefer Cursor as the main development vehicle when debugging the JSON-RPC UI bridge; it is the most exercised environment in this module’s test suite.
Related
- CSP & build pipeline — how HTML and
_metaare produced. - Patterns & limits — iframe constraints and API cheat sheet.
- Handlers — optional
https://…/mcp/appssurface if you split apps from other tools. - OpenAI: Build your MCP server
- OpenAI: Connect from ChatGPT
- OpenAI: Test your integration
- OpenAI: Submit and maintain your app
- OpenAI: App submission guidelines