Getting Started
Clone the repo and navigate to the Coffee Shop example:Setting up the MCP Server
Creating the MCP Server
server.ts
Registering Resources and Tools
Widget resources and tools work together. The resource provides the widget HTML, and tools reference it to display your UI:server.ts
Understanding the Widget
ChatGPT Apps can display interactive widgets inside the chat. Widget resources are registered by your app and become available when the client connects to your MCP server. Your widget can be built using vanilla JavaScript or a framework like React (optionally with TypeScript), and is bundled into a self-contained HTML file. When your tool is called, the client renders this HTML inside a sandboxed iframe and injectswindow.openai into it, which is how your widget communicates with the client and invokes tools on your MCP server.
The window.openai API
window.openai provides globals, methods for calling tools, sending follow-ups, managing layout, and much more.
Here’s a basic React widget structure using the window.openai API:
src/CoffeeShopWidget.tsx
window.openai.toolOutput- ThestructuredContentyour MCP server returned. It’s the data your tool sends to both the widget and the model for contextwindow.openai.callTool()- Lets widget buttons trigger server tools directly (requiresopenai/widgetAccessible: truein the tool’s metadata)openai:set_globals- Event that fires when users trigger tools via chat (e.g., “order me a coffee”), keeping everything in sync
Our Coffee Shop stores state on the server, so the widget just reads
toolOutput. If you need to persist state in the widget and expose it to the client, use window.openai.widgetState and window.openai.setWidgetState().window.openai component bridge (file uploads, modals, follow-up messages, and more), see the OpenAI Apps SDK docs.
Display Modes
Widgets can request different display modes to optimize their presentation:- Inline (default) - Widget renders within the chat message flow
- Picture-in-Picture (PiP) - Widget floats at the top of the screen, staying visible while scrolling
- Fullscreen - Widget expands to fill the entire viewport for immersive experiences
Content Security Policy (CSP)
Widgets run in a sandboxed iframe, so you need to declare which external domains your widget can interact with. Setopenai/widgetCSP in your resource’s _meta to configure these permissions:
connect_domains- Domains your widget can fetch from (API calls)resource_domains- Domains for static assets (images, fonts, scripts)redirect_domains- Domains forwindow.openai.openExternal()redirects
redirect_domains to allow the “Learn more” button to redirect users to mcpjam.com:
src/CoffeeShopWidget.tsx
Without declaring a domain in your CSP, the sandbox will block the request. Only declare the domains you actually need.
A note on authentication and monetization
Since the purpose of this guide is to get you building your first ChatGPT App, we’ve kept things simple and skipped authentication and monetization. For production apps, check out OpenAI’s authentication docs and monetization docs.Running Your App
Build and start the server
http://localhost:8787/mcp.
Testing with MCPJam Inspector
The easiest way to test your app:- Run the inspector:
npx @mcpjam/inspector@latest - Enter URL:
http://localhost:8787/mcp - Try your app in our Chat or App Builder!
Connecting to ChatGPT
To connect your app to ChatGPT:- In MCPJam Inspector, click Create ngrok tunnel with your server connected
- Use the tunnel URL as your connector endpoint in ChatGPT
What’s next?
Now that your Coffee Shop is running, you can:- Test the flow - Call the
orderCoffeetool to see your widget - Try the buttons - Click “Order” and “Drink” to interact with your server
- Chat naturally - Say “order me 3 coffees” and watch the widget update
- Iterate and expand - Add more tools, improve the UI, or build something completely new!

