### Test Host Setup
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Commands to clone the ext-apps repository, install dependencies, and start the basic-host example.
```bash
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
cd examples/basic-host
npm start
```
--------------------------------
### Install dependencies
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Install the dependencies you'll need.
```bash
npm init -y
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk express cors
npm install -D typescript vite vite-plugin-singlefile @types/express @types/cors @types/node tsx concurrently cross-env
```
--------------------------------
### Running Examples with basic-host
Source: https://apps.extensions.modelcontextprotocol.io/api/index.html
Steps to clone the repository, install dependencies, and start the basic-host for running examples.
```bash
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
npm start
```
--------------------------------
### Create project directory
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Start by creating a project directory.
```bash
mkdir my-mcp-app && cd my-mcp-app
```
--------------------------------
### Start Server Command
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Command to start the MCP server in watch mode.
```bash
npm start
```
--------------------------------
### Configure package.json scripts
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Configure your package.json scripts for building and starting the app.
```bash
npm pkg set type=module
npm pkg set scripts.build="tsc --noEmit && tsc -p tsconfig.server.json && cross-env INPUT=mcp-app.html vite build"
npm pkg set scripts.start='concurrently --raw "cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch" "tsx watch main.ts"'
```
--------------------------------
### Configure package.json scripts
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html
Sets up npm scripts for building and starting the MCP app, including build commands for TypeScript compilation and Vite bundling, and a start command for development with hot-reloading.
```bash
npm pkg set type=module
npm pkg set scripts.build="tsc --noEmit && tsc -p tsconfig.server.json && cross-env INPUT=mcp-app.html vite build"
npm pkg set scripts.start='concurrently "cross-env NODE_ENV=development INPUT=mcp-app.html vite build --watch" "tsx watch main.ts"'
```
--------------------------------
### Server Listening Message
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
The expected output message when the MCP server starts successfully.
```bash
MCP server listening on http://localhost:3001/mcp
```
--------------------------------
### HTML for the View
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Creates the HTML structure for the 'Get Time' application, including a paragraph to display the server time and a button to trigger the time retrieval.
```html
Get Time App
Server Time:Loading...
```
--------------------------------
### Building and running examples
Source: https://apps.extensions.modelcontextprotocol.io/api/media/CONTRIBUTING.md
Command to build and run examples.
```bash
npm run examples:start
```
--------------------------------
### Clone repository and install dependencies
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/Testing_MCP_Apps.html
Steps to clone the MCP Apps repository and install necessary dependencies for the basic-host example.
```bash
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
cd examples/basic-host
```
--------------------------------
### main.ts
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
This file serves as the entry point for the application, providing functions to start the MCP server using either Streamable HTTP transport or stdio transport. It includes logic to select the transport based on command-line arguments.
```typescript
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
/**
* Starts an MCP server with Streamable HTTP transport in stateless mode.
*
* @param createServer - Factory function that creates a new McpServer instance per request.
*/
export async function startStreamableHTTPServer(
createServer: () => McpServer,
): Promise {
const port = parseInt(process.env.PORT ?? "3001", 10);
const app = createMcpExpressApp({ host: "0.0.0.0" });
app.use(cors());
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on("close", () => {
transport.close().catch(() => {});
server.close().catch(() => {});
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("MCP error:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(port, (err) => {
if (err) {
console.error("Failed to start server:", err);
process.exit(1);
}
console.log(`MCP server listening on http://localhost:${port}/mcp`);
});
const shutdown = () => {
console.log("\nShutting down...");
httpServer.close(() => process.exit(0));
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
/**
* Starts an MCP server with stdio transport.
*
* @param createServer - Factory function that creates a new McpServer instance.
*/
export async function startStdioServer(
createServer: () => McpServer,
): Promise {
await createServer().connect(new StdioServerTransport());
}
async function main() {
if (process.argv.includes("--stdio")) {
await startStdioServer(createServer);
} else {
await startStreamableHTTPServer(createServer);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
```
--------------------------------
### MCP Apps Server Setup
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/migrate-openai-app.html
Example of setting up an MCP Apps server, registering a tool, and registering a UI resource.
```typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { z } from "zod";
function createServer() {
const server = new McpServer({ name: "shop", version: "1.0.0" });
// Register tool with MCP Apps metadata
registerAppTool(
server,
"shopping-cart",
{
title: "Shopping Cart",
description: "Display the user's shopping cart",
inputSchema: { userId: z.string() },
annotations: { readOnlyHint: true },
_meta: { ui: { resourceUri: "ui://view/cart.html" } },
},
async (args) => {
const cart = await getCart(args.userId);
return {
content: [{ type: "text", text: JSON.stringify(cart) }],
structuredContent: { cart },
};
},
);
// Register UI resource
registerAppResource(
server,
"Cart View",
"ui://view/cart.html",
{ description: "Shopping cart UI" },
async () => ({
contents: [
{
uri: "ui://view/cart.html",
mimeType: RESOURCE_MIME_TYPE,
text: getCartHtml(),
_meta: {
ui: {
csp: {
resourceDomains: ["https://cdn.example.com"],
connectDomains: ["https://api.example.com"],
frameDomains: ["https://embed.example.com"],
},
},
},
},
],
}),
);
return server;
}
```
--------------------------------
### main.ts
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html
This file serves as the entry point for the application and provides functions to start the MCP server using either Streamable HTTP transport or stdio transport. It configures Express.js for the HTTP server, including CORS handling and request routing for MCP requests. It also includes graceful shutdown handling for server termination.
```typescript
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import cors from "cors";
import type { Request, Response } from "express";
import { createServer } from "./server.js";
/**
* Starts an MCP server with Streamable HTTP transport in stateless mode.
*
* @param createServer - Factory function that creates a new McpServer instance per request.
*/
export async function startStreamableHTTPServer(
createServer: () => McpServer,
): Promise {
const port = parseInt(process.env.PORT ?? "3001", 10);
const app = createMcpExpressApp({ host: "0.0.0.0" });
app.use(cors());
app.all("/mcp", async (req: Request, res: Response) => {
const server = createServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
res.on("close", () => {
transport.close().catch(() => {});
server.close().catch(() => {});
});
try {
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("MCP error:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: { code: -32603, message: "Internal server error" },
id: null,
});
}
}
});
const httpServer = app.listen(port, (err) => {
if (err) {
console.error("Failed to start server:", err);
process.exit(1);
}
console.log(`MCP server listening on http://localhost:${port}/mcp`);
});
const shutdown = () => {
console.log("\nShutting down...");
httpServer.close(() => process.exit(0));
};
process.on("SIGINT", shutdown);
process.on("SIGTERM", shutdown);
}
/**
* Starts an MCP server with stdio transport.
*
* @param createServer - Factory function that creates a new McpServer instance.
*/
export async function startStdioServer(
createServer: () => McpServer,
): Promise {
await createServer().connect(new StdioServerTransport());
}
async function main() {
if (process.argv.includes("--stdio")) {
await startStdioServer(createServer);
} else {
await startStreamableHTTPServer(createServer);
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
```
--------------------------------
### Starting development environment with hot reloading
Source: https://apps.extensions.modelcontextprotocol.io/api/media/CONTRIBUTING.md
Command to start the development environment with hot reloading for examples.
```bash
npm run examples:dev
```
--------------------------------
### Manual setup for custom scenarios
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html
This example shows how to manually set up automatic size change notifications when autoResize is initially false.
```typescript
const app = new App(
{ name: "MyApp", version: "1.0.0" },
{},
{ autoResize: false },
);
await app.connect(transport);
// Later, enable auto-resize manually
const cleanup = app.setupSizeChangedNotifications();
// Clean up when done
cleanup();
```
--------------------------------
### Manual Installation
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/agent-skills.html
Cloning the repository for manual installation.
```git
git clone https://github.com/modelcontextprotocol/ext-apps.git
```
--------------------------------
### Installing dependencies
Source: https://apps.extensions.modelcontextprotocol.io/api/media/CONTRIBUTING.md
Command to install project dependencies.
```bash
npm install
```
--------------------------------
### Install beta version
Source: https://apps.extensions.modelcontextprotocol.io/api/media/CONTRIBUTING.md
Command to install a beta version of the package.
```bash
npm install @modelcontextprotocol/ext-apps@beta
```
--------------------------------
### Start basic-host with multiple servers
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/Testing_MCP_Apps.html
Command to start the basic-host, connecting to multiple local MCP servers.
```bash
SERVERS='["http://localhost:3001/mcp", "http://localhost:3002/mcp"]' npm start
```
--------------------------------
### Example: View ready
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html
This example shows how to set up a callback for `oninitialized` to log a message and send tool input to the View.
```javascript
bridge.oninitialized = () => {
console.log("View ready");
bridge.sendToolInput({ arguments: toolArgs });
};
```
--------------------------------
### Example: Basic usage
Source: https://apps.extensions.modelcontextprotocol.io/api/functions/server-helpers.registerAppResource.html
This example demonstrates the basic usage of the registerAppResource function.
```typescript
registerAppResource(
server,
"Weather View",
"ui://weather/view.html",
{
description: "Interactive weather display",
},
async () => ({
contents: [
{
uri: "ui://weather/view.html",
mimeType: RESOURCE_MIME_TYPE,
text: await fs.readFile("dist/view.html", "utf-8"),
},
],
}),
);
```
--------------------------------
### Start basic-host with a single server
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/Testing_MCP_Apps.html
Command to start the basic-host, pointing it to a single local MCP server.
```bash
SERVERS='["http://localhost:3001/mcp"]' npm start
```
--------------------------------
### Clone and Install Repository
Source: https://apps.extensions.modelcontextprotocol.io/api/media/CONTRIBUTING.md
Steps to clone the repository and install dependencies for local development.
```bash
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps
npm install
```
--------------------------------
### Example: With CSP configuration for network access
Source: https://apps.extensions.modelcontextprotocol.io/api/functions/server-helpers.registerAppResource.html
This example shows how to configure CSP for network access when registering an app resource.
```typescript
registerAppResource(
server,
"Music Player",
"ui://music/player.html",
{
description: "Audio player with external soundfonts",
},
async () => ({
contents: [
{
uri: "ui://music/player.html",
mimeType: RESOURCE_MIME_TYPE,
text: musicPlayerHtml,
_meta: {
ui: {
csp: {
resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images
connectDomains: ["https://api.example.com"], // For fetch/WebSocket
},
},
},
},
],
}),
);
```
--------------------------------
### Client-Side Migration Example - Before (OpenAI)
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/migrate-openai-app.html
Example code demonstrating how to use the OpenAI Apps SDK for various functionalities.
```javascript
// OpenAI Apps SDK
applyTheme(window.openai.theme);
console.log("Tool args:", window.openai.toolInput);
console.log("Tool result:", window.openai.toolOutput);
// Call a tool
const result = await window.openai.callTool("get_weather", { city: "Tokyo" });
// Send a message
await window.openai.sendFollowUpMessage({ prompt: "Weather updated!" });
// Report height
window.openai.notifyIntrinsicHeight(400);
// Open link
await window.openai.openExternal({ href: "https://example.com" });
```
--------------------------------
### Example: Apply style variables from host context
Source: https://apps.extensions.modelcontextprotocol.io/api/functions/app.applyHostStyleVariables.html
This example shows how to use CSS variables in your styles and apply them when the host context changes or after connecting.
```javascript
// Use CSS variables in your styles
document.body.style.background = "var(--color-background-primary)";
// Apply when host context changes
app.onhostcontextchanged = (ctx) => {
if (ctx.styles?.variables) {
applyHostStyleVariables(ctx.styles.variables);
}
};
// Apply initial styles after connecting
app.connect().then(() => {
const ctx = app.getHostContext();
if (ctx?.styles?.variables) {
applyHostStyleVariables(ctx.styles.variables);
}
});
```
--------------------------------
### Install via Vercel Skills CLI
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/agent-skills.html
Installation command using the Vercel Skills CLI.
```bash
npx skills add modelcontextprotocol/ext-apps
```
--------------------------------
### Client-Side Migration Example - After (MCP Apps)
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/migrate-openai-app.html
Example code demonstrating how to use the MCP Apps SDK, including registering handlers before connecting and using MCP-specific APIs.
```javascript
import { App } from "@modelcontextprotocol/ext-apps";
const app = new App({ name: "MyApp", version: "1.0.0" });
// Register handlers BEFORE connect (events may occur immediately after connect)
app.ontoolinput = (params) => {
console.log("Tool args:", params.arguments);
};
app.ontoolresult = (params) => {
console.log("Tool result:", params.structuredContent);
};
app.onhostcontextchanged = (ctx) => {
if (ctx.theme) applyTheme(ctx.theme);
};
// Connect (auto-detects OpenAI vs MCP)
await app.connect();
// Access context
applyTheme(app.getHostContext()?.theme);
// Call a tool
const result = await app.callServerTool({
name: "get_weather",
arguments: { city: "Tokyo" },
});
// Send a message
await app.sendMessage({
role: "user",
content: [{ type: "text", text: "Weather updated!" }],
});
// Open link (note: url not href)
await app.openLink({ url: "https://example.com" });
```
--------------------------------
### Example: Forward to your LLM provider
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html
This example shows how to set up a callback for `createSamplingMessage` to forward the message to an LLM provider.
```javascript
bridge.oncreatesamplingmessage = async (params, extra) => {
// Apply rate limiting, user approval, cost controls here
return await myLlmProvider.complete(params, { signal: extra.signal });
};
```
--------------------------------
### App constructor example
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html
This example demonstrates how to create a new MCP App instance with basic information, capabilities, and options.
```typescript
const app = new App(
{ name: "MyApp", version: "1.0.0" },
{ tools: { listChanged: true } }, // capabilities
{ autoResize: true }, // options
);
```
--------------------------------
### Example: Simple completion
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html
Creates a message and logs the content. This is a basic example of using the `createSamplingMessage` function.
```javascript
const result = await app.createSamplingMessage({
messages: [
{
role: "user",
content: { type: "text", text: "Summarize this in one line." },
},
],
maxTokens: 100,
});
console.log(result.content);
```
--------------------------------
### Example: Agentic loop with tools
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html
Demonstrates an agentic loop with tools, checking host capabilities and handling tool use. This example shows how to define tools and process results when the `stopReason` is 'toolUse'.
```javascript
if (!app.getHostCapabilities()?.sampling?.tools) return;
const result = await app.createSamplingMessage({
messages,
maxTokens: 1024,
tools: [
{
name: "get_weather",
description: "Get the current weather",
inputSchema: {
type: "object",
properties: { city: { type: "string" } },
},
},
],
});
if (result.stopReason === "toolUse") {
// result.content may be an array containing tool_use blocks
}
```
--------------------------------
### Example: Download a JSON file (embedded text resource)
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html
This example demonstrates how to use the `downloadFile` method to download an embedded JSON file. It shows how to construct the `resource` object with `uri`, `mimeType`, and `text` properties, and how to handle potential download denials.
```javascript
const data = JSON.stringify({ items: selectedItems }, null, 2);
const { isError } = await app.downloadFile({
contents: [{
type: "resource",
resource: {
uri: "file:///export.json",
mimeType: "application/json",
text: data,
},
}],
});
if (isError) {
console.warn("Download denied or cancelled");
}
```
--------------------------------
### Compile Project
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Command to verify that the project compiles successfully. No output indicates success.
```bash
npx tsc --noEmit
```
--------------------------------
### Project Directory Structure
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
This shows the expected directory structure after setting up the project.
```bash
my-mcp-app/
├── main.ts
├── package.json
├── server.ts
├── tsconfig.json
├── tsconfig.server.json
└── vite.config.ts
```
--------------------------------
### Build Command
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Command to build the bundled View for the MCP application.
```bash
npm run build
```
--------------------------------
### Fetch updated weather data
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html
Example of calling a server tool to get weather data.
```javascript
try {
const result = await app.callServerTool({
name: "get_weather",
arguments: { location: "Tokyo" },
});
if (result.isError) {
console.error("Tool returned error:", result.content);
} else {
console.log(result.content);
}
} catch (error) {
console.error("Tool call failed:", error);
}
```
--------------------------------
### Build Output
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Indicates the output file generated after running the build command.
```bash
$ ls dist/
mcp-app.html
```
--------------------------------
### Vite Configuration
Source: https://apps.extensions.modelcontextprotocol.io/api/media/SKILL-3.md
Configure Vite to use `vite-plugin-singlefile` for bundling the MCP App into a single HTML file. This example assumes a basic Vite setup; add framework-specific plugins as needed.
```typescript
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
outDir: "dist",
rollupOptions: {
input: "mcp-app.html",
},
},
});
```
--------------------------------
### TypeScript for the View
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Connects the HTML View to the MCP host, handles user interactions by calling server tools, and displays the received server time.
```typescript
import { App } from "@modelcontextprotocol/ext-apps";
// Get element references
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
// Create app instance
const app = new App({ name: "Get Time App", version: "1.0.0" });
// Handle tool results from the server. Set before `app.connect()` to avoid
// missing the initial tool result.
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
};
// Wire up button click
getTimeBtn.addEventListener("click", async () => {
// `app.callServerTool()` lets the UI request fresh data from the server
const result = await app.callServerTool({ name: "get-time", arguments: {} });
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
});
// Connect to host
app.connect();
```
--------------------------------
### Directory Structure
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Shows the expected file structure for the 'my-mcp-app' directory after creating the HTML and TypeScript files.
```bash
my-mcp-app/
├── main.ts
├── mcp-app.html
├── package.json
├── server.ts
├── src/
│ └── mcp-app.ts
├── tsconfig.json
├── tsconfig.server.json
└── vite.config.ts
```
--------------------------------
### server.ts
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
This file registers a tool and its associated UI resource with the MCP server. It demonstrates the two-part registration process where a tool is linked to a resource via a URI, enabling interactive UIs when the tool is called.
```typescript
import {
registerAppResource,
registerAppTool,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import fs from "node:fs/promises";
import path from "node:path";
const DIST_DIR = path.join(import.meta.dirname, "dist");
/**
* Creates a new MCP server instance with tools and resources registered.
*/
export function createServer(): McpServer {
const server = new McpServer({
name: "Quickstart MCP App Server",
version: "1.0.0",
});
// Two-part registration: tool + resource, tied together by the resource URI.
const resourceUri = "ui://get-time/mcp-app.html";
// Register a tool with UI metadata. When the host calls this tool, it reads
// `_meta.ui.resourceUri` to know which resource to fetch and render as an
// interactive UI.
registerAppTool(
server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time.",
inputSchema: {},
_meta: { ui: { resourceUri } }, // Links this tool to its UI resource
},
async () => {
const time = new Date().toISOString();
return { content: [{ type: "text", text: time }] };
},
);
// Register the resource, which returns the bundled HTML/JavaScript for the UI.
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(path.join(DIST_DIR, "mcp-app.html"), "utf-8");
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
},
);
return server;
}
```
--------------------------------
### Testing with basic-host
Source: https://apps.extensions.modelcontextprotocol.io/api/media/SKILL-1.md
Commands to build and run the server, and then run the basic-host example for testing.
```bash
# Terminal 1: Build and run your server
npm run build && npm run serve
# Terminal 2: Run basic-host (from cloned repo)
cd /tmp/mcp-ext-apps/examples/basic-host
npm install
SERVERS='["http://localhost:3001/mcp"]' npm run start
# Open http://localhost:8080
```
--------------------------------
### sendToolCancelled Example
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html
Example of how to send a tool cancellation notification with a reason.
```javascript
// User clicked "Cancel" button
bridge.sendToolCancelled({ reason: "User cancelled the operation" });
```
--------------------------------
### Create tsconfig.server.json
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Create tsconfig.server.json for compiling server-side code.
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"emitDeclarationOnly": true,
"outDir": "./dist",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["server.ts", "main.ts"]
}
```
--------------------------------
### sendResourceListChanged Example
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html
Example of how to use sendResourceListChanged in an MCP client notification handler.
```javascript
// In your MCP client notification handler:
mcpClient.setNotificationHandler(ResourceListChangedNotificationSchema, () => {
bridge.sendResourceListChanged();
});
```
--------------------------------
### Example
Source: https://apps.extensions.modelcontextprotocol.io/api/modules/server-helpers.html
Register a tool that displays a view and the HTML resource the tool references.
```javascript
// Register a tool that displays a view
registerAppTool(
server,
"weather",
{
description: "Get weather forecast",
_meta: { ui: { resourceUri: "ui://weather/view.html" } },
},
toolCallback,
);
// Register the HTML resource the tool references
registerAppResource(
server,
"Weather View",
"ui://weather/view.html",
{},
readCallback,
);
```
--------------------------------
### sendPromptListChanged Example
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html
Example of how to use sendPromptListChanged in an MCP client notification handler.
```javascript
// In your MCP client notification handler:
mcpClient.setNotificationHandler(PromptListChangedNotificationSchema, () => {
bridge.sendPromptListChanged();
});
```
--------------------------------
### Create vite.config.ts
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Create vite.config.ts to bundle the UI into a single HTML file.
```typescript
import { createLogger, defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
const INPUT = process.env.INPUT;
if (!INPUT) {
throw new Error("INPUT environment variable is not set");
}
const isDevelopment = process.env.NODE_ENV === "development";
const prefixedLogger = createLogger();
for (const level of ["info", "warn", "error"] as const) {
const fn = prefixedLogger[level];
prefixedLogger[level] = (msg, opts) => fn(msg.replace(/^/mg, "[vite] "), opts);
}
export default defineConfig({
customLogger: prefixedLogger,
plugins: [viteSingleFile()],
build: {
sourcemap: isDevelopment ? "inline" : undefined,
cssMinify: !isDevelopment,
minify: !isDevelopment,
rollupOptions: {
input: INPUT,
},
outDir: "dist",
emptyOutDir: false,
},
});
```
--------------------------------
### Domain Example 2
Source: https://apps.extensions.modelcontextprotocol.io/api/interfaces/app.McpUiResourceMeta.html
Example of a URL-derived subdomain for the domain field.
```string
"www-example-com.oaiusercontent.com"
```
--------------------------------
### Domain Example 1
Source: https://apps.extensions.modelcontextprotocol.io/api/interfaces/app.McpUiResourceMeta.html
Example of a hash-based subdomain for the domain field.
```string
"a904794854a047f6.claudemcpcontent.com"
```
--------------------------------
### Create tsconfig.json
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html
Create the tsconfig.json file for general TypeScript compilation.
```json
{
"compilerOptions": {
"target": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "server.ts", "main.ts"]
}
```
--------------------------------
### useHostStyles Example
Source: https://apps.extensions.modelcontextprotocol.io/api/functions/_modelcontextprotocol_ext-apps_react.useHostStyles.html
Applies all host styles - pass initial context to apply styles from connect() immediately
```javascript
function MyApp() {
const { app } = useApp({
appInfo: { name: "MyApp", version: "1.0.0" },
capabilities: {},
});
// Apply all host styles - pass initial context to apply styles from connect() immediately
useHostStyles(app, app?.getHostContext());
return (
Hello!
);
}
```
--------------------------------
### Install via Claude Code Plugin
Source: https://apps.extensions.modelcontextprotocol.io/api/documents/agent-skills.html
Installation command for Claude Code plugin.
```bash
/plugin marketplace add modelcontextprotocol/ext-apps
/plugin install mcp-apps@modelcontextprotocol-ext-apps
```
--------------------------------
### sendToolResult example
Source: https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html
Example of sending a tool execution result using the AppBridge client.
```typescript
const result = await mcpClient.request(
{ method: "tools/call", params: { name: "get_weather", arguments: args } },
CallToolResultSchema,
);
bridge.sendToolResult(result);
```
--------------------------------
### Complete Hybrid Example with Theming and Styling
Source: https://apps.extensions.modelcontextprotocol.io/api/media/SKILL-3.md
This comprehensive example integrates the hybrid app pattern with MCP-specific features like theming, style variables, fonts, and safe area insets. It initializes the MCP app by registering handlers before connecting and applies host styles. For standalone apps, it initializes using URL parameters. The core rendering logic remains unified.
```typescript
import { App, PostMessageTransport, applyDocumentTheme, applyHostStyleVariables, applyHostFonts } from "@modelcontextprotocol/ext-apps";
const isMcpApp = window.location.origin === "null";
async function initMcpApp(): Promise> {
const app = new App({ name: "My App", version: "1.0.0" });
// Register ALL handlers BEFORE connect()
const params = await new Promise>((resolve) => {
app.ontoolinput = (input) => resolve(input.arguments ?? {});
});
app.onhostcontextchanged = (ctx) => {
if (ctx.theme) applyDocumentTheme(ctx.theme);
if (ctx.styles?.variables) applyHostStyleVariables(ctx.styles.variables);
if (ctx.styles?.css?.fonts) applyHostFonts(ctx.styles.css.fonts);
if (ctx.safeAreaInsets) {
const { top, right, bottom, left } = ctx.safeAreaInsets;
document.body.style.padding = `${top}px ${right}px ${bottom}px ${left}px`;
}
};
app.onteardown = async () => {
return {};
};
await app.connect(new PostMessageTransport());
return params;
}
async function initStandaloneApp(): Promise> {
return Object.fromEntries(new URL(location.href).searchParams);
}
async function main() {
const params = isMcpApp ? await initMcpApp() : await initStandaloneApp();
renderApp(params); // Same rendering logic — no fork needed
}
main().catch(console.error);
```
--------------------------------
### Example Usage
Source: https://apps.extensions.modelcontextprotocol.io/api/variables/app.OPEN_LINK_METHOD.html
This example demonstrates how to use the OPEN_LINK_METHOD constant to check message methods.
```typescript
import { SANDBOX_PROXY_READY_METHOD } from '@modelcontextprotocol/ext-apps';
if (event.data.method === SANDBOX_PROXY_READY_METHOD) {
// Handle sandbox proxy ready notification
}
```