Getting an MCP tool to respond is one thing. Getting ChatGPT to render its UI is another.
When building a ChatGPT MCP App with Next.js, it's useful to separate the moving parts instead of treating the tool, widget and sidebar as one feature.
I put together a small, open-source starter to demonstrate that architecture without a giant boilerplate. It contains three main pieces.
1. A read-only MCP tool
The first piece is a tool called open_starter_hello.
It takes no arguments, makes no external API calls and returns a simple text response plus structured content.
The important part is that the tool works independently of its UI. Even if the client can't render the widget, the model still receives a useful response.
In the starter, this is registered using registerAppTool from @modelcontextprotocol/ext-apps/server, with a Zod input schema and an honest readOnlyHint.
2. An HTML UI resource
Next, the app registers a resource at:
ui://starter/hello.html
This isn't a public website URL. It's an MCP resource identifier that the client resolves using resources/read.
The resource serves static HTML with the MIME type text/html;profile=mcp-app.
To connect the tool with that resource, the tool metadata includes:
_meta: {
ui: { resourceUri: UI_URI },
"openai/ui": {
entrypoints: [
{ type: "thread" },
{ type: "global" }
]
}
}
This is an excerpt from the starter's tool configuration, where UI_URI points to the registered resource.
The ui.resourceUri field links the tool to its standard inline UI. The optional thread and global entrypoints advertise conversation-panel and sidebar experiences in compatible ChatGPT clients.
Advertising an entrypoint doesn't guarantee every client will display it.
3. One Streamable HTTP endpoint
The entire example lives in a Next.js App Router handler:
app/api/mcp/route.ts
Using mcp-handler, the route exports the MCP handler for both GET and POST.
Its local endpoint is:
http://localhost:3000/api/mcp
There's no database, paid API key or account system in this Hello World example. The shipped HTML is static, so a genuinely interactive widget would require further UI integration.
Testing the pieces separately
Clone the ChatGPT MCP App Starter, install dependencies and run npm run dev.
Then launch MCP Inspector:
npx @modelcontextprotocol/inspector@latest
Connect using the local /api/mcp URL.
I recommend checking these separately:
-
Tool discovery: Does
tools/listexposeopen_starter_helloand its UI metadata? -
Tool execution: Does calling it with
{}return the expected text and structured response? -
Resource loading: Can the registered HTML resource be retrieved through
resources/read?
Only after these checks should you test how the UI actually appears in ChatGPT.
For a remote connection, deploy your own publicly reachable HTTPS endpoint and use a ChatGPT account that supports custom MCP plugins. An endpoint responding successfully doesn't prove the inline widget or additional panels rendered correctly.
A few gotchas
If the tool works but the UI doesn't, check the resource URI, MIME type and client support before rewriting the server.
If the side panel or sidebar is missing, check the optional entrypoint metadata and refresh the plugin after changing it.
If the endpoint returns 401 or 403, review authentication and deployment protection rather than blindly disabling security.
I've covered the full architecture, setup and troubleshooting in my original ChatGPT MCP App walkthrough.
The starter is MIT-licensed community code, not an official OpenAI product.

Top comments (0)