What Is an MCP App? Architecture, Hosts, and How to Build One (June 2026)

If you already know MCP, you know the basic pattern: a host calls a tool, the MCP server returns data, and the model turns that data into a response. An MCP App adds a UI resource to that flow, so the user can work with a real interface inside the conversation.
TL;DR: An MCP App is an interactive web app attached to an MCP tool. The tool returns data for the model and points the host at a resource for the user. The host renders that resource in a sandboxed iframe, then passes tool data, display mode, host context, and app state through a bridge. That is how you get dashboards, forms, review screens, maps, editors, and workflows inside ChatGPT, Claude, and other MCP hosts.
MCP Apps vs MCP Servers
An MCP server exposes tools, resources, and prompts. A plain MCP tool might return this:
{
"content": [{ "type": "text", "text": "The account has 12 open invoices." }],
"structuredContent": {
"openInvoiceCount": 12,
"totalDue": 4812
}
}
That is enough when the user only needs an answer. It is not enough when the user needs to compare rows, approve a change, edit a draft, filter a chart, or step through a workflow.
An MCP App keeps the tool and adds a resource. The model still receives structured data, but the user sees an interface that renders the same data. A billing tool can return invoice data and open an invoice dashboard. A code review tool can return findings and open a review UI. A booking tool can return available slots and open a calendar picker.
The short version:
- An MCP server gives the host callable tools and readable resources.
- An MCP App connects a tool result to an interactive resource.
- The app UI runs in an iframe, not in the host page.
- The model sees structured data and selected app state, not arbitrary DOM internals.
For a deeper protocol primer, read MCP Concepts Explained.
The Current Architecture
An MCP App has four moving pieces.
The MCP server owns the tools. It validates input, runs business logic, returns content and structuredContent, and includes metadata that tells the host which app resource to render.
The app resource is the UI bundle. In practice this is usually React, Vue, Svelte, or plain HTML and JavaScript. The resource can render tool output, hold app state, call server tools through the host, request display mode changes, and send user-visible updates back into the conversation.
The host is the runtime that embeds the app. ChatGPT Apps, interactive Claude Connectors, IDE integrations, and other MCP-compatible hosts can decide when to call tools, how to show the iframe, which display modes are available, and which host-specific APIs exist.
The bridge is the communication layer between the iframe and host. The MCP Apps extension defines a JSON-RPC style bridge over window.postMessage, so the resource can receive tool data and context without reaching into the host page.
That separation is the point. Your server owns data and side effects. Your resource owns UI. The host owns conversation placement and permissions. The bridge is the narrow channel between them.
How a Tool Opens an App
The exact metadata differs a bit by host and SDK, but the shape is consistent:
- The server registers a resource, usually with a
ui://URI. - The server registers a tool that points at that resource.
- The host calls the tool.
- The tool returns
structuredContentfor the model and metadata for the app. - The host renders or updates the resource iframe.
- The resource reads the tool data and renders the UI.
In the MCP Apps extension, the important app metadata is _meta.ui.resourceUri. In ChatGPT’s Apps SDK, you will also see compatibility fields such as _meta["openai/outputTemplate"], resource metadata such as _meta["openai/widgetDescription"], and CSP fields such as _meta["openai/widgetCSP"].
If you are building for more than one host, treat the MCP Apps metadata as the portable base and add host-specific fields only where they are needed. That keeps the same tool and resource usable in ChatGPT, Claude, and future hosts.
What the Model Sees
This is the most common design mistake in MCP Apps: developers assume the model can read whatever is on screen. It cannot, and it should not.
The model sees the tool result and any app state you intentionally expose back to the host. The user sees the iframe. If the user clicks a filter, checks a box, or approves a draft, your app must sync that state through the host bridge if the model needs to act on it later.
That gives you three useful channels:
structuredContentfor data the model should reason over._metafor app-only data the component needs but the model should not read.- App state for user interactions that should become model context.
Keep those channels separate. Put sensitive display-only details in _meta, not structuredContent. Put model-relevant user choices in app state, not hidden React state. For a longer guide, see MCP App Tool Results.
What You Can Build
MCP Apps are best when a text answer would force the user to do too much work. Good fits include:
- Review screens for approvals, diffs, policies, and generated drafts.
- Dashboards that need sorting, filtering, grouping, or drill-down.
- Forms where the model can prefill data and the user confirms it.
- Editors for documents, code, queries, workflows, and structured records.
- Maps, timelines, dependency graphs, and other visual views.
- Multi-step tasks where the user alternates between chat and UI.
Avoid building an app when a simple tool response is enough. If the user asks for the weather, text may be fine. If they need to compare hourly forecast risk across five job sites and pick a dispatch plan, an app starts to make sense.
A Minimal sunpeak Example
With sunpeak, a resource is a React component and a tool is a TypeScript handler. The framework discovers both from the file system and wires the tool to the resource.
// src/resources/invoices/invoices.tsx
import { useToolData, SafeArea } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';
type InvoiceSummary = {
customer: string;
openInvoiceCount: number;
totalDue: number;
};
export const resource: ResourceConfig = {
description: 'Show an invoice summary',
};
export default function InvoicesResource() {
const { output } = useToolData<unknown, InvoiceSummary>(undefined, undefined);
return (
<SafeArea>
<h1>{output.customer}</h1>
<p>{output.openInvoiceCount} open invoices</p>
<strong>${output.totalDue.toLocaleString()}</strong>
</SafeArea>
);
}
The matching tool returns the data that the component renders:
// src/tools/show-invoices.ts
import { z } from 'zod';
import type { AppToolConfig } from 'sunpeak/mcp';
export const tool: AppToolConfig = {
resource: 'invoices',
title: 'Show Invoices',
description: 'Show open invoices for a customer',
annotations: { readOnlyHint: true },
};
export const schema = {
customerId: z.string().describe('Customer ID to look up'),
};
export default async function showInvoices(args: { customerId: string }) {
return {
structuredContent: {
customer: args.customerId,
openInvoiceCount: 12,
totalDue: 4812,
},
};
}
Run it locally:
npx sunpeak new
pnpm dev
The sunpeak inspector renders the resource in replicated ChatGPT and Claude runtimes on localhost. You can switch hosts, themes, display modes, and tool states without deploying to a live host.
Host Support and Portability
The safest way to talk about host support is by capability, not by brand list. A host can support MCP tools without supporting embedded app UI. A host can support embedded UI but expose different display modes, CSP rules, auth flows, file handling, or state behavior.
When you plan an MCP App, ask these questions:
- Does the host support app resources attached to tool results?
- Which display modes can it render?
- Which bridge methods can the resource call?
- How does it scope app state across messages and reloads?
- What CSP and network rules apply inside the iframe?
- Which host-specific APIs are worth using behind feature checks?
Build the core app against the shared MCP App bridge. Then add host-specific behavior in narrow layers, for example with sunpeak/chatgpt or sunpeak/claude imports. That gives you a portable base without blocking richer host behavior.
For a hands-on portability guide, see Building One MCP App for ChatGPT and Claude.
Security Boundaries
An MCP App is a web app running in someone else’s conversation UI, so the boundaries matter.
The iframe should not be able to read the host DOM. The host should not hand the iframe the whole conversation. The model should not see app-only data unless you put it in structuredContent or sync it as app state. Network access should be limited through CSP, and external API calls should go through the server unless the client truly needs direct access.
Use this rule of thumb:
- Put secrets and privileged API calls on the server.
- Put model-readable facts in
structuredContent. - Put UI-only details in
_meta. - Put user choices the model needs in app state.
- Put allowed client network domains in resource CSP.
That structure makes the app easier to audit because each data path has a purpose. For more detail, read MCP App CSP Domains and Security Testing for MCP Apps.
Testing MCP Apps
You cannot fully test an MCP App by opening a component in a browser. The component only works when a host injects tool data, display mode, theme, app state, and bridge APIs.
Test these layers:
- Tool tests: schema validation, auth, errors, and
structuredContentshape. - Resource unit tests: rendering, empty states, loading states, and app state transitions.
- Inspector E2E tests: iframe rendering through a simulated host.
- Visual tests: display modes, themes, widths, and safe areas.
- Cross-host tests: ChatGPT and Claude behavior, especially state and bridge actions.
- Live tests: final smoke checks against real hosts before launch.
sunpeak uses simulation files to pin app states. A simulation can define tool input, tool output, user message, host, theme, display mode, and server tool mocks. That makes local and CI tests deterministic, which matters because live host testing is slower and harder to reproduce.
For a complete testing plan, read The Complete Guide to Testing ChatGPT Apps and MCP Apps and MCP App Testing Strategy.
When to Use a Framework
You can build an MCP App directly from the protocol, but you will need to solve the same set of problems:
- Register resources and connect them to tool results.
- Bundle UI resources for host iframes.
- Handle the host bridge, display mode, theme, and context.
- Keep ChatGPT and Claude compatibility fields straight.
- Mock host state for local development.
- Run tests inside a realistic iframe runtime.
A framework is useful when you want those pieces handled in one project shape. sunpeak gives you the inspector, file-based tools and resources, typed React hooks, app UI components, simulation fixtures, Playwright tests, visual tests, and CI-friendly host replicas.
The tradeoff is that you accept a project convention. For most teams, that is a good trade because the hard part of MCP Apps is not rendering a React component. The hard part is making that component behave correctly across host data, state, security, and testing boundaries.
The Practical Build Path
Start small:
- Pick one tool where UI clearly beats text.
- Define the
structuredContentshape before writing the component. - Build a read-only resource first.
- Add app state only when the model needs to know what the user did.
- Add host-specific features behind capability checks.
- Write simulations for success, empty, loading, error, and cancelled states.
- Run inspector E2E tests before trying a live host.
That path keeps the first version simple and gives you a clean place to add richer behavior. Once the read-only app is stable, add state, server tool calls, display mode transitions, file handling, auth, and live host tests.
If you want the shortest path, start with the MCP App tutorial. If you are choosing architecture first, read How to Build an MCP App and How to Choose an MCP App Framework.
Build and Test with sunpeak
sunpeak is an open-source MCP App framework and testing framework. It helps you build one app for ChatGPT, Claude, and other MCP hosts, inspect it locally, and test it in CI without paid host accounts or AI credits.
Create a project:
npx sunpeak new
Inspect an existing MCP server:
npx sunpeak inspect --server http://localhost:8000/mcp
Add tests to an existing server:
npx sunpeak test init --server http://localhost:8000/mcp
The useful mental model is simple: MCP gives the model tools, MCP Apps give the user UI, and a good framework helps you prove the boundary between the two works before real users find the edge cases.
Get Started
npx sunpeak newFurther Reading
- MCP App Tutorial - build and test your first MCP App
- How to Build an MCP App - architecture for cross-host interactive UI
- MCP App Resource Metadata - CSP, permissions, and host fields
- MCP App Tool Results - content, structuredContent, and _meta
- Interactive MCP Apps with useAppState - two-way state and model context
- Complete Guide to Testing ChatGPT Apps and MCP Apps
- MCP App Framework - portable app framework for ChatGPT and Claude
- MCP App Inspector - test MCP Apps locally
- sunpeak Docs - framework, inspector, and testing guides
- MCP Specification - tools, resources, and protocol model
- MCP Apps Extension - standard app UI model
- OpenAI Apps SDK Reference - ChatGPT app metadata and bridge fields
Frequently Asked Questions
What is an MCP App?
An MCP App is an interactive web UI attached to a Model Context Protocol tool. The MCP server returns structured data and points the host at a UI resource, then the host renders that resource in a sandboxed iframe inside the conversation.
What is the difference between an MCP App and an MCP server?
An MCP server exposes tools, resources, and prompts to a host. An MCP App uses those same MCP building blocks, but adds an interactive resource that renders when a tool runs. The model still receives structuredContent, while the user gets a real interface instead of a text-only answer.
Do MCP Apps replace ChatGPT Apps or Claude Connectors?
No. ChatGPT Apps and interactive Claude Connectors are host-specific ways to ship MCP-powered experiences. MCP Apps are the portable app pattern underneath: tools return data, resources render UI, and host bridges connect the iframe to the conversation.
How does an MCP App connect a tool to UI?
The tool result includes normal MCP fields such as content and structuredContent, plus metadata that points to a resource URI. In the MCP Apps extension this is _meta.ui.resourceUri. ChatGPT also supports compatibility fields such as openai/outputTemplate for Apps SDK integrations.
Can one MCP App run in multiple hosts?
Yes, if the app sticks to the shared MCP App bridge for tool data, host context, display modes, and state. Host-specific APIs can still be useful, but they should be optional layers so the core resource works across compatible hosts.
What should I test in an MCP App?
Test the tool contract, structuredContent shape, resource metadata, iframe rendering, display modes, themes, app state, and any host-specific paths. MCP Apps fail at the boundaries between server output, host rendering, and UI state, so tests need to cover those boundaries.
Do I need a paid ChatGPT or Claude account to build MCP Apps?
No. sunpeak includes a local inspector that replicates ChatGPT and Claude runtimes. You can render resources, switch hosts and display modes, and run Playwright tests locally or in CI before using a live host.
What is the fastest way to start building an MCP App?
Start with a small tool that returns typed structuredContent and a resource that reads that data. With sunpeak, run npx sunpeak new, add a resource in src/resources, add a tool in src/tools, then run pnpm dev to inspect and test the app locally.