Skip to content

Add UI to Your MCP Server with OpenAI

Serve React or HTML components from your MCP server so they render directly within the ChatGPT interface, instead of returning text-based replies.

Tuan Tran Van
8 min read
Contents (10 sections)
  1. Step 1 — Create the project and install the MCP Apps packages
  2. Step 2 — Build the UI and connect it to the host with the App class
  3. Step 3 — Bundle the UI into a single HTML file (for members)
  4. Step 4 — Register the UI resource and attach it to a tool (for members)
  5. Step 5 — Run the server and confirm the UI renders (for members)
  6. Step 6 — Connect it to ChatGPT and test it in a conversation (for members)
  7. Quick reference for UI metadata and APIs (for members)
  8. Troubleshooting common problems (for members)
  9. What to do next (for members)
  10. References (for members)

Finish this guide and you will have a working MCP server that renders interactive React or HTML components directly inside the ChatGPT interface, using the MCP Apps standard.

Add UI to your MCP server for dashboards, forms, and visualizations that go beyond text-based replies.

You need Node.js 18 or higher and an existing MCP server project. You also need a paid ChatGPT plan with developer mode enabled to connect and test your local development server.

The complete sample project for this guide is on GitHub: camnangai-public-sources/mcp-app-demo.

An interactive UI widget served by an MCP server rendering inside the ChatGPT conversation, alongside the model's text response

Step 1 — Create the project and install the MCP Apps packages

Create a new project directory and initialize npm.

bash
mkdir mcp-app-demo && cd mcp-app-demo
npm init -y

Install the core MCP and extension packages along with the Express framework for serving the transport, plus dev dependencies for TypeScript and Vite.

bash
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors zod
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors tsx

After installing, verify that your package.json includes "type": "module" to enable ES module syntax. These packages bridge the UI iframe and the MCP host.

Add three scripts to package.json:

json
{
  "scripts": {
    "build": "vite build",
    "serve": "tsx server.ts",
    "dev": "INPUT=mcp-app.html npm run build && npm run serve"
  }
}

By the end of this guide the project looks like this:

text
mcp-app-demo/
├── package.json
├── tsconfig.json
├── vite.config.ts      # bundles the UI into a single HTML file
├── server.ts           # MCP server, reads ./dist/mcp-app.html
├── mcp-app.html        # UI entry point
└── src/
    └── mcp-app.ts      # client-side UI logic

Step 2 — Build the UI and connect it to the host with the App class

Create an src/mcp-app.ts file to handle the client-side logic of your UI. Use the App class to connect to the ChatGPT host.

The JSON-RPC bridge over postMessage between the UI iframe and the ChatGPT host: the host pushes tool results into the UI, the UI calls server tools back

typescript
import { App } from "@modelcontextprotocol/ext-apps";
 
const app = new App({ name: "My MCP UI", version: "1.0.0" });
 
// Establish the JSON-RPC bridge over postMessage
app.connect();
 
// Handle data pushed from the host when a tool is invoked
app.ontoolresult = (result) => {
  console.log("Received tool result:", result.structuredContent);
  // Update your UI components with the result data here
};
 
// Proactively call a server tool from the UI
async function handleUserInput() {
  const response = await app.callServerTool({
    name: "my-data-tool",
    arguments: { query: "example" },
  });
  console.log("Tool response:", response);
}

app.connect() opens the JSON-RPC bridge over postMessage, so the iframe can communicate securely with the ChatGPT host. ontoolresult reacts to results from the initial model-driven tool call, while callServerTool lets the UI trigger server logic independently — useful for refresh buttons or manual state updates inside the component.

Read more

Share this article