If you are building a ChatGPT MCP app, the server-side tool is usually the straightforward part: register a handler on Streamable HTTP, return text the model can use, and keep tools/list honest. What takes more wiring is the UI—getting HTML to show inline on a tool call, beside the conversation via a thread entrypoint, or from the sidebar via a global launcher. That means the right text/html;profile=mcp-app resource, _meta.ui.resourceUri, and optional "openai/ui" metadata—and because ChatGPT and Codex share MCP plugin tooling, you still cannot assume every client paints the same surfaces.
mustafa-ramax/chatgpt-mcp-app-starter is a minimal Next.js reference for that split: one read-only demo tool, one linked HTML resource, a single /api/mcp endpoint, and the standard versus extension entrypoints in one route file. It is MIT community code, not an official OpenAI product.
Tools, UI, and ChatGPT extensions
Three layers often get mixed up:
- MCP tools — structured actions the model can call. ChatGPT and Codex can attach many MCP servers through the shared plugin directory model; that shared tooling does not mean every client renders the same app UI.
- MCP Apps UI (standard) — an HTML resource with MIME type
text/html;profile=mcp-app, linked from a tool via_meta.ui.resourceUri. When the host supports MCP Apps, that link drives the inline widget tied to the tool (OpenAI documents this in the ChatGPT UI guide). The URI (for exampleui://starter/hello.html) is an internal MCP resource name resolved throughresources/read, not a public URL you paste into a browser. - Optional ChatGPT entrypoints — extra
"openai/ui"metadata on a tool advertises global (sidebar / fullscreen launcher) and thread (conversation side panel) surfaces in compatible ChatGPT builds. That is separate from the standard tool-linked widget; see plugin extensions.
The starter’s demo surfaces the same Hello World HTML in three places when a compatible ChatGPT build loads them:
Inline widget — MCP Apps standard: UI tied to the tool call via _meta.ui.resourceUri.

Conversation side panel — optional thread entrypoint beside the active conversation.

Sidebar app — optional global entrypoint from the left launcher.

Your server should stay useful without UI so headless workflows still work—OpenAI’s UI guide is explicit about that. The starter follows that: one read-only tool that returns text even if the iframe never loads.
Where it shows up: As of the official docs reviewed in October 2026, extension availability varies by ChatGPT surface and plan (for example, web extensions noted as coming soon on some tiers; composer-related UI described for desktop in places). Codex may use the same plugin directory for tools but is not documented as offering the same MCP App panels everywhere ChatGPT web/desktop does. Check extensions and ChatGPT MCP help for the client you use—do not assume parity across ChatGPT and Codex without verifying current docs.
What the starter contains
The repo is Next.js with a single Streamable HTTP MCP route at /api/mcp. The official app quickstart uses /mcp in its example URL; this starter mounts the handler under /api/mcp instead. Both are valid paths—you choose the route in your app.
Stack from package.json: Next.js 16.x, React 19, TypeScript, @modelcontextprotocol/ext-apps, mcp-handler, and Zod. Use Node.js 24 LTS for new work. Next.js requires Node 20.9 or later, but Node 20 is already end-of-life; the starter’s current engines field (>=20) is broader than Next.js actually supports.
Architecturally it is deliberately tiny:
- One UI resource at
ui://starter/hello.html— static HTML returned from the handler via MCPresources/read. - One app tool —
open_starter_hello, read-only, no external HTTP. - One HTTP endpoint —
app/api/mcp/route.tsexports the same handler forGETandPOST.
The metadata that wires the standard UI link and optional ChatGPT entrypoints lives next to the tool registration in that route:
_meta: {
ui: { resourceUri: UI_URI },
"openai/ui": { entrypoints: [{ type: "thread" }, { type: "global" }] },
},
thread targets the conversation side panel; global targets the left-sidebar style launcher described in OpenAI’s extensions overview. The shipped demo HTML is static—it does not implement the full interactive postMessage JSON-RPC bridge yet; that is the next step when you build a real interactive MCP App UI per the app quickstart.
Run it locally
From the repo root:
git clone https://github.com/mustafa-ramax/chatgpt-mcp-app-starter.git
cd chatgpt-mcp-app-starter
npm install
npm run dev
- Landing page:
http://localhost:3000 - MCP endpoint:
http://localhost:3000/api/mcp
No API keys, database, or upstream services are required for the demo. A successful open_starter_hello call should return text such as “Starter Hello World opened…” and structuredContent like { message: 'Hello from your MCP App!', stage: 'hello-world' }—that confirms the tool path; it does not by itself prove ChatGPT rendered the UI.
To exercise tools and resources without ChatGPT, use the Inspector from the quickstart:
npx @modelcontextprotocol/inspector@latest
Point it at http://localhost:3000/api/mcp, call open_starter_hello with an empty input object {} (the tool takes no arguments), and confirm the structured response matches the repo. UI rendering still depends on a host that implements MCP Apps.
Deploy and connect ChatGPT
Deploy the Next.js app to your infrastructure—any host that runs this stack cleanly (Vercel is the common choice; other Next-compatible platforms work if you configure the MCP route and HTTPS). You need a public HTTPS URL ending in /api/mcp. The demo ships without auth; usage limits, abuse, and any user-specific data are your responsibility once it is public. If your tools touch private data, add proper server-side authentication before you rely on it in production.
In ChatGPT, the current flow documented in the app quickstart is roughly: ChatGPT Plugins → + → Add custom MCP server → enter your endpoint and auth → review risk → Create as plugin → install → invoke via @ mention. Refresh plugin metadata after you change tools or UI URIs.
Private development does not require public directory approval; directory review is a separate process from “custom MCP server on my URL”.
Customise and troubleshoot
Change the tool — add inputs with Zod, keep readOnlyHint honest, and return content the model can use if the UI fails.
Change the UI — edit the HTML string (or serve files) and keep RESOURCE_MIME_TYPE plus _meta.ui.resourceUri aligned. Validate the resource through MCP resources/read in the Inspector, not by opening ui://… in a browser tab.
Change entrypoints — adjust "openai/ui".entrypoints only when you have a ChatGPT surface that supports them; thread vs global behave differently per extensions.
When ChatGPT cannot see the tool or UI, work through plugin troubleshooting:
- Tool discovery — endpoint reachable, correct path, metadata refreshed after deploy (shared checks for ChatGPT and Codex tool listing).
- Standard widget —
tools/listshows_meta.ui.resourceUrimatching the URI you serve fromresources/read, with HTML MIMEtext/html;profile=mcp-app; confirm the tool succeeds separately from whether the host paints the iframe. - Global / thread entrypoints — advertised in
"openai/ui", metadata refreshed, and the client build you use documents support for that entry type. - Deployment — HTTP 401/403 from deployment protection, auth middleware, or redirects can block ChatGPT from reaching
/api/mcp; fix routing and auth, do not disable security wholesale.
Related reading on this site
If you want a production-shaped MCP server with real upstream data—not a UI shell—see my write-up on FinMCP, a financial data MCP server for ChatGPT, Codex, and Claude.
Get the starter
Clone chatgpt-mcp-app-starter, run it locally, deploy your own /api/mcp, and replace the Hello World HTML with your product. The repo includes docs/HOW_IT_WORKS.md and CI for the template itself—use those as the source of truth when this article and the code drift.
