Skip to main content
All posts

MCP Apps SDK Guide: Packages, Architecture, and Your First App (August 2026)

Abe Wheeler
MCP AppsMCP Apps SDKMCP App FrameworkMCP App TestingChatGPT AppsChatGPT App FrameworkClaude ConnectorsClaude Connector FrameworkTypeScriptReact
The MCP Apps SDK connects an MCP server, an interactive view, and the AI host that renders it.

The MCP Apps SDK connects an MCP server, an interactive view, and the AI host that renders it.

Search for “MCP Apps SDK” and you will quickly find two SDKs, several import paths, a React entry point, server helpers, and something called AppBridge. They fit together, but the names make the first setup harder than it needs to be.

The short version is that core MCP and MCP Apps cover different connections. The base MCP SDK connects your server to an AI host. The MCP Apps SDK connects your interactive view to that host after the host renders it in a sandboxed iframe.

TL;DR: Install @modelcontextprotocol/ext-apps for the interactive app layer. A manual TypeScript server also needs a compatible base MCP SDK. Use @modelcontextprotocol/ext-apps in the view, /react for optional React helpers, /server for tool and resource registration helpers, and /app-bridge only when you are building an MCP host. Keep the server-to-host and view-to-host contracts separate, then test both.

The MCP Apps SDK Is One Layer of the App

An MCP App has three running parts:

MCP server
  <--- MCP over HTTP or stdio --->
AI host
  <--- MCP Apps JSON-RPC over postMessage --->
sandboxed app view

The base MCP SDK owns the top connection. It gives your server tools, resources, schemas, transports, and request handlers.

The MCP Apps SDK owns the bottom connection. It gives the iframe a typed way to receive tool data, read host context, request display mode changes, call server tools through the host, and take other actions that the host allows.

The host sits in the middle because the iframe cannot reach into the parent page. The MCP Apps overview specifies a sandboxed iframe and postMessage bridge. That boundary keeps the app isolated from host cookies, local storage, and page DOM.

This split explains a common setup bug: a working MCP server is not yet an MCP App. The server also needs a UI resource, the tool needs metadata that points to it, and the view needs a bridge connection to the host.

The Package Map

Most developers need two installed packages and only a few imports:

ImportJobWho needs it
@modelcontextprotocol/sdkCore MCP server, tools, resources, and transports in the current low-level app quickstartServer authors using the compatible v1 stack
@modelcontextprotocol/ext-appsApp, view lifecycle, host context, and view-to-host actionsApp view developers
@modelcontextprotocol/ext-apps/reactuseApp and host style helpersReact view developers
@modelcontextprotocol/ext-apps/serverregisterAppTool, registerAppResource, and RESOURCE_MIME_TYPEMCP App server authors
@modelcontextprotocol/ext-apps/app-bridgeHost-side iframe rendering and message proxyingMCP host developers

The last three are subpath exports from @modelcontextprotocol/ext-apps. Do not try to install them as separate npm packages.

The base SDK also has a v2 generation with split packages such as @modelcontextprotocol/server, @modelcontextprotocol/client, and runtime adapters. That package split changes the server side, not the view protocol. The compatibility section below explains how to choose a coherent set.

Pick Your Role Before Picking Imports

The official package has APIs for three different jobs. You rarely need all of them.

You Are Building an App

Most readers are here. You own:

  • An MCP server with one or more tools.
  • One or more ui:// resources containing HTML, CSS, and JavaScript.
  • The code inside each resource iframe.
  • Tests for tool contracts and rendered behavior.

Use the App class directly, use the React helpers, or use an MCP App framework that wraps them.

You Are Adding UI to an Existing MCP Server

Keep your current tool logic, then add:

  1. _meta.ui.resourceUri to the UI tool.
  2. A matching app resource.
  3. The MCP App resource MIME type.
  4. A bundled view that connects to the host.

The server can be written in any language because the host sees the MCP protocol, not your implementation. The TypeScript SDK is convenient, but it is not a protocol requirement.

You Are Building an MCP Host

