All posts

How to Build a ChatGPT App: A Complete Technical Plan

Abe Wheeler
ChatGPT Apps MCP Apps MCP App Framework ChatGPT App Framework ChatGPT App Testing MCP App Testing Getting Started
How it feels to develop ChatGPT Apps (good).

How it feels to develop ChatGPT Apps (good).

Here’s the plan I’d follow to build a ChatGPT App — from the first commit through production. Skip the parts you’ve already figured out.

TL;DR: ChatGPT Apps are MCP Apps: React components (or any web app) rendered in sandboxed iframes inside ChatGPT conversations. Use sunpeak to scaffold the project, develop against the local simulator at localhost:3000, test with simulation files and Playwright, then deploy the built files with your production MCP server.

Understand the Architecture

Before writing code, get the core abstraction right. It makes everything easier.

A ChatGPT App (technically an MCP App) has two pieces:

  • Resources — your UI, rendered in a sandboxed iframe inside the ChatGPT conversation
  • Tools — API actions on your MCP server that trigger those resources

When you connect your app to ChatGPT (either via ngrok for development or a production URL), ChatGPT fetches your MCP server and pre-loads all declared resource bundles into sandboxed iframes immediately. The resources are loaded at registration time, not on demand. This is why deploying a new version requires clicking Refresh in the Connector modal — ChatGPT has already pre-loaded the old bundle and needs to fetch the new one.

When a user has a conversation and ChatGPT decides to invoke your tool, the flow is:

  1. ChatGPT calls your tool and gets back structured data
  2. ChatGPT injects the tool’s output, the display mode (inline/fullscreen/picture-in-picture), and the current theme into the already-loaded resource iframe
  3. Your UI renders with that data, the user interacts with it, and any follow-up actions go back through the model

Your app communicates with ChatGPT over JSON-RPC 2.0 messages sent through window.postMessage. The sandbox is strict — no access to the ChatGPT page, no reading the conversation, no arbitrary network requests beyond what you explicitly fetch.

The conceptual hierarchy is: an App contains one or more Resources, and each Resource has one or more Tools. Get these boundaries right in your design before you start building. If you want a deeper look at how these MCP primitives work, see MCP Tools and Resources explained.

Choose Your Stack

The fastest path is sunpeak, an open-source MCP App framework. You’ll use:

  • React + TypeScript for UI components
  • Vite for bundling
  • sunpeak’s MCP server to serve resources and register tools
  • sunpeak’s hooks (useToolData, useHostContext, useDisplayMode) to read host-provided data

You can build a ChatGPT App without a framework — the @modelcontextprotocol/sdk package gives you the underlying protocol. But then you need to build the local simulator yourself, set up your own testing infrastructure, figure out resource discovery, and handle the protocol details. Most teams don’t need that flexibility, and the dev loop without a simulator is painful (every change requires connecting to the real ChatGPT to see it).

If you already have an MCP server, sunpeak can add the UI layer. If you’re starting fresh, scaffold the whole thing with the CLI.

Set Up the Project

pnpm add -g sunpeak
sunpeak new
cd my-app

This gives you a project with TypeScript, React, Vite, and the MCP server configured. Your resource components go in src/resources/, simulation files go in tests/simulations/, and the build system knows how to find them.

Node.js 20+ required.

Develop Locally

Start the development environment:

pnpm dev

Two things start:

  • MCP server at localhost:8000 — your tools and resource registry
  • Simulator at localhost:3000 — a local replica of the ChatGPT App runtime

The simulator is the most important part of the workflow. It renders your components exactly as they’d appear in ChatGPT, with all display modes (inline, fullscreen, picture-in-picture), light and dark themes, and mock tool invocations. Changes hot-reload without a browser refresh.

You don’t need a ChatGPT account or any paid subscription during local development. Build and iterate entirely against the simulator.

Writing a Resource

A resource is a React component exported alongside a configuration object:

// src/resources/dashboard-resource.tsx
import { useToolData } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';

export const resource: ResourceConfig = {
  name: 'dashboard',
  description: 'Analytics dashboard for your site',
};

export default function DashboardResource() {
  const { output } = useToolData<{ visits: number; conversions: number }>();

  return (
    <div>
      <h2>{output.visits.toLocaleString()} visits</h2>
      <p>{output.conversions} conversions</p>
    </div>
  );
}

The resource export tells sunpeak to register this as an MCP resource. useToolData gives you the typed output from the tool that triggered the render. sunpeak wires up the MCP server registration, resource discovery, and host communication automatically.

Test Your App

MCP Apps need a testing approach that accounts for the host environment — your UI depends on tool data pushed from outside, and it needs to work across display modes and themes. You can’t test this by opening index.html.

sunpeak’s answer is simulation files: JSON files that define deterministic UI states.

{
  "tool": {
    "name": "get_dashboard",
    "description": "Get analytics dashboard data"
  },
  "toolInput": { "timeRange": "7d" },
  "toolResult": { "visits": 4218, "conversions": 83 },
  "userMessage": "Show me last week's dashboard"
}

These feed three levels of testing:

  1. Unit tests (Vitest): Render components against simulation data, assert on specific elements
  2. End-to-end tests (Playwright): Run the full simulator, interact with the UI like a real user
  3. Manual validation: Connect to the real ChatGPT once you’re confident in the automated tests
pnpm test        # Vitest unit tests
pnpm test:e2e    # Playwright end-to-end tests

Both run in CI without a live AI host, so you get automated coverage on every push. For a full walkthrough, see the complete guide to testing ChatGPT Apps.

