Apps

Testing & publishing

Test MCP Apps locally, pick a compatible host, and ship HTTPS endpoints for ChatGPT, Claude, and IDE clients.

Host compatibility

HostRendersendPromptcallToolopenLinkNotes
CursorTested 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 hostsvariesvariesvariesThe 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

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-picker or release-control;
  • calling the tool returns structuredContent;
  • the tool result includes a text/html;profile=mcp-app resource;
  • 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:

  1. Enable Developer Mode in SettingsAppsAdvanced settings.
  2. Open SettingsApps.
  3. Click Create app.
  4. Enter your public MCP URL, including /mcp.
  5. Enable the app and start a new chat.
  6. 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:

nuxt.config.ts
export default defineNuxtConfig({
  vite: {
    server: {
      allowedHosts: ['.ngrok-free.dev', '.ngrok.app'],
    },
  },
})

For cross-origin hosts such as ChatGPT, configure MCP origin checks intentionally:

nuxt.config.ts
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:

app/mcp/my-app.vue
<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.resourceUri is present (the toolkit generates it);
  • _meta.ui.domain is set or auto-detected;
  • CSP is explicit for external images, scripts, fonts, APIs, or iframes;
  • structuredContent is concise and safe for the model to read;
  • large or widget-only data goes in _meta, not in structuredContent;
  • 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.allowedOrigins to '*' 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.

Copyright © 2026