Use @modelcontextprotocol/ext-apps/app-bridge only if you are building the client that embeds apps. Host work includes:

  • Detecting a tool’s linked UI resource.
  • Reading the resource from the MCP server.
  • Applying sandbox and Content Security Policy rules.
  • Creating and sizing the iframe.
  • Proxying allowed tool calls and app requests.
  • Sending tool input, tool results, cancellation, and host context to the view.

If your app runs inside an existing host, the host already owns this work.

Install the Direct SDK Stack

The current official MCP Apps build guide uses:

npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk

That is the clearest low-level path when you want to learn the protocol or own the server and bundler setup yourself.

You will also need a web framework or transport for the server, a bundler such as Vite, and whatever UI library you choose. The app view can use React, Vue, Svelte, Preact, Solid, or plain JavaScript. The protocol does not require React.

Do not add app-bridge to an app project just because it appears in the API docs. It is a subpath import for host implementations, not a second bridge that the view needs.

Register the Tool and UI Resource

The server side links a tool to a view with a shared resource URI. The helpers from /server keep the metadata and MIME type consistent:

import fs from 'node:fs/promises';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import {
  registerAppResource,
  registerAppTool,
  RESOURCE_MIME_TYPE,
} from '@modelcontextprotocol/ext-apps/server';

const server = new McpServer({
  name: 'orders-app',
  version: '1.0.0',
});

const resourceUri = 'ui://orders/summary.html';

registerAppTool(
  server,
  'show-orders',
  {
    title: 'Show Orders',
    description: 'Show the open orders for an account',
    inputSchema: {},
    _meta: {
      ui: { resourceUri },
    },
  },
  async () => ({
    content: [{ type: 'text', text: 'Found 2 open orders.' }],
    structuredContent: {
      orders: [
        { id: 'A-104', total: 82 },
        { id: 'A-105', total: 31 },
      ],
    },
  })
);

registerAppResource(
  server,
  'Orders summary',
  resourceUri,
  {
    description: 'Interactive order summary',
  },
  async () => ({
    contents: [
      {
        uri: resourceUri,
        mimeType: RESOURCE_MIME_TYPE,
        text: await fs.readFile(new URL('./dist/orders.html', import.meta.url), 'utf8'),
      },
    ],
  })
);

Three values form the link:

  • The tool descriptor contains _meta.ui.resourceUri.
  • The registered resource uses the exact same URI.
  • The returned resource uses text/html;profile=mcp-app, preferably through RESOURCE_MIME_TYPE.

If any one of those differs, the tool may still run while the UI stays blank. Read the UI resource guide for resource metadata, resource links, and common mismatches.

Connect the View With the App Class

Inside the bundled HTML, the framework-agnostic App class handles the handshake and typed bridge messages:

import { App } from '@modelcontextprotocol/ext-apps';

type OrdersResult = {
  orders: Array<{ id: string; total: number }>;
};

const root = document.querySelector<HTMLDivElement>('#app');

const app = new App({ name: 'orders-view', version: '1.0.0' }, {}, { autoResize: true });

app.ontoolresult = (result) => {
  const data = result.structuredContent as OrdersResult | undefined;
  if (!root || !data) return;

  root.replaceChildren(
    ...data.orders.map((order) => {
      const row = document.createElement('p');
      row.textContent = `${order.id}: $${order.total}`;
      return row;
    })
  );
};

app.onhostcontextchanged = (context) => {
  document.documentElement.dataset.theme = context.theme ?? 'light';
};

await app.connect();

const initialContext = app.getHostContext();
document.documentElement.dataset.theme = initialContext?.theme ?? 'light';

Register handlers before connect() so the view does not miss early notifications. connect() performs the app initialization handshake, records host capabilities and context, and starts automatic resize notifications by default.

The App object also exposes methods for app-to-host actions, including server tool calls, display mode requests, links, downloads, messages, and model context updates. Check host capabilities before using optional actions because hosts can support different subsets.

For the full event order, see the MCP App lifecycle guide.

