Skip to content

Commit f1c1c9b

Browse files
authored
feat: support passing resource metadata (#87)
This PR adds the option to pass resource metadata over the `_meta` key. It allows passing mcp-ui specific metadata that can be used on the client side. See discussion in #49. ## Server side When using `createUIResource()` from the server SDK, you can now pass `uiMetadata`, `metadata` and `resourceProps`. 1. `metadata` will be translated to the standard `_meta` property on the resource. 2. `uiMetadata` is for mcp-ui specific configuration. The keys will be prefixed with `mcpui.dev/ui-`. There is a closed list: 1. `preferred-frame-size` - for the resource to define its preferred initial frame size 2. `initial-render-data` - for the resource to define an object that should be passed to it when rendering the iframe on the page 3. `resourceProps` will be spread and added to the resource definition, this allows adding additional spec-supported props (such as `annotations`). ```ts createUIResource({ uri: 'ui://user-form/1', content: { type: 'externalUrl', iframeUrl: 'https://yourapp.com' }, encoding: 'text', // mcp-ui specific and typed metadata // these will be prefixed `mcpui.dev/ui-` and will be picked up by `UIResourceRenderer` uiMetadata: { // preferred initial frame size 'preferred-frame-size': ['300px', '400px'], // initial data that will be sent back to the iframe on render 'initial-render-data': { someKey: 'Some data' }, }, // these will be put as-is under the resource's `_meta` key, can be also read by `getResourceMetadata()` metadata: { 'arbitrary-metadata': 'value', //... }, // these will be passed as-is to the resource resourceProps: { annotations: { audience: 'user', priority: 0.8, //... etc } }, }); ``` ## Renderer On the client side, the `<UIResourceRenderer />` can pick up the reserved mcp-ui keys from the metadata: 1. `preferred-frame-size` will be used as the initial frame size 2. `initial-render-data` will be merged with `iframeRenderData` prop (if exists) and will be passed using the `ui-lifecycle-iframe-render-data` [mechanism](https://mcpui.dev/guide/embeddable-ui#passing-render-data-to-the-iframe) ## Utility functions 1. `getResourceMetadata(resource)` - returns the resource's `_meta` content 2. `getUIResourceMetadata(resource)` - returns only the mcp-ui keys from the resource `_meta` content
1 parent 3dc3d1f commit f1c1c9b

18 files changed

Lines changed: 677 additions & 25 deletions

docs/src/guide/client/overview.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ The `@mcp-ui/client` package helps you render UI resources sent from an MCP-enab
44

55
## What's Included?
66

7+
### Components
78
- **`<UIResourceRenderer />`**: The main component you'll use. It inspects the resource's `mimeType` and renders either `<HTMLResourceRenderer />` or `<RemoteDOMResourceRenderer />` internally.
89
- **`<HTMLResourceRenderer />`**: Internal component for HTML/URL resources
910
- **`<RemoteDOMResourceRenderer />`**: Internal component for remote DOM resources
1011
- **`isUIResource()`**: Utility function to check if content is a UI resource (replaces manual `content.type === 'resource' && content.resource.uri?.startsWith('ui://')` checks)
1112

13+
### Utility Functions
14+
- **`getResourceMetadata(resource)`**: Extracts the resource's `_meta` content (standard MCP metadata)
15+
- **`getUIResourceMetadata(resource)`**: Extracts only the MCP-UI specific metadata keys (prefixed with `mcpui.dev/ui-`) from the resource's `_meta` content
16+
1217
## Purpose
1318
- **Standardized UI**: mcp-ui's client guarantees full compatibility with the latest MCP UI standards.
1419
- **Simplified Rendering**: Abstract away the complexities of handling different resource types.
@@ -25,6 +30,94 @@ To build just this package from the monorepo root:
2530
pnpm build --filter @mcp-ui/client
2631
```
2732

33+
## Utility Functions Reference
34+
35+
### `getResourceMetadata(resource)`
36+
37+
Extracts the standard MCP metadata from a resource's `_meta` property.
38+
39+
```typescript
40+
import { getResourceMetadata } from '@mcp-ui/client';
41+
42+
const resource = {
43+
uri: 'ui://example/demo',
44+
mimeType: 'text/html',
45+
text: '<div>Hello</div>',
46+
_meta: {
47+
title: 'Demo Component',
48+
version: '1.0.0',
49+
'mcpui.dev/ui-preferred-frame-size': ['800px', '600px'],
50+
'mcpui.dev/ui-initial-render-data': { theme: 'dark' },
51+
author: 'Development Team'
52+
}
53+
};
54+
55+
const metadata = getResourceMetadata(resource);
56+
console.log(metadata);
57+
// Output: {
58+
// title: 'Demo Component',
59+
// version: '1.0.0',
60+
// 'mcpui.dev/ui-preferred-frame-size': ['800px', '600px'],
61+
// 'mcpui.dev/ui-initial-render-data': { theme: 'dark' },
62+
// author: 'Development Team'
63+
// }
64+
```
65+
66+
### `getUIResourceMetadata(resource)`
67+
68+
Extracts only the MCP-UI specific metadata keys (those prefixed with `mcpui.dev/ui-`) from a resource's `_meta` property, with the prefixes removed for easier access.
69+
70+
```typescript
71+
import { getUIResourceMetadata } from '@mcp-ui/client';
72+
73+
const resource = {
74+
uri: 'ui://example/demo',
75+
mimeType: 'text/html',
76+
text: '<div>Hello</div>',
77+
_meta: {
78+
title: 'Demo Component',
79+
version: '1.0.0',
80+
'mcpui.dev/ui-preferred-frame-size': ['800px', '600px'],
81+
'mcpui.dev/ui-initial-render-data': { theme: 'dark' },
82+
author: 'Development Team'
83+
}
84+
};
85+
86+
const uiMetadata = getUIResourceMetadata(resource);
87+
console.log(uiMetadata);
88+
// Output: {
89+
// 'preferred-frame-size': ['800px', '600px'],
90+
// 'initial-render-data': { theme: 'dark' },
91+
// }
92+
```
93+
94+
### Usage Examples
95+
96+
These utility functions are particularly useful when you need to access metadata programmatically:
97+
98+
```typescript
99+
import { getUIResourceMetadata, UIResourceRenderer } from '@mcp-ui/client';
100+
101+
function SmartResourceRenderer({ resource }) {
102+
const uiMetadata = getUIResourceMetadata(resource);
103+
104+
// Use metadata to make rendering decisions
105+
const initialRenderData = uiMetadata['initial-render-data'];
106+
const containerClass = initialRenderData.preferredContext === 'hero' ? 'hero-container' : 'default-container';
107+
108+
return (
109+
<div className={containerClass}>
110+
{preferredContext === 'hero' && (
111+
<h2>Featured Component</h2>
112+
)}
113+
<UIResourceRenderer resource={resource} />
114+
</div>
115+
);
116+
}
117+
```
118+
119+
## See More
120+
28121
See the following pages for more details:
29122

30123
- [UIResourceRenderer Component](./resource-renderer.md) - **Main entry point**

docs/src/guide/client/resource-renderer.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ For developers using frameworks other than React, a Web Component version is ava
2828
- **External URLs** (`text/uri-list`): External applications and websites
2929
- **Remote DOM Resources** (`application/vnd.mcp-ui.remote-dom`): Server-generated UI components using Shopify's remote-dom
3030

31+
## Metadata Integration
32+
33+
The `UIResourceRenderer` automatically detects and uses metadata from resources created with the server SDK's `createUIResource()` function. It looks for MCP-UI specific metadata keys prefixed with `mcpui.dev/ui-` in the resource's `_meta` property:
34+
35+
### Automatic Frame Sizing
36+
- **`mcpui.dev/ui-preferred-frame-size`**: When present, this metadata is used as the initial size for iframe-based resources, overriding any default sizing behavior.
37+
38+
### Automatic Data Passing
39+
- **`mcpui.dev/ui-initial-render-data`**: When present, this data is automatically merged with the `iframeRenderData` prop (if provided) and passed to the iframe using the `ui-lifecycle-iframe-render-data` mechanism.
40+
3141
## Props
3242

3343
```typescript
@@ -118,6 +128,57 @@ function App({ mcpResource }) {
118128
}
119129
```
120130

131+
## Metadata Usage Examples
132+
133+
When a resource is created on the server with metadata, the `UIResourceRenderer` automatically applies the configuration:
134+
135+
```tsx
136+
// Server-side resource creation (for reference)
137+
// const serverResource = createUIResource({
138+
// uri: 'ui://chart/dashboard',
139+
// content: { type: 'externalUrl', iframeUrl: 'https://charts.example.com' },
140+
// encoding: 'text',
141+
// uiMetadata: {
142+
// 'preferred-frame-size': [ '800px', '600px' ],
143+
// 'initial-render-data': { theme: 'dark', userId: '123' }
144+
// }
145+
// });
146+
147+
// Client-side rendering - metadata is automatically applied
148+
function Dashboard({ mcpResource }) {
149+
const handleUIAction = async (result: UIActionResult) => {
150+
// Handle UI actions
151+
return { status: 'handled' };
152+
};
153+
154+
return (
155+
<UIResourceRenderer
156+
resource={mcpResource.resource} // Contains metadata from server
157+
htmlProps={{
158+
// Additional render data can be merged with metadata
159+
iframeRenderData: {
160+
sessionId: 'abc123',
161+
permissions: ['read', 'write']
162+
}
163+
}}
164+
onUIAction={handleUIAction}
165+
/>
166+
);
167+
}
168+
169+
// The UIResourceRenderer will:
170+
// 1. Use ['800px', '600px'] as the initial iframe size
171+
// 2. Merge server metadata with prop data:
172+
// { theme: 'dark', userId: '123', sessionId: 'abc123', permissions: ['read', 'write'] }
173+
// 3. Pass the combined data to the iframe via ui-lifecycle-iframe-render-data
174+
```
175+
176+
### Metadata Precedence
177+
178+
When both server metadata and component props provide similar data:
179+
- **Frame sizing**: Server `preferred-frame-size` is used as the initial size, but can be overridden by component styling
180+
- **Render data**: Server `initial-render-data` is merged with the `iframeRenderData` prop, with prop values taking precedence for duplicate keys
181+
121182
## Utility Functions
122183

123184
### `isUIResource()`

docs/src/guide/server/typescript/overview.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,30 @@ For a complete example, see the [`typescript-server-demo`](https://github.com/id
77
## Key Exports
88

99
- **`createUIResource(options: CreateUIResourceOptions): UIResource`**:
10-
The primary function for creating UI snippets. It takes an options object to define the URI, content (direct HTML or external URL), and encoding method (text or blob).
10+
The primary function for creating UI snippets. It takes an options object to define the URI, content (direct HTML or external URL), encoding method (text or blob), and metadata configuration.
1111

1212
## Purpose
1313

1414
- **Ease of Use**: Simplifies the creation of valid `UIResource` objects.
1515
- **Validation**: Includes basic validation (e.g., URI prefixes matching content type).
1616
- **Encoding**: Handles Base64 encoding when `encoding: 'blob'` is specified.
17+
- **Metadata Support**: Provides flexible metadata configuration for enhanced client-side rendering and resource management.
18+
19+
## Metadata Features
20+
21+
The `createUIResource()` function supports three types of metadata configuration to enhance resource functionality:
22+
23+
### `metadata`
24+
Standard MCP resource metadata that becomes the `_meta` property on the resource. This follows the MCP specification for resource metadata.
25+
26+
### `uiMetadata`
27+
MCP-UI specific configuration options. These keys are automatically prefixed with `mcpui.dev/ui-` in the resource metadata:
28+
29+
- **`preferred-frame-size`**: Define the resource's preferred initial frame size (e.g., `{ width: 800, height: 600 }`)
30+
- **`initial-render-data`**: Provide data that should be passed to the iframe when rendering
31+
32+
### `resourceProps`
33+
Additional properties that are spread directly onto the resource definition, allowing you to add any MCP specification-supported properties like `annotations`.
1734

1835
## Building
1936

docs/src/guide/server/typescript/usage-examples.md

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,120 @@ console.log('Resource 5:', JSON.stringify(resource5, null, 2));
139139
// of a toolResult in an MCP interaction.
140140
```
141141

142+
## Metadata Configuration Examples
143+
144+
The `createUIResource` function supports three types of metadata configuration to enhance your UI resources:
145+
146+
```typescript
147+
import { createUIResource } from '@mcp-ui/server';
148+
149+
// Example 7: Using standard metadata
150+
const resourceWithMetadata = createUIResource({
151+
uri: 'ui://analytics/dashboard',
152+
content: { type: 'rawHtml', htmlString: '<div id="dashboard">Loading...</div>' },
153+
encoding: 'text',
154+
metadata: {
155+
title: 'Analytics Dashboard',
156+
description: 'Real-time analytics and metrics',
157+
created: '2024-01-15T10:00:00Z',
158+
author: 'Analytics Team',
159+
preferredRenderContext: 'sidebar'
160+
}
161+
});
162+
console.log('Resource with metadata:', JSON.stringify(resourceWithMetadata, null, 2));
163+
/* Output includes:
164+
{
165+
"type": "resource",
166+
"resource": {
167+
"uri": "ui://analytics/dashboard",
168+
"mimeType": "text/html",
169+
"text": "<div id=\"dashboard\">Loading...</div>",
170+
"_meta": {
171+
"title": "Analytics Dashboard",
172+
"description": "Real-time analytics and metrics",
173+
"created": "2024-01-15T10:00:00Z",
174+
"author": "Analytics Team",
175+
"preferredRenderContext": "sidebar"
176+
}
177+
}
178+
}
179+
*/
180+
181+
// Example 8: Using uiMetadata for client-side configuration
182+
const resourceWithUIMetadata = createUIResource({
183+
uri: 'ui://chart/interactive',
184+
content: { type: 'externalUrl', iframeUrl: 'https://charts.example.com/widget' },
185+
encoding: 'text',
186+
uiMetadata: {
187+
'preferred-frame-size': ['800px', '600px'],
188+
'initial-render-data': {
189+
theme: 'dark',
190+
chartType: 'bar',
191+
dataSet: 'quarterly-sales'
192+
},
193+
}
194+
});
195+
console.log('Resource with UI metadata:', JSON.stringify(resourceWithUIMetadata, null, 2));
196+
/* Output includes:
197+
{
198+
"type": "resource",
199+
"resource": {
200+
"uri": "ui://chart/interactive",
201+
"mimeType": "text/uri-list",
202+
"text": "https://charts.example.com/widget",
203+
"_meta": {
204+
"mcpui.dev/ui-preferred-frame-size": ["800px", "600px"],
205+
"mcpui.dev/ui-initial-render-data": {
206+
"theme": "dark",
207+
"chartType": "bar",
208+
"dataSet": "quarterly-sales"
209+
},
210+
}
211+
}
212+
}
213+
*/
214+
215+
// Example 9: Using resourceProps for additional MCP properties
216+
const resourceWithProps = createUIResource({
217+
uri: 'ui://form/user-profile',
218+
content: { type: 'rawHtml', htmlString: '<form id="profile">...</form>' },
219+
encoding: 'text',
220+
resourceProps: {
221+
annotations: {
222+
audience: ['developers', 'admins'],
223+
priority: 'high'
224+
}
225+
}
226+
});
227+
console.log('Resource with additional props:', JSON.stringify(resourceWithProps, null, 2));
228+
/* Output includes:
229+
{
230+
"type": "resource",
231+
"resource": {
232+
"uri": "ui://form/user-profile",
233+
"mimeType": "text/html",
234+
"text": "<form id=\"profile\">...</form>",
235+
"annotations": {
236+
"audience": ["developers", "admins"],
237+
"priority": "high"
238+
}
239+
}
240+
}
241+
*/
242+
```
243+
244+
### Metadata Best Practices
245+
246+
- **Use `metadata` for standard MCP resource information** like titles, descriptions, timestamps, and authorship
247+
- **Use `uiMetadata` for client rendering hints** like preferred sizes, initial data, and context preferences
248+
- **Use `resourceProps` for MCP specification properties** like annotations, descriptions at the resource level, and other standard fields
249+
142250
## Advanced URI List Example
143251

144252
You can provide multiple URLs in the `text/uri-list` format for fallback purposes. However, **MCP-UI requires a single URL** and will only use the first valid URL found:
145253

146254
```typescript
147-
// Example 6: Multiple URLs with fallbacks (MCP-UI uses only the first)
255+
// Example 10: Multiple URLs with fallbacks (MCP-UI uses only the first)
148256
const multiUrlContent = `# Primary dashboard
149257
https://dashboard.example.com/main
150258
@@ -154,7 +262,7 @@ https://backup.dashboard.example.com/main
154262
# Emergency fallback (will be logged but not used)
155263
https://emergency.dashboard.example.com/main`;
156264

157-
const resource6 = createUIResource({
265+
const resource = createUIResource({
158266
uri: 'ui://dashboard-with-fallbacks/session-123',
159267
content: { type: 'externalUrl', iframeUrl: multiUrlContent },
160268
encoding: 'text',

0 commit comments

Comments
 (0)