All posts

Ship a ChatGPT App in 2 Commands (July 2026)

Abe Wheeler
ChatGPT AppsMCP AppsGetting StartedTutorialChatGPT App FrameworkMCP App TestingDeveloper ModeApps SDK
The default carousel ChatGPT App built and deployed with sunpeak.

The default carousel ChatGPT App built and deployed with sunpeak.

With sunpeak, you can go from an empty folder to a working ChatGPT App in two commands:

npx sunpeak new
cd sunpeak-app && pnpm dev

That gets you a TypeScript MCP server, React resources, example tools, simulation fixtures, and the local sunpeak Inspector. The Inspector opens at http://localhost:3000, and the MCP endpoint runs at http://localhost:8000/mcp.

TL;DR: run npx sunpeak new, then pnpm dev. Build and test locally in the Inspector first. When you need to test inside the real ChatGPT host, expose http://localhost:8000/mcp with a tunnel, open Plugins from the ChatGPT sidebar, then select the existing app or use + to add one. Before publishing, test tool metadata, schemas, UI rendering, auth, and production resource loading, then submit the app as a With MCP plugin.

Why this is still a ChatGPT App

OpenAI’s current Apps SDK docs describe a ChatGPT App as three pieces working together: an MCP server, a UI bundle rendered in an iframe, and the model that decides when to call your tools based on your metadata.

The portable MCP Apps model uses the same basic shape:

  • A tool exposes a name, description, input schema, optional output schema, annotations, and metadata.
  • The tool can point to a UI resource with _meta.ui.resourceUri, usually a ui:// URI.
  • The resource returns bundled HTML, JavaScript, and CSS for the host to render.
  • The tool result can return content, structuredContent, and private _meta.
  • The app and host communicate over a controlled bridge, so the UI can receive tool results and call allowed tools.

That matters because the old ChatGPT-only examples often center OpenAI-specific keys such as _meta["openai/outputTemplate"]. Those still exist for compatibility, but for a new app in June 2026, prefer the standard MCP Apps fields first. Use _meta.ui.resourceUri to link tools to UI resources, _meta.ui.visibility to control model versus app access, and outputSchema whenever your tool returns structuredContent.

sunpeak builds around that portable MCP Apps shape. You can still target ChatGPT directly, but you avoid baking one host’s compatibility keys into the middle of your app design.

Command 1: scaffold the app

Run:

npx sunpeak new

The scaffold gives you a working project instead of a blank SDK setup:

sunpeak-app/
  src/
    resources/        React UI resources rendered by the host
    tools/            MCP tool configs, Zod schemas, and handlers
    server.ts         Server configuration, auth hooks, and deployment options
  tests/
    simulations/      Deterministic tool input and output fixtures
    e2e/              Playwright tests against the Inspector
    live/             Optional tests against the real host
    evals/            Optional model tool-calling evals
  package.json

In a sunpeak project, a resource is usually a React component plus resource metadata. A tool is a server-side file that exports tool metadata, a schema, and a handler. If the tool includes a resource link, sunpeak wires the tool to the generated MCP Apps resource metadata for you.

That means the starter app already covers the boring but easy-to-break parts: resource bundling, local rendering, MCP endpoint setup, test fixtures, and a project layout an agent can navigate.

Command 2: start the local runtime

Inside the generated project, run:

pnpm dev

This starts:

  1. An MCP endpoint at http://localhost:8000/mcp.
  2. The sunpeak Inspector at http://localhost:3000.
  3. Hot reload for resource UI changes.
  4. Simulation loading for deterministic app states.

Open http://localhost:3000 and render the starter app. You can call tools, switch host runtimes, change themes, test display modes, and resize the viewport without deploying anything.

This is the main reason the two-command flow is useful. You do not need to connect ChatGPT on day one just to find out whether your iframe overflows on mobile, your tool result shape changed, or your component expects data that the server never returns.

Check the tool contract before the UI

When a ChatGPT App fails, the UI is often blamed first. Start with the MCP contract instead.

For each app-backed tool, check:

  • The tool name is stable and descriptive.
  • The description says when the model should call the tool.
  • inputSchema only asks for fields the model can reasonably infer or ask the user for.
  • outputSchema matches structuredContent.
  • annotations.readOnlyHint is correct for read-only tools.
  • Destructive or write actions use clear annotations and confirmation UX.
  • _meta.ui.resourceUri points at the resource you expect.
  • _meta.ui.visibility allows the model, the app, or both to call the tool.
  • Private data needed by the component goes in result _meta, not transcript-visible content.

The Inspector helps because you can test both sides of the boundary. Call the tool directly to verify protocol output, then render the resource with the same result to verify the UI. That is faster than debugging the full ChatGPT loop every time.

Test locally before using developer mode

Run the default local tests:

pnpm test

For a sunpeak framework project, the default test run covers unit checks if configured and E2E tests against the local Inspector. For visual coverage, run:

pnpm test:visual

or use the CLI form:

npx sunpeak test --visual

Good early tests for a ChatGPT App include:

  • The tool appears in tools/list with the expected title, schema, annotations, and _meta.
  • The tool handler returns structuredContent that matches outputSchema.
  • The linked resource renders with realistic tool results.
  • Empty, loading, partial, and error states do not break layout.
  • App-only actions cannot be called by the model unless you intended that.
  • The same UI works in light and dark themes.
  • Mobile and narrow display modes keep controls usable.

Only add live host tests after the local contract is stable. Live tests are valuable because they catch real-host behavior, auth setup, and production resource loading issues, but they cost more time and can require paid or workspace-level access.

Connect it to ChatGPT when the local app is stable

To test with the real ChatGPT host, expose your local MCP endpoint:

ngrok http 8000

