All posts

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

Abe Wheeler
MCP AppsMCP App FrameworkMCP App TestingChatGPT AppsChatGPT App FrameworkChatGPT App TestingClaude ConnectorsClaude Connector FrameworkMCP
The MCP concepts behind app resources, tool calls, and host-rendered UI.

The MCP concepts behind app resources, tool calls, and host-rendered UI.

You’ll run into four terms over and over when building MCP Apps: hosts, servers, Tools, and Resources. They sound abstract until you build your first app, but they map cleanly to parts you already know from web development.

The host is the AI product. The server is your backend. A Tool is the action the model can call. A Resource is the thing the host can read. An MCP App combines a Tool with a browser Resource so a model call can produce an interactive UI instead of plain text.

TL;DR: MCP Apps use standard MCP Tools and Resources. The Tool gives the model a callable action. The Resource gives the host an HTML app to render. When the model calls the Tool, the host sends the Tool result into the Resource iframe. sunpeak handles that wiring and gives you a local inspector so you can test the same flow across ChatGPT, Claude, and other MCP hosts.

What MCP Is

MCP, the Model Context Protocol, is an open protocol for connecting AI applications to external systems. It gives hosts and servers one shared way to describe capabilities, exchange data, and call actions.

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.

MCP started at Anthropic and is now governed under the Agentic AI Foundation. The MCP Apps extension builds on core MCP by defining how a host can render UI Resources from an MCP server.

Hosts and Servers

MCP has two sides.

A host is the AI application the user talks to. ChatGPT, Claude, VS Code with GitHub Copilot, Goose, Postman, MCPJam, and other clients can act as hosts. The host connects to MCP servers, discovers their capabilities, decides when the model should call a Tool, and displays the result.

A server is the backend you build. It exposes Tools, Resources, Prompts, and other MCP capabilities. The server can run locally over stdio, or remotely over Streamable HTTP. Remote servers are the normal path for shared apps and organization-wide connectors.

For MCP Apps, the split matters:

  • The host owns the chat, model session, iframe sandbox, permissions, and user consent.
  • 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. The difference is that the Tool also points at a UI Resource.

Resources

A Resource is something the host can read from the server. Standard MCP Resources are often files, documents, database rows, or generated text.

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.

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:

  1. A server exposes a Tool.
  2. The Tool metadata points at a UI Resource.
  3. The host fetches and preloads that Resource.
  4. The model calls the Tool.
  5. The host sends the Tool result into the Resource iframe.
  6. 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 MCP Apps compatibility guide now recommends starting with MCP Apps standard keys and layering ChatGPT-only fields where needed. That is the right default: keep your app portable first, then add host-specific behavior behind feature checks.

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 the main UI contract.

{
  "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-07-05"
  }
}

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 keeps the model’s view clean while giving the iframe enough data to render a proper interface.

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 July 2026

The official MCP Apps client matrix tracks support across ChatGPT, Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, Cursor, Archestra.AI, PostHog Code, and other clients. The ext-apps repository is now around 2,500 stars, which is a useful sign that the extension has developer attention beyond one host.

That does not mean every host supports every feature. A host may render Resources but skip a display mode. It may support Tool calls from the iframe but deny downloads. It may expose host context but not a host-specific upload API.

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.

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

Documentation →
npx sunpeak new

Further Reading

Frequently Asked Questions

What are the core primitives of the Model Context Protocol?

MCP servers expose Tools, Resources, Prompts, and related server features such as sampling and roots. MCP Apps mainly use Tools and Resources. A Tool is the callable action the model can invoke, and a Resource is the readable item the host can fetch. In an MCP App, the Tool metadata points at a UI Resource that contains the HTML, CSS, and JavaScript the host renders.

What is the difference between an MCP host and an MCP server?

An MCP host is the AI application the user interacts with, such as ChatGPT, Claude, VS Code, Goose, Postman, MCPJam, or another compatible client. An MCP server is the backend you build. The server exposes capabilities through MCP, and the host connects to it, discovers those capabilities, routes tool calls, and renders results or app UI.

How do MCP Apps use Tools and Resources together?

An MCP App links a Tool to a UI Resource. The Tool handles the model-callable action and returns content, structuredContent, and optional private _meta data. The Resource contains the interactive browser UI. When the model calls the Tool, the host delivers the Tool result to the preloaded Resource iframe so the UI can render the 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 user-visible text in content, typed UI data in structuredContent, and private UI-only state in _meta. The model can see content and structuredContent. The app iframe can also receive _meta, which is useful for IDs, pagination cursors, cache keys, and other values the UI needs but the model does not need to quote back.

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 Apps client matrix now tracks support across ChatGPT, Claude, Claude Desktop, VS Code GitHub Copilot, Microsoft 365 Copilot, Goose, Postman, MCPJam, Cursor, Archestra.AI, and other clients. Support differs by feature, plan, and host policy, so production apps should detect host capabilities and provide fallbacks.

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.