Replace deprecated RESOURCE_URI_META_KEY usage with modern format (#457)

This commit is contained in:
Hryhorii (Greg) Avcharov 2026-03-06 15:32:04 +01:00 • committed by GitHub
commit 19fe73a08a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 80 additions and 28 deletions

View file

@ -17,7 +17,6 @@ import {
registerAppTool, registerAppTool,
registerAppResource, registerAppResource,
RESOURCE_MIME_TYPE, RESOURCE_MIME_TYPE,
RESOURCE_URI_META_KEY,
} from "@modelcontextprotocol/ext-apps/server"; } from "@modelcontextprotocol/ext-apps/server";
import { randomUUID } from "crypto"; import { randomUUID } from "crypto";
@ -25,7 +24,7 @@ import { randomUUID } from "crypto";
const DIST_DIR = import.meta.filename.endsWith(".ts") const DIST_DIR = import.meta.filename.endsWith(".ts")
? path.join(import.meta.dirname, "dist") ? path.join(import.meta.dirname, "dist")
: import.meta.dirname; : import.meta.dirname;
const RESOURCE_URI = "ui://cesium-map/mcp-app.html"; const resourceUri = "ui://cesium-map/mcp-app.html";
// Nominatim API response type // Nominatim API response type
interface NominatimResult { interface NominatimResult {
@ -118,8 +117,8 @@ export function createServer(): McpServer {
// Register the CesiumJS map resource with CSP for external tile sources // Register the CesiumJS map resource with CSP for external tile sources
registerAppResource( registerAppResource(
server, server,
RESOURCE_URI, resourceUri,
RESOURCE_URI, resourceUri,
{ mimeType: RESOURCE_MIME_TYPE }, { mimeType: RESOURCE_MIME_TYPE },
async (): Promise<ReadResourceResult> => { async (): Promise<ReadResourceResult> => {
const html = await fs.readFile( const html = await fs.readFile(
@ -130,7 +129,7 @@ export function createServer(): McpServer {
contents: [ contents: [
// CSP metadata on the content item takes precedence over listing-level _meta // CSP metadata on the content item takes precedence over listing-level _meta
{ {
uri: RESOURCE_URI, uri: resourceUri,
mimeType: RESOURCE_MIME_TYPE, mimeType: RESOURCE_MIME_TYPE,
text: html, text: html,
_meta: cspMeta, _meta: cspMeta,
@ -175,7 +174,7 @@ export function createServer(): McpServer {
.optional() .optional()
.describe("Optional label to display on the map"), .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<CallToolResult> => ({ async ({ west, south, east, north, label }): Promise<CallToolResult> => ({
content: [ content: [

View file

@ -9,13 +9,12 @@ import {
registerAppTool, registerAppTool,
registerAppResource, registerAppResource,
RESOURCE_MIME_TYPE, RESOURCE_MIME_TYPE,
RESOURCE_URI_META_KEY,
} from "@modelcontextprotocol/ext-apps/server"; } from "@modelcontextprotocol/ext-apps/server";
// Works both from source (server.ts) and compiled (dist/server.js) // Works both from source (server.ts) and compiled (dist/server.js)
const DIST_DIR = import.meta.filename.endsWith(".ts") const DIST_DIR = import.meta.filename.endsWith(".ts")
? path.join(import.meta.dirname, "dist") ? path.join(import.meta.dirname, "dist")
: import.meta.dirname; : 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. * Creates a new MCP server instance with tools and resources registered.
@ -35,7 +34,7 @@ export function createServer(): McpServer {
description: description:
"Opens a live speech transcription interface using the Web Speech API.", "Opens a live speech transcription interface using the Web Speech API.",
inputSchema: {}, inputSchema: {},
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI }, _meta: { ui: { resourceUri } },
}, },
async (): Promise<CallToolResult> => { async (): Promise<CallToolResult> => {
return { return {
@ -55,8 +54,8 @@ export function createServer(): McpServer {
// Register the UI resource // Register the UI resource
registerAppResource( registerAppResource(
server, server,
RESOURCE_URI, resourceUri,
RESOURCE_URI, resourceUri,
{ mimeType: RESOURCE_MIME_TYPE, description: "Transcript UI" }, { mimeType: RESOURCE_MIME_TYPE, description: "Transcript UI" },
async (): Promise<ReadResourceResult> => { async (): Promise<ReadResourceResult> => {
const html = await fs.readFile( const html = await fs.readFile(
@ -67,7 +66,7 @@ export function createServer(): McpServer {
return { return {
contents: [ contents: [
{ {
uri: RESOURCE_URI, uri: resourceUri,
mimeType: RESOURCE_MIME_TYPE, mimeType: RESOURCE_MIME_TYPE,
text: html, text: html,
_meta: { _meta: {

View file

@ -12,16 +12,46 @@ import type {
McpServer, McpServer,
ToolCallback, ToolCallback,
} from "@modelcontextprotocol/sdk/server/mcp.js"; } 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, server: McpServer,
handler: ToolCallback, 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( server.registerTool(
"weather", "weather",
{ {
@ -32,16 +62,19 @@ function RESOURCE_URI_META_KEY_serverSide(
}, },
handler, 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) { function RESOURCE_URI_META_KEY_hostSide(tool: Tool) {
//#region RESOURCE_URI_META_KEY_hostSide //#region RESOURCE_URI_META_KEY_hostSide
// Check tool definition metadata (from tools/list response): // Hosts should check both modern and legacy formats
const uiUri = tool._meta?.[RESOURCE_URI_META_KEY]; 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://")) { if (typeof uiUri === "string" && uiUri.startsWith("ui://")) {
// Fetch the resource and display the UI // Fetch the resource and display the UI
} }

View file

@ -75,12 +75,30 @@ export {
* When hosts see a tool with this metadata, they fetch and render the * When hosts see a tool with this metadata, they fetch and render the
* corresponding {@link App `App`}. * corresponding {@link App `App`}.
* *
* **Note**: This constant is provided for reference. App developers typically * **Note**: This constant is provided for reference and backwards compatibility.
* don't need to use it directly. Prefer using {@link server-helpers!registerAppTool `registerAppTool`} * Server developers should use {@link server-helpers!registerAppTool `registerAppTool`}
* with the `_meta.ui.resourceUri` format instead. * 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) * @example Modern format (server-side, not in Apps)
* ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_serverSide" * ```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( * server.registerTool(
* "weather", * "weather",
* { * {
@ -93,10 +111,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" * ```ts source="./app.examples.ts#RESOURCE_URI_META_KEY_hostSide"
* // Check tool definition metadata (from tools/list response): * // Hosts should check both modern and legacy formats
* const uiUri = tool._meta?.[RESOURCE_URI_META_KEY]; * 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://")) { * if (typeof uiUri === "string" && uiUri.startsWith("ui://")) {
* // Fetch the resource and display the UI * // Fetch the resource and display the UI
* } * }