MCP Concepts Explained: Tools and Resources, and How MCP Apps Use Them (October 2026)

The MCP concepts behind app resources, tool calls, and host-rendered UI.
You’ll run into five terms over and over when building MCP Apps: hosts, clients, servers, Tools, and Resources. The distinction matters when a Tool works in a protocol inspector but its UI does not open in ChatGPT or Claude.
The host is the AI product. It creates a client connection to your server. A Tool is a callable action, while a Resource is content the client can read. An MCP App links a Tool to a browser Resource so a tool call can also produce interactive UI.
TL;DR: The server exposes a Tool and a ui:// HTML Resource. The Tool descriptor links them with _meta.ui.resourceUri. A compatible host fetches the Resource, renders it in an iframe, and sends tool input and results to the UI through the MCP Apps bridge. Keep a useful text result for clients without UI, then test the Tool, Resource, and iframe separately. sunpeak can inspect that flow locally.
What MCP Is
MCP, the Model Context Protocol is an open protocol for connecting AI applications to external systems. It defines how clients discover capabilities, exchange data, and call actions on servers. The AI host decides when and how to use those capabilities.
Without MCP, each AI host needs a separate integration. With MCP, a server can expose the same tools and data to any compatible host. That is why developers use it for ChatGPT Apps, Claude Connectors, IDE integrations, internal tools, and app UI inside chat.
The MCP Apps extension builds on core MCP by defining how a compatible host renders UI Resources from a server. A server can expose useful MCP Tools without shipping app UI at all.
Hosts, Clients, and Servers
The MCP architecture guide names three participants. You usually write the server, but understanding the client makes debugging easier.
- The host is the AI application the user talks to. It manages the chat, model, consent, and UI.
- An MCP client is the host’s connection to one MCP server. A host can create a separate client for each connected server.
- The server is the program you build. It exposes Tools, Resources, Prompts, and other capabilities over MCP.
The server can run locally over stdio or remotely over Streamable HTTP. A remote server is the usual path for a shared connector. Transport changes how the client reaches the server; it does not turn a Resource into a Tool or change the UI contract.
For MCP Apps, the split matters:
- The host owns the chat, model session, iframe sandbox, permissions, and user consent.
- Its MCP client discovers the server’s capabilities and routes protocol requests.
- The server owns the Tool handlers, Resource bundles, schemas, and business logic.
- The app UI runs in a sandboxed iframe that receives data through the host bridge.
That boundary is why MCP Apps can be portable. Your app does not need direct access to the host page, and the host does not need to trust arbitrary server code inside its own DOM.
The MCP Primitives That Matter for Apps
MCP servers can expose several capability types. For app developers, the two you need first are Tools and Resources.
Tools
A Tool is a callable action. You define a name, description, input schema, optional output schema, annotations, and metadata. The model reads the Tool definition and decides whether to call it for the user’s request.
{
"name": "get_weather",
"title": "Get Weather",
"description": "Get the current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
},
"annotations": {
"readOnlyHint": true
}
}
The description and schema are not just documentation. They are part of how the model chooses a Tool, fills arguments, and explains the result. If a Tool mutates state, reads private data, or reaches outside your system, use annotations such as readOnlyHint, destructiveHint, and openWorldHint so hosts can make better choices and show the right consent UI.
An MCP App still starts with a Tool. Its descriptor also points at a UI Resource. Keep Tool descriptions and schemas precise because hosts use them for selection, and enforce authorization in the handler because annotations are hints, not access control.
Resources
A Resource is something the host can read from the server. Standard MCP Resources are often files, documents, database rows, or generated text. A client reads a concrete URI through resources/read; a server can advertise parameterized URI patterns through resources/templates/list.
uri: file:///reports/q2-summary.pdf
mimeType: application/pdf
An MCP App Resource is a browser app. It has a ui:// URI and returns an HTML bundle, usually with the MIME type text/html;profile=mcp-app.
uri: ui://weather/app.html
mimeType: text/html;profile=mcp-app
That bundle can contain React, plain JavaScript, CSS, charts, forms, maps, or any UI that works inside the host’s sandbox policy. The Resource is fetched by the host, rendered in an iframe, and connected to the Tool result through the app bridge. A ui:// Resource is a concrete HTML document; it is different from a parameterized MCP resource template or a resource_link returned inside a Tool result. See MCP App resource templates for the three cases.
Prompts
MCP also defines Prompts, which are reusable conversation templates. They are useful for workflows, but they are not the core of MCP Apps. You can build a full app without defining any Prompts. Start with Tools and Resources, then add Prompts only when they make a user workflow easier.
How MCP Apps Combine Tools and Resources
MCP Apps do not replace MCP. They define a pattern on top of it:
- A server exposes a Tool.
- The Tool metadata points at a UI Resource.
- The host fetches and preloads that Resource.
- The model calls the Tool.
- The host sends the Tool result into the Resource iframe.
- The iframe renders interactive UI and can request host-approved actions.
At the protocol level, the Tool includes app metadata:
{
"name": "get_weather",
"title": "Get Weather",
"description": "Get the current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
},
"_meta": {
"ui": {
"resourceUri": "ui://weather/app.html"
}
}
}
The resourceUri points at the UI Resource. The Resource itself can define extra metadata such as Content Security Policy, allowed frame origins, requested permissions, preferred display mode, and host-specific compatibility fields. For the details, see MCP App Resource Metadata.
OpenAI’s plugin UI guide uses _meta.ui.resourceUri as the shared field and treats _meta["openai/outputTemplate"] as a compatibility alias. Keep the standard link as the source of truth, then add host-specific fields only when a feature needs them.
Trace one request across the boundary
Suppose someone asks, “What’s the weather in Denver?” The host’s client discovers get_weather with tools/list, the model selects it, and the client sends tools/call with { "city": "Denver" }. The server returns both a text summary and structured weather data. Because the Tool descriptor points to ui://weather/app.html, a host that supports MCP Apps also reads that Resource, mounts the iframe, and sends the input and result to it through the bridge.
That sequence gives you three separate checks:
| Check | Protocol or UI surface | What success looks like |
|---|---|---|
| Tool discovery | tools/list | The Tool name, schema, description, and _meta.ui.resourceUri are present. |
| Tool execution | tools/call | The result answers the request even when no UI is rendered. |
| UI rendering | resources/read and the host bridge | The ui:// URI returns MCP App HTML, the iframe loads, and it receives the matching Tool result. |
If the model never calls the Tool, inspect the Tool description and input schema. If the Tool returns text but no UI appears, inspect the Resource URI, MIME type, and iframe console before changing the Tool handler. The MCP App conformance testing guide walks through those checks in CI.
Tool Results: content, structuredContent, and _meta
Tool results are where many first MCP Apps get messy. You have three places to put data:
content: user-visible text or media that the model can read and quote.structuredContent: typed JSON that the model can read and the app can render._meta: private data sent to the app UI but hidden from the model.
For an MCP App, treat structuredContent as a typed UI contract, while content remains useful to the model and to clients that do not render UI. An outputSchema can describe the structured result. It does not replace input validation or a readable text fallback.
{
"content": [
{
"type": "text",
"text": "Weather for Denver: 72 F and sunny."
}
],
"structuredContent": {
"city": "Denver",
"temperatureF": 72,
"condition": "Sunny",
"hourly": [
{ "time": "10:00", "temperatureF": 68 },
{ "time": "11:00", "temperatureF": 70 }
]
},
"_meta": {
"requestId": "req_123",
"cacheKey": "weather:denver:2026-10-09"
}
}
Put enough in content for a non-visual fallback. Put the app’s render data in structuredContent. Put UI-only implementation details in _meta; this field is withheld from the model in the MCP Apps flow, but the iframe receives it, so it is no place for secrets. If your Tool declares an outputSchema, validate structuredContent against it and test the result with and without UI.
For a deeper guide, read MCP App Tool Results: content, structuredContent, and _meta.
The App Bridge
The MCP App UI runs in a sandboxed iframe. It cannot read the host DOM, access host cookies, or call arbitrary parent-frame APIs. All host communication goes through the bridge, usually over window.postMessage with JSON-RPC-style messages.
The host sends the app:
- Tool input and Tool results
- Host context, such as theme, locale, and viewport
- Display mode and safe-area information
- Capability information, when the host exposes it
The app can ask the host to:
- Call another server Tool
- Send a message into the conversation
- Update model context with user selections
- Request a display mode change
- Open a link or download a file
The host decides what to allow. That is why you should write apps with graceful fallbacks. If a host does not support a capability, the app should still render the current Tool result and give the user a clear next step.
The Security Model
The iframe sandbox lets hosts render third-party app UI without handing the app full control of the chat page.
An MCP App cannot:
- Access the host page’s DOM
- Read the host’s cookies or local storage
- Navigate the parent frame
- Run scripts in the parent context
- Load network resources outside the origins allowed by app metadata and host policy
An MCP App can:
- Render browser UI inside the iframe
- Receive Tool data from the host
- Call server Tools through the host bridge
- Send user-approved messages or context updates
- React to display mode, theme, locale, viewport, and safe-area changes
The Resource metadata is where you declare CSP domains and permissions. Keep that list narrow. If your UI only needs your API and a CDN for assets, do not request more. Hosts are more likely to approve apps that explain and limit what they need.
Display Modes and Host Context
MCP Apps are not always full-screen dashboards. The same Resource may render in different display modes:
- Inline: a compact view inside the conversation.
- Fullscreen: a larger app surface for workflows, tables, maps, and editors.
- Picture-in-picture: a floating view where supported by the host.
Your UI should read the display mode from host context and adapt. A weather app might show one summary card inline, then a chart and hourly table in fullscreen. A file picker might start inline, then ask for fullscreen before showing a dense list.
Host context also gives you practical UI data: theme, locale, viewport, and safe-area insets. Use those values instead of assuming a browser window size. For more detail, see MCP App Host Context: Theme, Locale, Viewport, and Safe Areas.
Where MCP Apps Run in October 2026
The official MCP extension support matrix lists ChatGPT, Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, Cursor, and other clients. It is a starting point for choosing hosts, not a promise that every host exposes every app feature. Check the matrix and test the exact client version and surface your users will run.
A host may render Resources but skip a display mode, allow Tool calls from the iframe but deny downloads, or expose host context without a host-specific upload API. OpenAI’s UI guide documents the shared bridge and ChatGPT extensions. Claude’s interactive connector guide describes where users can open app UI and how to troubleshoot a missing interface.
Build for the standard MCP Apps surface first:
- Tool metadata that points at a
ui://Resource - HTML app Resources with explicit CSP and permissions
- Tool results with clean
content,structuredContent, and_meta - Runtime checks for optional host capabilities
- Non-visual fallback content for hosts or states where UI cannot render
Then add host-specific features only when they improve a real workflow. sunpeak keeps that split explicit: the core sunpeak APIs target the shared MCP App bridge, while host-specific APIs live behind imports such as sunpeak/chatgpt or sunpeak/claude.
The July 2026 MCP specification also changed core discovery: its architecture describes per-request protocol metadata and optional server/discover. That is separate from the MCP Apps Tool-to-Resource link. Check the protocol version your SDK and host negotiate before copying raw JSON-RPC examples from another version; the examples above show Tool and Resource payloads, not complete wire messages.
Building an MCP App with sunpeak
You can implement MCP Apps by hand: define Tools, serve Resources, bundle the iframe app, handle bridge messages, and test each host state yourself. That is a good way to learn the protocol, but it becomes repetitive once you build real apps.
sunpeak is an open-source MCP App framework and testing framework that handles resource registration, bundle generation, host communication, inspection, and test fixtures.
A minimal Resource component looks like this:
// src/resources/weather/weather.tsx
import { useDisplayMode, useToolData } from 'sunpeak';
import type { ResourceConfig } from 'sunpeak';
type WeatherOutput = {
city: string;
temperatureF: number;
condition: string;
};
export const resource: ResourceConfig = {
description: 'Show current weather conditions',
};
export default function WeatherResource() {
const { output } = useToolData<WeatherOutput>();
const { displayMode } = useDisplayMode();
return (
<main>
<h1>{output.city}</h1>
<p>
{output.temperatureF} F, {output.condition}
</p>
{displayMode === 'inline' ? <p>Open fullscreen for the hourly forecast.</p> : null}
</main>
);
}
The Tool references that Resource:
// src/tools/get-weather.ts
import { z } from 'zod';
import type { AppToolConfig, ToolHandlerExtra } from 'sunpeak/mcp';
export const tool: AppToolConfig = {
resource: 'weather',
title: 'Get Weather',
description: 'Get the current weather for a city',
annotations: { readOnlyHint: true },
};
export const schema = {
city: z.string().describe('City name'),
};
export default async function getWeather(args: { city: string }, _extra: ToolHandlerExtra) {
return {
content: [{ type: 'text', text: `Weather for ${args.city}: 72 F and sunny.` }],
structuredContent: {
city: args.city,
temperatureF: 72,
condition: 'Sunny',
},
};
}
sunpeak registers the Resource, bundles the UI, links it to the Tool, and runs the local inspector.
npx sunpeak new sunpeak-app
cd sunpeak-app
pnpm dev
If you already have an MCP server, you can inspect it without moving code into a sunpeak project:
npx sunpeak inspect --server http://localhost:8000/mcp
The inspector replicates ChatGPT and Claude-style MCP App runtimes on localhost. You can switch host profiles, display modes, themes, viewport sizes, Tool input, Tool output, and app context. For automated coverage, sunpeak’s test utilities let you run E2E, visual, live-host, and eval tests in CI without burning host credits on every change.
A Practical Mental Model
When you design an MCP App, draw the boundary like this:
- Tool schema: what the model can ask your server to do.
- Tool handler: what your backend does with that request.
- Tool result: what the model and app receive.
- Resource: how the user sees and manipulates that result.
- Bridge actions: what the app can ask the host to do next.
If a value helps the model explain the answer, put it in content or structuredContent. If a value only helps the UI render, put it in _meta. If a user interaction should affect a follow-up model response, send it through the host bridge as model context instead of hiding it in local component state.
That model works across ChatGPT Apps, Claude Apps, Claude Connectors with interactive UI, and other MCP hosts. The host changes, but the Tool and Resource contract stays the same.
Get Started
npx sunpeak newFurther Reading
- What Is an MCP App? - architecture, hosts, and build path
- How to Build an MCP App - cross-host architecture guide
- MCP App Tutorial - build and test your first app
- MCP App Tool Results - content, structuredContent, and _meta
- MCP App resource templates - how dynamic Resources differ from UI Resources
- MCP App conformance testing - check the Tool and Resource contract
- MCP App Resource Metadata - CSP, permissions, and ChatGPT fields
- MCP App Lifecycle - connect, tool data, and teardown
- MCP App framework
- MCP App inspector
- Testing framework
- sunpeak documentation
- MCP Apps overview - official MCP documentation
- MCP extension support matrix
- MCP architecture - hosts, clients, servers, and current discovery
- MCP Tools specification - discovery, calls, and results
- MCP Resources specification - listing and reading URIs
- OpenAI plugin UI - MCP Apps standard and ChatGPT extensions
- Interactive connectors in Claude - supported surfaces and behavior
- MCP Apps specification repository
Frequently Asked Questions
What are the core primitives of the Model Context Protocol?
MCP servers expose Tools, Resources, and Prompts. Tools perform actions, Resources provide readable content, and Prompts provide reusable conversation templates. MCP Apps primarily use Tools and UI Resources: a Tool descriptor links to the HTML Resource that a compatible host renders. Other protocol features and supported versions vary by client.
What is the difference between an MCP host and an MCP server?
The host is the AI application the user interacts with, such as ChatGPT or Claude. It creates an MCP client connection to each server. The server exposes Tools, Resources, and Prompts; the host decides how to use them and whether to render an MCP App UI. One host can connect to several servers.
How do MCP Apps use Tools and Resources together?
An MCP App links a Tool descriptor to a ui:// Resource with _meta.ui.resourceUri. The server returns that Resource as HTML with the text/html;profile=mcp-app MIME type. A compatible host fetches the Resource, renders it in a sandboxed iframe, and delivers tool input and results to the app over the host bridge. The Tool should still return a useful non-visual result.
What is a ui:// resource URI in MCP Apps?
A ui:// URI identifies an app Resource provided by the MCP server. The Tool declares that URI in metadata, usually _meta.ui.resourceUri for MCP Apps. Some ChatGPT App integrations also map this to OpenAI compatibility fields. The host fetches the Resource from the server and renders it in a sandboxed iframe when the Tool runs.
What data should an MCP Tool return to an App Resource?
Return a useful text fallback in content, structured JSON in structuredContent when the UI needs data, and UI-only metadata in _meta when the model should not see it. Do not place secrets in _meta merely because it is hidden from the model; the app iframe receives it. If you declare an outputSchema, validate structuredContent against it.
How does communication work between an MCP App and its host?
MCP Apps run in sandboxed iframes and communicate with the host through postMessage using JSON-RPC-style messages. The host sends initialization data, tool results, host context, display mode, theme, and safe-area information. The app can call server tools through the host bridge, send messages, update model context, request display changes, open links, and trigger downloads when the host allows those actions.
Which MCP hosts support MCP Apps in 2026?
The official MCP extension support matrix lists ChatGPT, Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, Cursor, and other clients. The matrix tracks extensions at the client level; display modes, downloads, permissions, and host-specific APIs can still vary. Test the exact host and surface you plan to support.
How do I build and test an MCP App with sunpeak?
sunpeak is an open-source MCP App framework and testing framework. Run npx sunpeak new to scaffold a project, or npx sunpeak inspect --server URL to inspect an existing MCP server. The local inspector replicates ChatGPT and Claude-style MCP App runtimes so you can test tools, resources, display modes, themes, and app states without a live host account.