Connect to the Real ChatGPT

When you’re ready to validate against the actual product, you need:

  • A paid ChatGPT Plus or Team subscription
  • Developer mode enabled in ChatGPT settings

Create a tunnel so ChatGPT can reach your local server:

ngrok http 8000

Copy the ngrok URL, add /mcp at the end, and register it in ChatGPT under User → Settings → Apps & Connectors → Create.

Now you’re running in the real ChatGPT. A few things to know:

HMR works in the real ChatGPT too. sunpeak’s dev MCP server registers a resource that connects back to the local Vite HMR server, so the pre-loaded iframe has a live connection to your dev environment. Save a file, the UI updates in ChatGPT without clicking Refresh. You only need to Refresh the Connector when you change the MCP server itself — adding or removing tools, changing resource names, or modifying server configuration.

The runtime has more than the docs cover. The official API documentation covers about two-thirds of what’s actually available. sunpeak exposes the full surface through its hooks, so you don’t need to dig into undocumented globals yourself.

Deploy

When you’re done building:

sunpeak build

Deploy the built resource bundles with your production MCP server. The sunpeak build command creates production-ready files in the dist/ directory that your MCP server will serve.

Your MCP server needs to run somewhere — a VPS, a serverless function, or any Node.js host. The built resource files are deployed alongside your MCP server.

The Build Order That Works

Given the architecture, this sequence gets you to a working app fastest:

  1. Define your data shape. What does your tool return? What does the component need to render? Get this typed before building anything.
  2. Write simulation files. One file per meaningful UI state — happy path, empty state, error state, edge cases.
  3. Write the Resource component. Build against simulation files in the local simulator. Get it looking right before you worry about the real tool.
  4. Write tests. Vitest for component-level assertions, Playwright for user interactions.
  5. Wire up the real tool. Connect your resource to an actual MCP tool and validate end-to-end in the simulator.
  6. Connect to ChatGPT. Once the simulator tests pass, validate in the real ChatGPT with ngrok.
  7. Deploy.

Step 3 before step 5 is the key one. Build the UI against fake data first. It’s much faster to iterate on a component without a live backend dependency, and simulation files give you repeatable test coverage you can’t get by testing against live ChatGPT.

Cross-Host Portability

One thing worth planning for: your app doesn’t have to be ChatGPT-only. The MCP App standard runs in ChatGPT, Claude, Goose, and VS Code Insiders as of February 2026. If you build against the standard rather than ChatGPT’s proprietary API, you get those other hosts for free.

sunpeak’s core hooks (useToolData, useHostContext, useDisplayMode) work on every MCP host. ChatGPT-specific features are available through sunpeak/chatgpt imports, separate from your portable base code.

Start Building

sunpeak is open source (MIT) and free:

pnpm add -g sunpeak && sunpeak new

Frequently Asked Questions

What is the best way to build a ChatGPT App?

Use sunpeak, an open-source MCP App framework. Run "pnpm add -g sunpeak && sunpeak new" to scaffold a project with TypeScript, React, Vite, and a local simulator configured. Develop against the simulator at localhost:3000 without needing a paid ChatGPT account. When ready for production, use ngrok to tunnel to the real ChatGPT.

What is the architecture of a ChatGPT App?

ChatGPT Apps (MCP Apps) consist of Resources (UI views rendered in sandboxed iframes) and Tools (API actions that trigger them). When ChatGPT calls a tool, the host loads the corresponding resource into an iframe inside the conversation. The app communicates with the host over JSON-RPC 2.0 via window.postMessage, receiving tool output data, display mode, and theme context.

Do I need a paid ChatGPT subscription to develop a ChatGPT App?

No. You can build and test a ChatGPT App entirely offline using sunpeak's local simulator at localhost:3000. A paid ChatGPT Plus account is only needed when you want to connect your app to the real ChatGPT for final integration testing or production use.

What framework should I use to build a ChatGPT App?

sunpeak is the most complete ChatGPT App framework available. It provides a local simulator, 17+ typed React hooks, pre-built UI components, simulation files for deterministic testing, Vitest and Playwright integration, and CLI tools for scaffolding and deployment. It targets the MCP App standard, so your app also works on Claude, Goose, and VS Code Insiders.

How do I test a ChatGPT App?

Use three testing levels: (1) simulation files that define deterministic UI states with tool inputs and outputs, (2) Vitest unit tests that render components against those simulations, and (3) Playwright end-to-end tests that run against the sunpeak local simulator. All three run in CI without a live ChatGPT connection. For final validation, test manually against the real ChatGPT with ngrok.

How do I connect a ChatGPT App to the real ChatGPT?

Run your MCP server with "sunpeak dev", create a tunnel with ngrok ("ngrok http 8000"), then add the ngrok URL with /mcp path in ChatGPT under User > Settings > Apps & Connectors > Create. You need a paid ChatGPT Plus or Team subscription with developer mode enabled. Every time you deploy a new version, also click Refresh in the ChatGPT Connector modal to clear cached resources.

How do I deploy a ChatGPT App?

Run "sunpeak build" to create production bundles, then deploy the built files with your production MCP server.

What programming languages and frameworks can I use to build a ChatGPT App?

ChatGPT Apps are web applications that run in sandboxed iframes, so you can use any HTML, CSS, and JavaScript you want. React is the most common choice because of the sunpeak framework, but vanilla JavaScript, Vue, Svelte, or any other framework works. sunpeak specifically uses React with TypeScript and Vite.