Clarify spec and SDK: accept UIResourceMeta in both resources/list and resources/read (#410)
* Clarify spec and SDK: accept UIResourceMeta in both resources/list and resources/read The spec defines UIResource with _meta.ui, implicitly as an extension of the Resource type (resources/list), but examples and practice place _meta.ui on TextResourceContents (resources/read). The registerAppResource helper accepts _meta.ui in its config (which ends up in resources/list), but examples only return said _meta.ui from resources/read — and current hosts might be just ignoring it. This change: - Adds a 'Metadata Location' subsection to the draft spec explicitly stating that _meta.ui is valid on both the resource listing entry AND the content item, with content-item taking precedence. - Updates the spec example to show _meta on the resource declaration (resources/list). - Updates the CSP construction example to reference both locations. - Clarifies McpUiAppResourceConfig JSDoc: _meta here appears in resources/list and serves as a listing-level fallback for when content items omit _meta.ui. - Fixes the map-server example comment that incorrectly stated _meta must only be on the content item. * Add McpUiReadResourceResult type for UI metadata in resources/read * basic-host: support UIResourceMeta fallback from resources/list --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2dd56c3dce
commit
bcfffb6585
1 changed files with 73 additions and 16 deletions
|
|
@ -2,7 +2,7 @@ import { RESOURCE_MIME_TYPE, getToolUiResourceUri, type McpUiSandboxProxyReadyNo
|
|||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
||||
import type { CallToolResult, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import type { CallToolResult, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { getTheme, onThemeChange } from "./theme";
|
||||
import { HOST_STYLE_VARIABLES } from "./host-styles";
|
||||
|
||||
|
|
@ -22,6 +22,7 @@ export interface ServerInfo {
|
|||
name: string;
|
||||
client: Client;
|
||||
tools: Map<string, Tool>;
|
||||
resources: Map<string, Resource>;
|
||||
appHtmlCache: Map<string, string>;
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +37,12 @@ export async function connectToServer(serverUrl: URL): Promise<ServerInfo> {
|
|||
const tools = new Map(toolsList.tools.map((tool) => [tool.name, tool]));
|
||||
log.info("Server tools:", Array.from(tools.keys()));
|
||||
|
||||
return { name, client, tools, appHtmlCache: new Map() };
|
||||
// Fetch resources for listing-level _meta.ui (fallback for content-level)
|
||||
const resourcesList = await client.listResources();
|
||||
const resources = new Map(resourcesList.resources.map((r) => [r.uri, r]));
|
||||
log.info("Server resources:", Array.from(resources.keys()));
|
||||
|
||||
return { name, client, tools, resources, appHtmlCache: new Map() };
|
||||
}
|
||||
|
||||
async function connectWithFallback(serverUrl: URL): Promise<Client> {
|
||||
|
|
@ -128,14 +134,23 @@ async function getUiResource(serverInfo: ServerInfo, uri: string): Promise<UiRes
|
|||
|
||||
const html = "blob" in content ? atob(content.blob) : content.text;
|
||||
|
||||
// Extract CSP and permissions metadata from resource content._meta.ui (or content.meta for Python SDK)
|
||||
// Extract CSP and permissions metadata, preferring content-level (resources/read)
|
||||
// and falling back to listing-level (resources/list) per the spec
|
||||
log.info("Resource content keys:", Object.keys(content));
|
||||
log.info("Resource content._meta:", (content as any)._meta);
|
||||
|
||||
// Try both _meta (spec) and meta (Python SDK quirk)
|
||||
// Try both _meta (spec) and meta (Python SDK quirk) for content-level
|
||||
const contentMeta = (content as any)._meta || (content as any).meta;
|
||||
const csp = contentMeta?.ui?.csp;
|
||||
const permissions = contentMeta?.ui?.permissions;
|
||||
|
||||
// Get listing-level metadata as fallback
|
||||
const listingResource = serverInfo.resources.get(uri);
|
||||
const listingMeta = (listingResource as any)?._meta;
|
||||
log.info("Resource listing._meta:", listingMeta);
|
||||
|
||||
// Content-level takes precedence, fall back to listing-level
|
||||
const uiMeta = contentMeta?.ui ?? listingMeta?.ui;
|
||||
const csp = uiMeta?.csp;
|
||||
const permissions = uiMeta?.permissions;
|
||||
|
||||
return { html, csp, permissions };
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue