diff --git a/docs/src/guide/server/typescript/overview.md b/docs/src/guide/server/typescript/overview.md index b07092ac..76ed9f89 100644 --- a/docs/src/guide/server/typescript/overview.md +++ b/docs/src/guide/server/typescript/overview.md @@ -30,7 +30,10 @@ MCP-UI specific configuration options. These keys are automatically prefixed wit - **`initial-render-data`**: Provide data that should be passed to the iframe when rendering ### `resourceProps` -Additional properties that are spread directly onto the resource definition, allowing you to add any MCP specification-supported properties like `annotations`. +Additional properties that are spread directly onto the actual resource definition, allowing you to add/override any MCP specification-supported properties. + +### `embeddedResourceProps` +Additional properties that are spread directly onto the embedded resource top-level definition, allowing you to add any MCP embedded resource [specification-supported](https://modelcontextprotocol.io/specification/2025-06-18/schema#embeddedresource) properties, like `annotations`. ## Building diff --git a/docs/src/guide/server/typescript/usage-examples.md b/docs/src/guide/server/typescript/usage-examples.md index c77cded2..d4e09e94 100644 --- a/docs/src/guide/server/typescript/usage-examples.md +++ b/docs/src/guide/server/typescript/usage-examples.md @@ -141,7 +141,7 @@ console.log('Resource 5:', JSON.stringify(resource5, null, 2)); ## Metadata Configuration Examples -The `createUIResource` function supports three types of metadata configuration to enhance your UI resources: +The `createUIResource` function supports several types of metadata configuration to enhance your UI resources: ```typescript import { createUIResource } from '@mcp-ui/server'; @@ -212,14 +212,14 @@ console.log('Resource with UI metadata:', JSON.stringify(resourceWithUIMetadata, } */ -// Example 9: Using resourceProps for additional MCP properties +// Example 9: Using embeddedResourceProps for additional MCP properties const resourceWithProps = createUIResource({ uri: 'ui://form/user-profile', content: { type: 'rawHtml', htmlString: '
...
' }, encoding: 'text', - resourceProps: { + embeddedResourceProps: { annotations: { - audience: ['developers', 'admins'], + audience: ['user'], priority: 'high' } } @@ -232,10 +232,10 @@ console.log('Resource with additional props:', JSON.stringify(resourceWithProps, "uri": "ui://form/user-profile", "mimeType": "text/html", "text": "
...
", - "annotations": { - "audience": ["developers", "admins"], - "priority": "high" - } + }, + "annotations": { + "audience": ["user"], + "priority": "high" } } */ @@ -245,7 +245,8 @@ console.log('Resource with additional props:', JSON.stringify(resourceWithProps, - **Use `metadata` for standard MCP resource information** like titles, descriptions, timestamps, and authorship - **Use `uiMetadata` for client rendering hints** like preferred sizes, initial data, and context preferences -- **Use `resourceProps` for MCP specification properties** like annotations, descriptions at the resource level, and other standard fields +- **Use `resourceProps` for MCP specification properties**, descriptions at the resource level, and other standard fields +- **Use `embeddedResourceProps` for MCP embedded resource properties** like annotations. ## Advanced URI List Example diff --git a/sdks/typescript/server/src/__tests__/index.test.ts b/sdks/typescript/server/src/__tests__/index.test.ts index e1524f07..9bc8594d 100644 --- a/sdks/typescript/server/src/__tests__/index.test.ts +++ b/sdks/typescript/server/src/__tests__/index.test.ts @@ -86,6 +86,42 @@ describe('@mcp-ui/server', () => { expect(resource.resource._meta).toEqual({ foo: 'bar', 'arbitrary-prop': 'arbitrary2' }); }); + it('should create a text-based external URL resource with embedded resource props', () => { + const options = { + uri: 'ui://test-url' as const, + content: { type: 'externalUrl' as const, iframeUrl: 'https://example.com' }, + encoding: 'text' as const, + uiMetadata: { 'preferred-frame-size': ['100px', '100px'] as [string, string] }, + resourceProps: { _meta: { 'arbitrary-metadata': 'resource-level-metadata' } }, + embeddedResourceProps: { + annotations: { + audience: ['user'], + }, + _meta: { 'arbitrary-metadata': 'embedded-resource-metadata' }, + }, + }; + const resource = createUIResource(options); + expect(resource).toEqual({ + type: 'resource', + resource: { + uri: 'ui://test-url', + mimeType: 'text/uri-list', + text: 'https://example.com', + blob: undefined, + _meta: { + 'arbitrary-metadata': 'resource-level-metadata', + [`${UI_METADATA_PREFIX}preferred-frame-size`]: ['100px', '100px'], + }, + }, + annotations: { + audience: ['user'], + }, + _meta: { + 'arbitrary-metadata': 'embedded-resource-metadata', + }, + }); + }); + it('should create a blob-based external URL resource', () => { const options = { uri: 'ui://test-url-blob' as const, diff --git a/sdks/typescript/server/src/index.ts b/sdks/typescript/server/src/index.ts index 4a284c51..b567279d 100644 --- a/sdks/typescript/server/src/index.ts +++ b/sdks/typescript/server/src/index.ts @@ -15,6 +15,8 @@ import { getAdditionalResourceProps, utf8ToBase64 } from './utils.js'; export type UIResource = { type: 'resource'; resource: HTMLTextContent | Base64BlobContent; + annotations?: Record; + _meta?: Record; }; /** @@ -96,6 +98,7 @@ export function createUIResource(options: CreateUIResourceOptions): UIResource { return { type: 'resource', resource: resource, + ...(options.embeddedResourceProps ?? {}), }; } diff --git a/sdks/typescript/server/src/types.ts b/sdks/typescript/server/src/types.ts index 41c4d7b8..db875328 100644 --- a/sdks/typescript/server/src/types.ts +++ b/sdks/typescript/server/src/types.ts @@ -1,4 +1,4 @@ -import type { Resource } from '@modelcontextprotocol/sdk/types.js'; +import type { EmbeddedResource, Resource } from '@modelcontextprotocol/sdk/types.js'; // Primary identifier for the resource. Starts with ui://` export type URI = `ui://${string}`; @@ -43,11 +43,14 @@ export interface CreateUIResourceOptions { uiMetadata?: UIResourceMetadata; // additional metadata to be passed on _meta metadata?: Record; - // additional resource props to be passed on resource (i.e. annotations) + // additional resource props to be passed on the resource itself resourceProps?: UIResourceProps; + // additional resource props to be passed on the top-level embedded resource (i.e. annotations) + embeddedResourceProps?: EmbeddedUIResourceProps; } export type UIResourceProps = Omit, 'uri' | 'mimeType'>; +export type EmbeddedUIResourceProps = Omit, 'resource' | 'type'>; export const UIMetadataKey = { PREFERRED_FRAME_SIZE: 'preferred-frame-size',