What the React Entry Point Does

@modelcontextprotocol/ext-apps/react is optional. It gives React apps helpers for:

  • Creating and connecting an App instance with useApp.
  • Applying host CSS variables and fonts.
  • Updating document theme.
  • Controlling automatic resize behavior when needed.

It does not turn the low-level SDK into a full app framework. You still own the server layout, file discovery, resource bundling, tool result state, tests, and deployment unless another framework handles them.

A small React view starts like this:

import { useState } from 'react';
import { useApp } from '@modelcontextprotocol/ext-apps/react';

type OrdersResult = {
  orders: Array<{ id: string; total: number }>;
};

export function OrdersView() {
  const [data, setData] = useState<OrdersResult>();
  const { isConnected, error } = useApp({
    appInfo: { name: 'orders-view', version: '1.0.0' },
    capabilities: {},
    onAppCreated: (app) => {
      app.ontoolresult = (result) => {
        setData(result.structuredContent as OrdersResult);
      };
    },
  });

  if (error) return <p>Could not connect to the host.</p>;
  if (!isConnected || !data) return <p>Loading orders...</p>;

  return (
    <ul>
      {data.orders.map((order) => (
        <li key={order.id}>
          {order.id}: ${order.total}
        </li>
      ))}
    </ul>
  );
}

This is enough for a small app. As the app grows, write an adapter around the SDK events or use a framework with typed data hooks so result parsing does not spread across components.

Understand the v1 and v2 TypeScript SDK Boundary

As of August 3, 2026, the published @modelcontextprotocol/ext-apps 1.7.5 package declares @modelcontextprotocol/sdk 1.x as a peer dependency. The newer base MCP TypeScript SDK v2 uses split packages, including @modelcontextprotocol/server.

That means this combination deserves an explicit compatibility check:

@modelcontextprotocol/server v2
@modelcontextprotocol/ext-apps/server

Do not assume the current registerAppTool and registerAppResource types accept a v2 McpServer. If TypeScript reports a mismatch, do not cast the server or install with --force.

Use one of these paths:

  1. Follow the current MCP Apps quickstart with the compatible v1 base SDK.
  2. Use an ext-apps release that explicitly supports your v2 server package.
  3. Register the app tool and resource with the v2 server APIs directly, preserving _meta.ui.resourceUri, app visibility metadata, RESOURCE_MIME_TYPE, and resource metadata.
  4. Use a framework that owns the compatible SDK versions and registration layer.

If you already have a v1 server, the MCP TypeScript SDK v2 migration guide covers the package split and the MCP App-specific checks.

Choose the Direct SDK or a Framework

The direct SDK is a good fit when:

  • You already have an MCP server architecture you want to keep.
  • You need exact control over the bridge, bundler, or resource metadata.
  • You are building a host or protocol library.
  • You want to learn the wire-level app model.

A framework is a better fit when you want conventions for project layout, tool and resource discovery, build output, host adapters, simulations, and browser tests.

The choice does not change the protocol. A sound framework should still produce normal MCP tools, ui:// resources, portable metadata, and a view that speaks the MCP Apps bridge.

Test the Contracts in Order

An app can pass a server unit test and still fail in a host. Test from the protocol outward.

1. Tool Discovery

Call tools/list and assert that:

  • The tool name and schema are correct.
  • _meta.ui.resourceUri exists.
  • App-only tools are hidden from the model when appropriate.

2. Resource Loading

Call resources/read with the linked URI and assert that:

  • The URI matches the tool descriptor.
  • The MIME type is text/html;profile=mcp-app.
  • The HTML contains the built entry code and required assets.
  • CSP metadata covers every external API and asset domain.

3. Tool Results

Call the UI tool and test:

  • content gives non-UI hosts a useful fallback.
  • structuredContent matches the view’s expected shape.
  • _meta contains only app-only data.
  • Error and empty results use deliberate shapes.

4. Rendered View

