ext-apps/src/server/index.examples.ts
Jonathan Hefner c39b40e7c8
Replace "external" with "network" in CSP documentation
The term "external" is misleading for MCP Apps because developers
typically interpret it as "third-party." However, MCP App HTML runs in
a sandboxed iframe with no same-origin server, so *all* origins must be
declared—including where your own bundled JS/CSS is served from. Using
"network" instead avoids this confusion and aligns with the detailed
explanations in `patterns.md` and `spec.types.ts`.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 10:50:06 -06:00

291 lines
7.5 KiB
TypeScript

/**
* Type-checked examples for {@link registerAppTool `registerAppTool`} and {@link registerAppResource `registerAppResource`}.
*
* These examples are included in the API documentation via `@includeCode` tags.
* Each function's region markers define the code snippet that appears in the docs.
*
* @module
*/
import * as crypto from "node:crypto";
import * as fs from "node:fs/promises";
import type {
McpServer,
ToolCallback,
ReadResourceCallback,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import {
registerAppTool,
registerAppResource,
getUiCapability,
RESOURCE_MIME_TYPE,
} from "./index.js";
// Stubs for external functions used in examples
declare function fetchWeather(
location: string,
): Promise<{ temp: number; conditions: string }>;
declare function getCart(): Promise<{ items: unknown[]; total: number }>;
declare function updateCartItem(
itemId: string,
quantity: number,
): Promise<{ items: unknown[]; total: number }>;
/**
* Example: Module overview showing basic registration of tools and resources.
*/
function index_overview(
server: McpServer,
toolCallback: ToolCallback,
readCallback: ReadResourceCallback,
) {
//#region index_overview
// 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,
);
//#endregion index_overview
}
/**
* Example: Basic usage of registerAppTool with input schema and handler.
*/
function registerAppTool_basicUsage(server: McpServer) {
//#region registerAppTool_basicUsage
registerAppTool(
server,
"get-weather",
{
title: "Get Weather",
description: "Get current weather for a location",
inputSchema: { location: z.string() },
_meta: {
ui: { resourceUri: "ui://weather/view.html" },
},
},
async (args) => {
const weather = await fetchWeather(args.location);
return { content: [{ type: "text", text: JSON.stringify(weather) }] };
},
);
//#endregion registerAppTool_basicUsage
}
/**
* Example: Model-only visibility - tools visible to model but not callable by UI.
*/
function registerAppTool_modelOnlyVisibility(server: McpServer) {
//#region registerAppTool_modelOnlyVisibility
registerAppTool(
server,
"show-cart",
{
description: "Display the user's shopping cart",
_meta: {
ui: {
resourceUri: "ui://shop/cart.html",
visibility: ["model"],
},
},
},
async () => {
const cart = await getCart();
return { content: [{ type: "text", text: JSON.stringify(cart) }] };
},
);
//#endregion registerAppTool_modelOnlyVisibility
}
/**
* Example: App-only visibility - tools hidden from model, only callable by UI.
*/
function registerAppTool_appOnlyVisibility(server: McpServer) {
//#region registerAppTool_appOnlyVisibility
registerAppTool(
server,
"update-quantity",
{
description: "Update item quantity in cart",
inputSchema: { itemId: z.string(), quantity: z.number() },
_meta: {
ui: {
resourceUri: "ui://shop/cart.html",
visibility: ["app"],
},
},
},
async ({ itemId, quantity }) => {
const cart = await updateCartItem(itemId, quantity);
return { content: [{ type: "text", text: JSON.stringify(cart) }] };
},
);
//#endregion registerAppTool_appOnlyVisibility
}
/**
* Example: Basic usage of registerAppResource.
*/
function registerAppResource_basicUsage(server: McpServer) {
//#region registerAppResource_basicUsage
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"),
},
],
}),
);
//#endregion registerAppResource_basicUsage
}
/**
* Example: registerAppResource with CSP configuration for network access.
*/
function registerAppResource_withCsp(
server: McpServer,
musicPlayerHtml: string,
) {
//#region registerAppResource_withCsp
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
},
},
},
},
],
}),
);
//#endregion registerAppResource_withCsp
}
/**
* Example: registerAppResource with stable origin for external API CORS allowlists.
*/
function registerAppResource_withDomain(
server: McpServer,
dashboardHtml: string,
) {
//#region registerAppResource_withDomain
// Computes a stable origin from an MCP server URL for hosting in Claude.
function computeAppDomainForClaude(mcpServerUrl: string): string {
const hash = crypto
.createHash("sha256")
.update(mcpServerUrl)
.digest("hex")
.slice(0, 32);
return `${hash}.claudemcpcontent.com`;
}
const APP_DOMAIN = computeAppDomainForClaude("https://example.com/mcp");
registerAppResource(
server,
"Company Dashboard",
"ui://dashboard/view.html",
{
description: "Internal dashboard with company data",
},
async () => ({
contents: [
{
uri: "ui://dashboard/view.html",
mimeType: RESOURCE_MIME_TYPE,
text: dashboardHtml,
_meta: {
ui: {
// CSP: tell browser the app is allowed to make requests
csp: {
connectDomains: ["https://api.example.com"],
},
// CORS: give app a stable origin for the API server to allowlist
//
// (Public APIs that use `Access-Control-Allow-Origin: *` or API
// key auth don't need this.)
domain: APP_DOMAIN,
},
},
},
],
}),
);
//#endregion registerAppResource_withDomain
}
/**
* Example: Check for MCP Apps support in server initialization.
*/
function getUiCapability_checkSupport(
server: McpServer,
weatherHandler: ToolCallback,
textWeatherHandler: ToolCallback,
) {
//#region getUiCapability_checkSupport
server.server.oninitialized = () => {
const clientCapabilities = server.server.getClientCapabilities();
const uiCap = getUiCapability(clientCapabilities);
if (uiCap?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) {
// App-enhanced tool
registerAppTool(
server,
"weather",
{
description: "Get weather information with interactive dashboard",
_meta: { ui: { resourceUri: "ui://weather/dashboard" } },
},
weatherHandler,
);
} else {
// Text-only fallback
server.registerTool(
"weather",
{
description: "Get weather information",
},
textWeatherHandler,
);
}
};
//#endregion getUiCapability_checkSupport
}