Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions examples/map-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
RESOURCE_URI_META_KEY,
} from "@modelcontextprotocol/ext-apps/server";
import { randomUUID } from "crypto";

// 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://cesium-map/mcp-app.html";
const resourceUri = "ui://cesium-map/mcp-app.html";

// Nominatim API response type
interface NominatimResult {
Expand Down Expand Up @@ -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<ReadResourceResult> => {
const html = await fs.readFile(
Expand All @@ -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,
Expand Down Expand Up @@ -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<CallToolResult> => ({
content: [
Expand Down
11 changes: 5 additions & 6 deletions examples/transcript-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<CallToolResult> => {
return {
Expand All @@ -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<ReadResourceResult> => {
const html = await fs.readFile(
Expand All @@ -67,7 +66,7 @@ export function createServer(): McpServer {
return {
contents: [
{
uri: RESOURCE_URI,
uri: resourceUri,
mimeType: RESOURCE_MIME_TYPE,
text: html,
_meta: {
Expand Down
49 changes: 41 additions & 8 deletions src/app.examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
{
Expand All @@ -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
}
Expand Down
37 changes: 29 additions & 8 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
* {
Expand All @@ -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
* }
Expand Down
Loading