Open the resource in an MCP App host runtime and cover:

  • Partial input, complete input, success, error, and cancellation.
  • Light and dark themes.
  • Inline, fullscreen, and picture-in-picture where supported.
  • Mobile sizes, safe areas, and host context changes.
  • Every button that calls a tool or requests a host action.
  • Denied and unsupported capability paths.

5. Live Host Smoke Test

Keep a small test in each production host you support. Local host replicas catch most app bugs faster, while live tests catch authentication, deployment, connection, and host-release differences.

With sunpeak’s MCP testing framework, you can run protocol tests and Playwright tests against replicated ChatGPT and Claude runtimes for any MCP server. The Inspector also lets you switch host, theme, display mode, viewport, and simulation data without deploying.

A Practical Starting Rule

Use @modelcontextprotocol/ext-apps when you want the lowest-level official app API. Add only the subpath imports required by your role. Keep the base MCP server SDK on a version that the app helpers support, and treat a type mismatch between the two as a real compatibility warning.

If you would rather start with working project conventions, npx sunpeak new creates a typed React MCP App framework project with resource and tool discovery, local host simulation, and tests already wired. Either path should lead to the same portable MCP App contract.

Get Started

Documentation →
npx sunpeak new

Further Reading

Frequently Asked Questions

What is the MCP Apps SDK?

The MCP Apps SDK is the @modelcontextprotocol/ext-apps package. It implements the interactive UI extension to MCP, including the App class for view-to-host communication, optional React helpers, server helpers for registering UI tools and resources, and AppBridge for developers building an MCP host. It works alongside a base MCP SDK, which handles the connection between the host and your MCP server.

What is the difference between @modelcontextprotocol/ext-apps and @modelcontextprotocol/sdk?

@modelcontextprotocol/sdk implements core MCP features such as tools, resources, server transports, and client connections. @modelcontextprotocol/ext-apps adds the interactive app layer: UI resource metadata, the MCP App MIME type, the iframe-to-host bridge, host context, display modes, app state, and app actions. A manually built TypeScript MCP App normally needs both layers.

Which MCP Apps SDK packages do I need to install?

For the current official low-level TypeScript quickstart, install @modelcontextprotocol/ext-apps and @modelcontextprotocol/sdk. Import view APIs from @modelcontextprotocol/ext-apps, React helpers from @modelcontextprotocol/ext-apps/react, and registration helpers from @modelcontextprotocol/ext-apps/server. These are subpath exports from one ext-apps package, so you do not install them separately. Use @modelcontextprotocol/ext-apps/app-bridge only when building an MCP host.

Can I use the MCP Apps SDK with React, Vue, Svelte, or plain JavaScript?

Yes. The App class and postMessage protocol are framework-agnostic. The official repository includes starter templates for React, Vue, Svelte, Preact, Solid, and vanilla JavaScript. The @modelcontextprotocol/ext-apps/react entry point is optional and adds React hooks for connection and host styles.

Do I need AppBridge to build an MCP App?

No. AppBridge is for the host side, such as an AI client that discovers UI resources, creates sandboxed iframes, and proxies messages between apps and MCP servers. If you are building an app that runs inside ChatGPT, Claude, or another existing host, use the App class or a framework and let the host own AppBridge.

Can I use MCP TypeScript SDK v2 with @modelcontextprotocol/ext-apps?

Check the exact ext-apps release before combining them. As of August 3, 2026, @modelcontextprotocol/ext-apps 1.7.5 declares @modelcontextprotocol/sdk 1.x as a peer dependency, while the TypeScript SDK v2 uses split packages such as @modelcontextprotocol/server. Use a compatible set, or register MCP App metadata directly with the v2 server while keeping ext-apps in the view. Do not silence type errors at this boundary.

How should I test an app built with the MCP Apps SDK?

Test the full chain: tools/list exposes _meta.ui.resourceUri, resources/read returns text/html;profile=mcp-app, tools/call returns the expected content and structuredContent, and the rendered iframe handles input, results, errors, cancellation, host context, display modes, and app actions. Add browser tests across each target host runtime, then keep a small live-host smoke test for deployment and host-specific behavior.