Skip to content

Commit f46663a

Browse files
antonpk1claude
andcommitted
feat: tool visibility and nested _meta.ui format
- Rename visibility "apps" → "app" in McpUiToolVisibility type - Add _meta.ui.resourceUri nested format (deprecate flat format) - Add getToolUiResourceUri() utility with backward compatibility - Add visibility demo to system-monitor-server: - get-system-stats: visibility ["model"] with resourceUri - refresh-stats: visibility ["app"] (app-only polling) - Update all example servers to use new _meta.ui format - Add 11 unit tests for getToolUiResourceUri() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 9084aa6 commit f46663a

File tree

22 files changed

+253
-85
lines changed

22 files changed

+253
-85
lines changed

docs/quickstart.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ Create `server.ts`:
9797
```typescript
9898
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9999
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
100-
import { RESOURCE_URI_META_KEY } from "@modelcontextprotocol/ext-apps";
100+
import { RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps";
101101
import cors from "cors";
102102
import express from "express";
103103
import fs from "node:fs/promises";
@@ -119,7 +119,7 @@ server.registerTool(
119119
description: "Returns the current server time.",
120120
inputSchema: {},
121121
outputSchema: { time: z.string() },
122-
_meta: { [RESOURCE_URI_META_KEY]: resourceUri }, // Links tool to UI
122+
_meta: { ui: { resourceUri } }, // Links tool to UI
123123
},
124124
async () => {
125125
const time = new Date().toISOString();

examples/basic-host/src/implementation.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport } from "@modelcontextprotocol/ext-apps/app-bridge";
1+
import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNotification, AppBridge, PostMessageTransport } from "@modelcontextprotocol/ext-apps/app-bridge";
22
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
33
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
44
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
@@ -77,7 +77,7 @@ export function callTool(
7777

7878
const toolCallInfo: ToolCallInfo = { serverInfo, tool, input, resultPromise };
7979

80-
const uiResourceUri = getUiResourceUri(tool);
80+
const uiResourceUri = getToolUiResourceUri(tool);
8181
if (uiResourceUri) {
8282
toolCallInfo.appResourcePromise = getUiResource(serverInfo, uiResourceUri);
8383
}
@@ -86,16 +86,6 @@ export function callTool(
8686
}
8787

8888

89-
function getUiResourceUri(tool: Tool): string | undefined {
90-
const uri = tool._meta?.[RESOURCE_URI_META_KEY];
91-
if (typeof uri === "string" && uri.startsWith("ui://")) {
92-
return uri;
93-
} else if (uri !== undefined) {
94-
throw new Error(`Invalid UI resource URI: ${JSON.stringify(uri)}`);
95-
}
96-
}
97-
98-
9989
async function getUiResource(serverInfo: ServerInfo, uri: string): Promise<UiResourceData> {
10090
log.info("Reading UI resource:", uri);
10191
const resource = await serverInfo.client.readResource({ uri });

examples/basic-server-react/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
33
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
44
import fs from "node:fs/promises";
55
import path from "node:path";
6-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
6+
import { RESOURCE_MIME_TYPE } from "../../dist/src/app";
77
import { startServer } from "../shared/server-utils.js";
88

99
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -28,7 +28,7 @@ function createServer(): McpServer {
2828
title: "Get Time",
2929
description: "Returns the current server time as an ISO 8601 string.",
3030
inputSchema: {},
31-
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
31+
_meta: { ui: { resourceUri: RESOURCE_URI } },
3232
},
3333
async (): Promise<CallToolResult> => {
3434
const time = new Date().toISOString();

examples/basic-server-vanillajs/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
33
import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
44
import fs from "node:fs/promises";
55
import path from "node:path";
6-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
6+
import { RESOURCE_MIME_TYPE } from "../../dist/src/app";
77
import { startServer } from "../shared/server-utils.js";
88

99
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -28,7 +28,7 @@ function createServer(): McpServer {
2828
title: "Get Time",
2929
description: "Returns the current server time as an ISO 8601 string.",
3030
inputSchema: {},
31-
_meta: { [RESOURCE_URI_META_KEY]: RESOURCE_URI },
31+
_meta: { ui: { resourceUri: RESOURCE_URI } },
3232
},
3333
async (): Promise<CallToolResult> => {
3434
const time = new Date().toISOString();

examples/budget-allocator-server/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Exposes a single `get-budget-data` tool that returns:
4747
- Historical data (~120 data points) - 24 months of allocation history per category
4848
- Industry benchmarks (~60 data points) - Aggregated percentile data by company stage
4949

50-
The tool is linked to a UI resource via `_meta[RESOURCE_URI_META_KEY]`.
50+
The tool is linked to a UI resource via `_meta.ui.resourceUri`.
5151

5252
### App (`src/mcp-app.ts`)
5353

examples/budget-allocator-server/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
import fs from "node:fs/promises";
1414
import path from "node:path";
1515
import { z } from "zod";
16-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
16+
import { RESOURCE_MIME_TYPE } from "../../dist/src/app";
1717
import { startServer } from "../shared/server-utils.js";
1818

1919
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -242,7 +242,7 @@ function createServer(): McpServer {
242242
description:
243243
"Returns budget configuration with 24 months of historical allocations and industry benchmarks by company stage",
244244
inputSchema: {},
245-
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
245+
_meta: { ui: { resourceUri } },
246246
},
247247
async (): Promise<CallToolResult> => {
248248
const response: BudgetDataResponse = {

examples/cohort-heatmap-server/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { ReadResourceResult } from "@modelcontextprotocol/sdk/types.js";
44
import fs from "node:fs/promises";
55
import path from "node:path";
66
import { z } from "zod";
7-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
7+
import { RESOURCE_MIME_TYPE } from "../../dist/src/app";
88
import { startServer } from "../shared/server-utils.js";
99

1010
const DIST_DIR = path.join(import.meta.dirname, "dist");
@@ -163,7 +163,7 @@ function createServer(): McpServer {
163163
description:
164164
"Returns cohort retention heatmap data showing customer retention over time by signup month",
165165
inputSchema: GetCohortDataInputSchema.shape,
166-
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
166+
_meta: { ui: { resourceUri } },
167167
},
168168
async ({ metric, periodType, cohortCount, maxPeriods }) => {
169169
const data = generateCohortData(

examples/customer-segmentation-server/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Exposes a single `get-customer-data` tool that returns:
4848
- Segment summary with counts and colors for each group
4949
- Optional segment filter parameter
5050

51-
The tool is linked to a UI resource via `_meta[RESOURCE_URI_META_KEY]`.
51+
The tool is linked to a UI resource via `_meta.ui.resourceUri`.
5252

5353
### App (`src/mcp-app.ts`)
5454

examples/customer-segmentation-server/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77
import fs from "node:fs/promises";
88
import path from "node:path";
99
import { z } from "zod";
10-
import { RESOURCE_MIME_TYPE, RESOURCE_URI_META_KEY } from "../../dist/src/app";
10+
import { RESOURCE_MIME_TYPE } from "../../dist/src/app";
1111
import { startServer } from "../shared/server-utils.js";
1212
import {
1313
generateCustomers,
@@ -72,7 +72,7 @@ function createServer(): McpServer {
7272
description:
7373
"Returns customer data with segment information for visualization. Optionally filter by segment.",
7474
inputSchema: GetCustomerDataInputSchema.shape,
75-
_meta: { [RESOURCE_URI_META_KEY]: resourceUri },
75+
_meta: { ui: { resourceUri } },
7676
},
7777
async ({ segment }): Promise<CallToolResult> => {
7878
const data = getCustomerData(segment);

examples/scenario-modeler-server/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Exposes a single `get-scenario-data` tool that returns:
4747
- Default input values for the sliders
4848
- Optionally computes custom projections when `customInputs` are provided
4949

50-
The tool is linked to a UI resource via `_meta[RESOURCE_URI_META_KEY]`.
50+
The tool is linked to a UI resource via `_meta.ui.resourceUri`.
5151

5252
### App (`src/`)
5353

0 commit comments

Comments
 (0)