diff --git a/examples/map-server/server.ts b/examples/map-server/server.ts index 3dfa74906..540d35c4d 100644 --- a/examples/map-server/server.ts +++ b/examples/map-server/server.ts @@ -17,7 +17,6 @@ import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, - RESOURCE_URI_META_KEY, } from "@modelcontextprotocol/ext-apps/server"; import { randomUUID } from "crypto"; @@ -25,7 +24,7 @@ import { randomUUID } from "crypto"; const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") : import.meta.dirname; -const RESOURCE_URI = "ui://cesium-map/mcp-app.html"; +const resourceUri = "ui://cesium-map/mcp-app.html"; // Nominatim API response type interface NominatimResult { @@ -118,8 +117,8 @@ export function createServer(): McpServer { // Register the CesiumJS map resource with CSP for external tile sources registerAppResource( server, - RESOURCE_URI, - RESOURCE_URI, + resourceUri, + resourceUri, { mimeType: RESOURCE_MIME_TYPE }, async (): Promise => { const html = await fs.readFile( @@ -130,7 +129,7 @@ export function createServer(): McpServer { contents: [ // CSP metadata on the content item takes precedence over listing-level _meta { - uri: RESOURCE_URI, + uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: cspMeta, @@ -175,7 +174,7 @@ export function createServer(): McpServer { .optional() .describe("Optional label to display on the map"), }, - _meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI }, + _meta: { ui: { resourceUri } }, }, async ({ west, south, east, north, label }): Promise => ({ content: [ diff --git a/examples/transcript-server/server.ts b/examples/transcript-server/server.ts index 035c8b647..56bf88d99 100644 --- a/examples/transcript-server/server.ts +++ b/examples/transcript-server/server.ts @@ -9,13 +9,12 @@ import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, - RESOURCE_URI_META_KEY, } from "@modelcontextprotocol/ext-apps/server"; // Works both from source (server.ts) and compiled (dist/server.js) const DIST_DIR = import.meta.filename.endsWith(".ts") ? path.join(import.meta.dirname, "dist") : import.meta.dirname; -const RESOURCE_URI = "ui://transcript/mcp-app.html"; +const resourceUri = "ui://transcript/mcp-app.html"; /** * Creates a new MCP server instance with tools and resources registered. @@ -35,7 +34,7 @@ export function createServer(): McpServer { description: "Opens a live speech transcription interface using the Web Speech API.", inputSchema: {}, - _meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI }, + _meta: { ui: { resourceUri } }, }, async (): Promise => { return { @@ -55,8 +54,8 @@ export function createServer(): McpServer { // Register the UI resource registerAppResource( server, - RESOURCE_URI, - RESOURCE_URI, + resourceUri, + resourceUri, { mimeType: RESOURCE_MIME_TYPE, description: "Transcript UI" }, async (): Promise => { const html = await fs.readFile( @@ -67,7 +66,7 @@ export function createServer(): McpServer { return { contents: [ { - uri: RESOURCE_URI, + uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html, _meta: { diff --git a/src/app.examples.ts b/src/app.examples.ts index 4705071b5..9b5e9ff9d 100644 --- a/src/app.examples.ts +++ b/src/app.examples.ts @@ -12,16 +12,46 @@ import type { McpServer, ToolCallback, } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { App, PostMessageTransport, RESOURCE_URI_META_KEY } from "./app.js"; +import { + App, + PostMessageTransport, + RESOURCE_URI_META_KEY, + McpUiToolMeta, +} from "./app.js"; +import { registerAppTool } from "./server/index.js"; /** - * Example: How MCP servers use RESOURCE_URI_META_KEY (server-side, not in Apps). + * Example: Modern format for registering tools with UI (recommended). */ -function RESOURCE_URI_META_KEY_serverSide( +function RESOURCE_URI_META_KEY_modernFormat( server: McpServer, handler: ToolCallback, ) { - //#region RESOURCE_URI_META_KEY_serverSide + //#region RESOURCE_URI_META_KEY_modernFormat + // Preferred: Use registerAppTool with nested ui.resourceUri + registerAppTool( + server, + "weather", + { + description: "Get weather forecast", + _meta: { + ui: { resourceUri: "ui://weather/forecast" }, + }, + }, + handler, + ); + //#endregion RESOURCE_URI_META_KEY_modernFormat +} + +/** + * Example: Legacy format using RESOURCE_URI_META_KEY (deprecated). + */ +function RESOURCE_URI_META_KEY_legacyFormat( + server: McpServer, + handler: ToolCallback, +) { + //#region RESOURCE_URI_META_KEY_legacyFormat + // Deprecated: Direct use of RESOURCE_URI_META_KEY server.registerTool( "weather", { @@ -32,16 +62,19 @@ function RESOURCE_URI_META_KEY_serverSide( }, handler, ); - //#endregion RESOURCE_URI_META_KEY_serverSide + //#endregion RESOURCE_URI_META_KEY_legacyFormat } /** - * Example: How hosts check for RESOURCE_URI_META_KEY metadata (host-side). + * Example: How hosts check for RESOURCE_URI_META_KEY metadata (must support both formats). */ function RESOURCE_URI_META_KEY_hostSide(tool: Tool) { //#region RESOURCE_URI_META_KEY_hostSide - // Check tool definition metadata (from tools/list response): - const uiUri = tool._meta?.[RESOURCE_URI_META_KEY]; + // Hosts should check both modern and legacy formats + const meta = tool._meta; + const uiMeta = meta?.ui as McpUiToolMeta | undefined; + const legacyUri = meta?.[RESOURCE_URI_META_KEY] as string | undefined; + const uiUri = uiMeta?.resourceUri ?? legacyUri; if (typeof uiUri === "string" && uiUri.startsWith("ui://")) { // Fetch the resource and display the UI } diff --git a/src/app.ts b/src/app.ts index d8d49edc7..a59e7890a 100644 --- a/src/app.ts +++ b/src/app.ts @@ -67,12 +67,30 @@ export { * When hosts see a tool with this metadata, they fetch and render the * corresponding {@link App `App`}. * - * **Note**: This constant is provided for reference. App developers typically - * don't need to use it directly. Prefer using {@link server-helpers!registerAppTool `registerAppTool`} - * with the `_meta.ui.resourceUri` format instead. + * **Note**: This constant is provided for reference and backwards compatibility. + * Server developers should use {@link server-helpers!registerAppTool `registerAppTool`} + * with the `_meta.ui.resourceUri` format instead. Host developers must check both + * formats for compatibility. * - * @example How MCP servers use this key (server-side, not in Apps) - * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_serverSide" + * @example Modern format (server-side, not in Apps) + * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_modernFormat" + * // Preferred: Use registerAppTool with nested ui.resourceUri + * registerAppTool( + * server, + * "weather", + * { + * description: "Get weather forecast", + * _meta: { + * ui: { resourceUri: "ui://weather/forecast" }, + * }, + * }, + * handler, + * ); + * ``` + * + * @example Legacy format (deprecated, for backwards compatibility) + * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_legacyFormat" + * // Deprecated: Direct use of RESOURCE_URI_META_KEY * server.registerTool( * "weather", * { @@ -85,10 +103,13 @@ export { * ); * ``` * - * @example How hosts check for this metadata (host-side) + * @example How hosts check for this metadata (must support both formats) * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_hostSide" - * // Check tool definition metadata (from tools/list response): - * const uiUri = tool._meta?.[RESOURCE_URI_META_KEY]; + * // Hosts should check both modern and legacy formats + * const meta = tool._meta; + * const uiMeta = meta?.ui as McpUiToolMeta | undefined; + * const legacyUri = meta?.[RESOURCE_URI_META_KEY] as string | undefined; + * const uiUri = uiMeta?.resourceUri ?? legacyUri; * if (typeof uiUri === "string" && uiUri.startsWith("ui://")) { * // Fetch the resource and display the UI * }