Copy the public forwarding URL and append /mcp, for example:

https://abc123.ngrok-free.dev/mcp

Developer mode is required to add a custom app. Enable it from the user component in the bottom-left corner under Settings > Security and login > Developer mode. Then open Plugins from the ChatGPT sidebar, select an existing development app, or use the + button to add one. If adding an app, complete the form with the endpoint, name, description, icon, and connection details.

Once connected, start a new conversation and ask ChatGPT to perform the workflow your tool supports. If the model does not call the tool, improve the tool description and input schema before you change the UI. ChatGPT chooses tools from metadata, so weak metadata often looks like a frontend bug.

Deploy the MCP endpoint

For production, build and start the server:

pnpm build
pnpm start

Use a long-lived Node server when you want stateful MCP sessions and sticky routing is available. Use createHandler with stateless: true for serverless and edge deployments such as Cloudflare Workers, Deno, Bun, or Vercel Edge. In stateless mode, each request creates a fresh server and transport, so you avoid session affinity requirements.

Before you submit or share the app, verify:

  • The deployed /mcp endpoint is reachable over HTTPS.
  • Auth works from a clean browser profile.
  • Resource domains and CSP allow only the origins your UI needs.
  • Tool annotations match the real side effects of each tool.
  • Server instructions, tool descriptions, schemas, and resource metadata are production-ready.
  • A production URL, not an ngrok URL, is configured in ChatGPT.
  • Local E2E tests pass against the same build path you deploy.

OpenAI now publishes Apps as Plugins. In the OpenAI Platform plugin portal, choose With MCP and submit the production MCP server, not the development app ID. Prepare verified publisher identity, Apps Management write access, domain verification, exact CSP domains, accurate tool annotations, starter prompts, five positive tests, three negative tests, availability, release notes, and review credentials when the app requires login.

If you need to test an existing MCP server that was not built with sunpeak, initialize a test harness instead of moving the server:

npx sunpeak test init --server https://your-app.example.com/mcp
npx sunpeak test

That gives you Inspector-backed E2E coverage around an existing server, which is useful for Python, Go, Rust, or custom TypeScript MCP servers.

What the two-command path gives you

The short path is not just about fewer keystrokes. It gives you a working app with a local host runtime, deterministic fixtures, and tests before you make product decisions.

That changes the build order:

  1. Scaffold the app with npx sunpeak new.
  2. Run it locally with pnpm dev.
  3. Lock down tool contracts and UI states in the Inspector.
  4. Add E2E and visual tests.
  5. Open Plugins from the ChatGPT sidebar, then select the app or use + to add one with the tunnel URL.
  6. Deploy the production MCP endpoint.
  7. Run live tests or manual review against the real host.
  8. Submit an app-only plugin through the OpenAI Platform with With MCP.

You can still use OpenAI’s Apps SDK directly when you want to wire every part by hand. Use sunpeak when you want the app framework, Inspector, and testing setup to be present from the first commit.

Start with the sunpeak docs, the ChatGPT App framework, or the full ChatGPT App tutorial. If your app already has a server, use the testing framework to inspect and test it before you rebuild anything.

Get Started

Documentation →
npx sunpeak new

Further Reading

Frequently Asked Questions

What are the two commands to create and run a ChatGPT App?

Run "npx sunpeak new" to scaffold a full MCP App project, then run "pnpm dev" inside the generated project. The first command creates the app structure, example tools, React resources, simulations, and tests. The second command starts the local MCP endpoint and the sunpeak Inspector.

Can I build a ChatGPT App without connecting to ChatGPT first?

Yes. You can build and test locally in the sunpeak Inspector at localhost:3000. The Inspector replicates ChatGPT and Claude-style MCP App runtimes, supports simulations, and lets you test themes, display modes, device sizes, and tool results before you connect a real host.

What does ChatGPT call when it runs a ChatGPT App?

ChatGPT calls tools exposed by your MCP server. A tool can return content for the model, structuredContent that matches an outputSchema, and private _meta data for the UI. If the tool declares a UI resource with _meta.ui.resourceUri, the host can render that resource as an interactive app in the conversation.

How do I connect a local ChatGPT App to ChatGPT developer mode?

Expose your local MCP endpoint with a tunnel. Developer mode is required to add a custom app. Enable it from the user component in the bottom-left corner under Settings > Security and login > Developer mode. Then open Plugins from the ChatGPT sidebar, select an existing app, or use + to add one with the public /mcp URL.

What does sunpeak new scaffold for a ChatGPT App?

sunpeak new scaffolds a TypeScript project with src/resources for React UI resources, src/tools for MCP tool handlers and Zod schemas, src/server.ts for server configuration, tests/simulations for deterministic app states, and Playwright-based tests for the local Inspector.

How do I test a ChatGPT App before publishing it?

Start with "pnpm test" for local unit and E2E coverage. Add visual regression tests with "pnpm test:visual" or "sunpeak test --visual", use live tests when you need to verify behavior in the real ChatGPT host, and use evals when you need to measure whether model prompts reliably choose the right tools.

Which MCP Apps metadata should a new ChatGPT App use?

Prefer the standard MCP Apps metadata, especially _meta.ui.resourceUri for the UI resource and _meta.ui.visibility for model and app access. ChatGPT still supports OpenAI-specific compatibility keys such as _meta["openai/outputTemplate"], but the portable MCP Apps fields should be the source of truth for new cross-host apps.

Can one app work in ChatGPT and other MCP App hosts?

Yes. ChatGPT Apps are MCP Apps rendered inside ChatGPT. If you build against the portable MCP Apps model, keep host-specific behavior isolated, and test across host runtimes, the same tool and resource structure can work in ChatGPT, Claude Connectors, and other compatible hosts.