| /** | |
| * Utilities for MCP servers to register tools and resources that display interactive UIs. | |
| * | |
| * Use these helpers instead of the base SDK's `registerTool` and `registerResource` when | |
| * your tool should render an {@link app!App} in the client. They handle UI metadata normalization | |
| * and provide sensible defaults for the MCP Apps MIME type ({@link RESOURCE_MIME_TYPE}). | |
| * | |
| * @module server-helpers | |
| * | |
| * @example | |
| * ```typescript | |
| * import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server'; | |
| * | |
| * // Register a tool that displays a widget | |
| * registerAppTool(server, "weather", { | |
| * description: "Get weather forecast", | |
| * _meta: { ui: { resourceUri: "ui://weather/widget.html" } }, | |
| * }, handler); | |
| * | |
| * // Register the HTML resource the tool references | |
| * registerAppResource(server, "Weather Widget", "ui://weather/widget.html", {}, readCallback); | |
| * ``` | |
| */ | |
| import { | |
| RESOURCE_URI_META_KEY, | |
| RESOURCE_MIME_TYPE, | |
| McpUiResourceMeta, | |
| McpUiToolMeta, | |
| } from "../app.js"; | |
| import type { | |
| BaseToolCallback, | |
| McpServer, | |
| RegisteredTool, | |
| ResourceMetadata, | |
| ToolCallback, | |
| ReadResourceCallback, | |
| } from "@modelcontextprotocol/sdk/server/mcp.js"; | |
| import type { | |
| AnySchema, | |
| ZodRawShapeCompat, | |
| } from "@modelcontextprotocol/sdk/server/zod-compat.js"; | |
| import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js"; | |
| // Re-exports for convenience | |
| export { RESOURCE_URI_META_KEY, RESOURCE_MIME_TYPE }; | |
| export type { ResourceMetadata, ToolCallback, ReadResourceCallback }; | |
| /** | |
| * Base tool configuration matching the standard MCP server tool options. | |
| * Extended by {@link McpUiAppToolConfig} to add UI metadata requirements. | |
| */ | |
| export interface ToolConfig { | |
| title?: string; | |
| description?: string; | |
| inputSchema?: ZodRawShapeCompat | AnySchema; | |
| outputSchema?: ZodRawShapeCompat | AnySchema; | |
| annotations?: ToolAnnotations; | |
| _meta?: Record<string, unknown>; | |
| } | |
| /** | |
| * Configuration for tools that render an interactive UI. | |
| * | |
| * Extends {@link ToolConfig} with a required `_meta` field that specifies UI metadata. | |
| * The UI resource can be specified in two ways: | |
| * - `_meta.ui.resourceUri` (preferred) | |
| * - `_meta["ui/resourceUri"]` (deprecated, for backward compatibility) | |
| * | |
| * @see {@link registerAppTool} for the recommended way to register app tools | |
| */ | |
| export interface McpUiAppToolConfig extends ToolConfig { | |
| _meta: { | |
| [key: string]: unknown; | |
| } & ( | |
| | { | |
| ui: McpUiToolMeta; | |
| } | |
| | { | |
| /** | |
| * URI of the UI resource to display for this tool. | |
| * This is converted to `_meta["ui/resourceUri"]`. | |
| * | |
| * @example "ui://weather/widget.html" | |
| * | |
| * @deprecated Use `_meta.ui.resourceUri` instead. | |
| */ | |
| [RESOURCE_URI_META_KEY]?: string; | |
| } | |
| ); | |
| } | |
| /** | |
| * MCP App Resource configuration for {@link registerAppResource}. | |
| * | |
| * Extends the base MCP SDK `ResourceMetadata` with optional UI metadata | |
| * for configuring security policies and rendering preferences. | |
| * | |
| * @see {@link registerAppResource} for usage | |
| */ | |
| export interface McpUiAppResourceConfig extends ResourceMetadata { | |
| /** | |
| * Optional UI metadata for the resource. | |
| * Used to configure security policies (CSP) and rendering preferences. | |
| */ | |
| _meta?: { | |
| /** | |
| * UI-specific metadata including CSP configuration and rendering preferences. | |
| */ | |
| ui?: McpUiResourceMeta; | |
| // Allow additional metadata properties for extensibility. | |
| [key: string]: unknown; | |
| }; | |
| } | |
| /** | |
| * Register an app tool with the MCP server. | |
| * | |
| * This is a convenience wrapper around `server.registerTool` that normalizes | |
| * UI metadata: if `_meta.ui.resourceUri` is set, the legacy `_meta["ui/resourceUri"]` | |
| * key is also populated (and vice versa) for compatibility with older hosts. | |
| * | |
| * @param server - The MCP server instance | |
| * @param name - Tool name/identifier | |
| * @param config - Tool configuration with `_meta` field containing UI metadata | |
| * @param cb - Tool handler function | |
| * | |
| * @example Basic usage | |
| * ```typescript | |
| * import { registerAppTool } from '@modelcontextprotocol/ext-apps/server'; | |
| * import { z } from 'zod'; | |
| * | |
| * registerAppTool(server, "get-weather", { | |
| * title: "Get Weather", | |
| * description: "Get current weather for a location", | |
| * inputSchema: { location: z.string() }, | |
| * _meta: { | |
| * ui: { resourceUri: "ui://weather/widget.html" }, | |
| * }, | |
| * }, async (args) => { | |
| * const weather = await fetchWeather(args.location); | |
| * return { content: [{ type: "text", text: JSON.stringify(weather) }] }; | |
| * }); | |
| * ``` | |
| * | |
| * @example Tool visibility - create app-only tools for UI actions | |
| * ```typescript | |
| * import { registerAppTool } from '@modelcontextprotocol/ext-apps/server'; | |
| * import { z } from 'zod'; | |
| * | |
| * // Main tool - visible to both model and app (default) | |
| * registerAppTool(server, "show-cart", { | |
| * description: "Display the user's shopping cart", | |
| * _meta: { | |
| * ui: { | |
| * resourceUri: "ui://shop/cart.html", | |
| * visibility: ["model", "app"], | |
| * }, | |
| * }, | |
| * }, async () => { | |
| * const cart = await getCart(); | |
| * return { content: [{ type: "text", text: JSON.stringify(cart) }] }; | |
| * }); | |
| * | |
| * // App-only tool - hidden from the model, only callable by the UI | |
| * 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) }] }; | |
| * }); | |
| * ``` | |
| * | |
| * @see {@link registerAppResource} to register the HTML resource referenced by the tool | |
| */ | |
| export function registerAppTool< | |
| OutputArgs extends ZodRawShapeCompat | AnySchema, | |
| InputArgs extends undefined | ZodRawShapeCompat | AnySchema = undefined, | |
| >( | |
| server: Pick<McpServer, "registerTool">, | |
| name: string, | |
| config: McpUiAppToolConfig & { | |
| inputSchema?: InputArgs; | |
| outputSchema?: OutputArgs; | |
| }, | |
| cb: ToolCallback<InputArgs>, | |
| ): RegisteredTool { | |
| // Normalize metadata for backward compatibility: | |
| // - If _meta.ui.resourceUri is set, also set the legacy flat key | |
| // - If the legacy flat key is set, also set _meta.ui.resourceUri | |
| const meta = config._meta; | |
| const uiMeta = meta.ui as McpUiToolMeta | undefined; | |
| const legacyUri = meta[RESOURCE_URI_META_KEY] as string | undefined; | |
| let normalizedMeta = meta; | |
| if (uiMeta?.resourceUri && !legacyUri) { | |
| // New format -> also set legacy key | |
| normalizedMeta = { ...meta, [RESOURCE_URI_META_KEY]: uiMeta.resourceUri }; | |
| } else if (legacyUri && !uiMeta?.resourceUri) { | |
| // Legacy format -> also set new format | |
| normalizedMeta = { ...meta, ui: { ...uiMeta, resourceUri: legacyUri } }; | |
| } | |
| return server.registerTool(name, { ...config, _meta: normalizedMeta }, cb); | |
| } | |
| /** | |
| * Register an app resource with the MCP server. | |
| * | |
| * This is a convenience wrapper around `server.registerResource` that: | |
| * - Defaults the MIME type to {@link RESOURCE_MIME_TYPE} (`"text/html;profile=mcp-app"`) | |
| * - Provides a cleaner API matching the SDK's callback signature | |
| * | |
| * @param server - The MCP server instance | |
| * @param name - Human-readable resource name | |
| * @param uri - Resource URI (should match the `_meta.ui` field in tool config) | |
| * @param config - Resource configuration | |
| * @param readCallback - Callback that returns the resource contents | |
| * | |
| * @example Basic usage | |
| * ```typescript | |
| * import { registerAppResource } from '@modelcontextprotocol/ext-apps/server'; | |
| * | |
| * registerAppResource(server, "Weather Widget", "ui://weather/widget.html", { | |
| * description: "Interactive weather display", | |
| * }, async () => ({ | |
| * contents: [{ | |
| * uri: "ui://weather/widget.html", | |
| * mimeType: RESOURCE_MIME_TYPE, | |
| * text: await fs.readFile("dist/widget.html", "utf-8"), | |
| * }], | |
| * })); | |
| * ``` | |
| * | |
| * @example With CSP configuration for external domains | |
| * ```typescript | |
| * registerAppResource(server, "Music Player", "ui://music/player.html", { | |
| * description: "Audio player with external soundfonts", | |
| * _meta: { | |
| * ui: { | |
| * csp: { | |
| * connectDomains: ["https://api.example.com"], // For fetch/WebSocket | |
| * resourceDomains: ["https://cdn.example.com"], // For scripts/styles/images | |
| * }, | |
| * }, | |
| * }, | |
| * }, readCallback); | |
| * ``` | |
| * | |
| * @see {@link registerAppTool} to register tools that reference this resource | |
| */ | |
| export function registerAppResource( | |
| server: Pick<McpServer, "registerResource">, | |
| name: string, | |
| uri: string, | |
| config: McpUiAppResourceConfig, | |
| readCallback: ReadResourceCallback, | |
| ): void { | |
| server.registerResource( | |
| name, | |
| uri, | |
| { | |
| // Default MIME type for MCP App UI resources (can still be overridden by config below) | |
| mimeType: RESOURCE_MIME_TYPE, | |
| ...config, | |
| }, | |
| readCallback, | |
| ); | |
| } | |