UIResourceRenderer Component
For MCP Apps Hosts
If your host supports MCP Apps (the standard), use AppRenderer instead. It fetches resources, handles the lifecycle, and provides a complete MCP Apps experience.
UIResourceRenderer is for legacy MCP-UI hosts that embed UI resources directly in tool responses.
The UIResourceRenderer component renders MCP-UI resources embedded in tool responses. It automatically detects the resource type and renders the appropriate component internally. It is available as a React component and as a Web Component.
React Component
React applications may use the <UIResourceRenderer /> component.
Web Component
For developers using frameworks other than React, a Web Component version is available. It can be used as a standard HTML element (<ui-resource-renderer>) and styled with regular CSS.
» See the Web Component Usage & Examples for more details
Why use UIResourceRenderer?
- Automatic Type Detection: Inspects the resource's
mimeTypeandcontentTypeto determine the appropriate rendering method - Future-Proof: Handles new resource types as they're added to the MCP-UI specification
- Unified API: Single component interface for all resource types
- Security: Maintains sandboxing and security features across all resource types
Supported Resource Types
UIResourceRenderer automatically handles:
- HTML Resources (
text/html;profile=mcp-app): Direct HTML content rendered in sandboxed iframes - External URLs: External URLs now use
text/html;profile=mcp-app- hosts that support external URLs will detect URL content
Metadata Integration
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:
Automatic Frame Sizing
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.
Automatic Data Passing
mcpui.dev/ui-initial-render-data: When present, this data is automatically merged with theiframeRenderDataprop (if provided) and passed to the iframe using theui-lifecycle-iframe-render-datamechanism.
Props
import type { Resource } from '@modelcontextprotocol/sdk/types';
interface UIResourceRendererProps {
resource: Partial<Resource>;
onUIAction?: (result: UIActionResult) => Promise<unknown>;
supportedContentTypes?: ResourceContentType[];
htmlProps?: Omit<HTMLResourceRendererProps, 'resource' | 'onUIAction'>;
}2
3
4
5
6
7
8
Props Details
resource: The resource object from an MCP response. Should includeuri,mimeType, and content (text,blob, orcontent)onUIAction: Optional callback for handling UI actions from the resource:typescript{ type: 'tool', payload: { toolName: string, params: Record<string, unknown> }, messageId?: string } | { type: 'intent', payload: { intent: string, params: Record<string, unknown> }, messageId?: string } | { type: 'prompt', payload: { prompt: string }, messageId?: string } | { type: 'notify', payload: { message: string }, messageId?: string } | { type: 'link', payload: { url: string }, messageId?: string }1
2
3
4
5Asynchronous Communication: When actions include a
messageId, the iframe automatically receives response messages (ui-message-received,ui-message-response). See Protocol Details for examples.supportedContentTypes: Optional array to restrict which content types are allowed (['rawHtml', 'externalUrl'])htmlProps: Optional props for the<HTMLResourceRenderer>style: Optional custom styles for iframe-based resourcesproxy: Optional. A URL to a static "proxy" script for rendering external URLs. See Using a Proxy for External URLs for details.sandboxPermissions: Optional string to add additional iframe sandbox permissions. These are added to the default sandbox permissions:- For external URLs:
'allow-scripts allow-same-origin' - For raw HTML iframes:
'allow-scripts'
- For external URLs:
iframeProps: Optional props passed to iframe elements (for HTML/URL resources)ref: Optional React ref to access the underlying iframe element
iframeRenderData: OptionalRecord<string, unknown>to pass data to the iframe upon rendering. This enables advanced use cases where the parent application needs to provide initial state or configuration to the sandboxed iframe content.autoResizeIframe: Optionalboolean | { width?: boolean; height?: boolean }to automatically resize the iframe to the size of the content.
Basic Usage
import React from 'react';
import { UIResourceRenderer, UIActionResult, isUIResource } from '@mcp-ui/client';
function App({ mcpResource }) {
const handleUIAction = async (result: UIActionResult) => {
switch (result.type) {
case 'tool':
console.log('Tool call:', result.payload.toolName, result.payload.params);
// Handle tool execution
break;
case 'prompt':
console.log('Prompt:', result.payload.prompt);
// Handle prompt display
break;
case 'link':
console.log('Link:', result.payload.url);
// Handle link navigation
break;
case 'intent':
console.log('Intent:', result.payload.intent, result.payload.params);
// Handle intent processing
break;
case 'notify':
console.log('Notification:', result.payload.message);
// Handle notification display
break;
}
return { status: 'handled' };
};
if (isUIResource(mcpResource)) {
return (
<UIResourceRenderer
resource={mcpResource.resource}
onUIAction={handleUIAction}
/>
);
}
return <p>Unsupported resource</p>;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Metadata Usage Examples
When a resource is created on the server with metadata, the UIResourceRenderer automatically applies the configuration:
// Server-side resource creation (for reference)
// const serverResource = createUIResource({
// uri: 'ui://chart/dashboard',
// content: { type: 'externalUrl', iframeUrl: 'https://charts.example.com' },
// encoding: 'text',
// uiMetadata: {
// 'preferred-frame-size': [ '800px', '600px' ],
// 'initial-render-data': { theme: 'dark', userId: '123' }
// }
// });
// Client-side rendering - metadata is automatically applied
function Dashboard({ mcpResource }) {
const handleUIAction = async (result: UIActionResult) => {
// Handle UI actions
return { status: 'handled' };
};
return (
<UIResourceRenderer
resource={mcpResource.resource} // Contains metadata from server
htmlProps={{
// Additional render data can be merged with metadata
iframeRenderData: {
sessionId: 'abc123',
permissions: ['read', 'write']
}
}}
onUIAction={handleUIAction}
/>
);
}
// The UIResourceRenderer will:
// 1. Use ['800px', '600px'] as the initial iframe size
// 2. Merge server metadata with prop data:
// { theme: 'dark', userId: '123', sessionId: 'abc123', permissions: ['read', 'write'] }
// 3. Pass the combined data to the iframe via ui-lifecycle-iframe-render-data2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Metadata Precedence
When both server metadata and component props provide similar data:
- Frame sizing: Server
preferred-frame-sizeis used as the initial size, but can be overridden by component styling - Render data: Server
initial-render-datais merged with theiframeRenderDataprop, with prop values taking precedence for duplicate keys
Utility Functions
isUIResource()
The isUIResource() utility function is a convenient way to check if content from an MCP response is a UI resource. Instead of manually checking:
content.type === 'resource' && content.resource.uri?.startsWith('ui://')You can simply use:
import { isUIResource } from '@mcp-ui/client';
if (isUIResource(content)) {
// This is a UI resource, safe to render
}2
3
4
5
The function provides type narrowing, so TypeScript will understand that content.resource is available after the check.
Advanced Usage
Content Type Restrictions
// Only allow HTML resources (block external URLs and Remote DOM)
<UIResourceRenderer
resource={mcpResource.resource}
supportedContentTypes={['rawHtml']}
onUIAction={handleUIAction}
/>2
3
4
5
6
Custom Styling
<UIResourceRenderer
resource={mcpResource.resource}
htmlProps={{
style: {
border: '2px solid #007acc',
borderRadius: '8px',
minHeight: '400px'
},
iframeProps: {
title: 'Custom MCP Resource',
className: 'mcp-resource-frame'
}
}}
onUIAction={handleUIAction}
/>2
3
4
5
6
7
8
9
10
11
12
13
14
15
Passing Render-Time Data to Iframes
The iframeRenderData prop allows you to send a data payload to an iframe as it renders. This is useful for initializing the iframe with dynamic data from the parent application.
When iframeRenderData is provided:
- The iframe's URL will automatically include
?waitForRenderData=true. The iframe's internal script can use this to know it should wait for data instead of immediately rendering. - The data is sent to the iframe via
postMessageusing a dual-mechanism approach to ensure reliable delivery:- On Load: A
ui-lifecycle-iframe-render-datamessage is sent as soon as the iframe'sonLoadevent fires. - On Ready: If the iframe sends a
ui-lifecycle-iframe-readymessage, the parent will respond with the sameui-lifecycle-iframe-render-datapayload.
- On Load: A
This ensures the data is delivered whether the iframe is ready immediately or needs to perform setup work first.
<UIResourceRenderer
resource={mcpResource.resource}
htmlProps={{
iframeRenderData: {
theme: 'dark',
user: { id: '123', name: 'John Doe' }
}
}}
onUIAction={handleUIAction}
/>2
3
4
5
6
7
8
9
10
Inside the iframe, you can listen for this data:
// In the iframe's script
// If the iframe needs to do async work, it can tell the parent when it's ready
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('waitForRenderData') === 'true') {
let customRenderData = null;
// The parent will send this message on load or when we notify it we're ready
window.addEventListener('message', (event) => {
// Add origin checks for security
if (event.data.type === 'ui-lifecycle-iframe-render-data') {
// If the iframe has already received data, we don't need to do anything
if(customRenderData) {
return;
} else {
customRenderData = event.data.payload.renderData;
// Now you can render the UI with the received data
renderUI(customRenderData);
}
}
});
// We can let the parent know we're ready to receive data
window.parent.postMessage({ type: 'ui-lifecycle-iframe-ready' }, '*');
} else {
// If the iframe doesn't need to wait for data, we can render the default UI immediately
renderUI();
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Alternative: Explicit Render Data Requests
Instead of relying on the ui-lifecycle-iframe-ready lifecycle event, iframes can explicitly request render data when needed using the ui-request-render-data message type:
// In the iframe's script - explicit render data request
async function requestRenderData() {
return new Promise((resolve, reject) => {
const messageId = crypto.randomUUID();
window.parent.postMessage(
{ type: 'ui-request-render-data', messageId },
'*'
);
function handleMessage(event) {
if (event.data?.type !== 'ui-lifecycle-iframe-render-data') return;
if (event.data.messageId !== messageId) return;
window.removeEventListener('message', handleMessage);
const { renderData, error } = event.data.payload;
if (error) return reject(error);
return resolve(renderData);
}
window.addEventListener('message', handleMessage);
});
}
// Use it when your iframe is ready
const renderData = await requestRenderData();
renderUI(renderData);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Automatically Resizing the Iframe
The autoResizeIframe prop allows you to automatically resize the iframe to the size of the content.
<UIResourceRenderer
resource={mcpResource.resource}
htmlProps={{
autoResizeIframe: true,
}}
onUIAction={handleUIAction}
/>2
3
4
5
6
7
The autoResizeIframe prop can be a boolean or an object with the following properties:
width: Optional boolean to automatically resize the iframe's width to the size of the content.height: Optional boolean to automatically resize the iframe's height to the size of the content.
If autoResizeIframe is a boolean, the iframe will be resized to the size of the content.
Inside the iframe, you can listen for the ui-size-change message and resize the iframe to the size of the content.
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
window.parent.postMessage(
{
type: "ui-size-change",
payload: {
height: entry.contentRect.height,
},
},
"*",
);
});
});
resizeObserver.observe(document.documentElement)2
3
4
5
6
7
8
9
10
11
12
13
14
15
See Automatically Resizing the Iframe for a more detailed example.
Accessing the Iframe Element
You can pass a ref through iframeProps to access the underlying iframe element:
import React, { useRef } from 'react';
function MyComponent({ mcpResource }) {
const iframeRef = useRef<HTMLIFrameElement>(null);
const handleFocus = () => {
// Access the iframe element directly
if (iframeRef.current) {
iframeRef.current.focus();
}
};
return (
<div>
<button onClick={handleFocus}>Focus Iframe</button>
<UIResourceRenderer
resource={mcpResource.resource}
htmlProps={{
iframeProps: {
ref: iframeRef,
title: 'MCP Resource'
}
}}
onUIAction={handleUIAction}
/>
</div>
);
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Resource Type Detection
UIResourceRenderer determines the resource type using this logic:
- Explicit
contentType: Ifresource.contentTypeis set, use it directly - MIME Type Detection:
text/htmlortext/html;profile=mcp-app→ content is inspected to determine if it's HTML or a URL
- Fallback: Show unsupported resource error
Error Handling
function App({ mcpResource }) {
if (
mcpResource.type === 'resource' &&
mcpResource.resource.uri?.startsWith('ui://')
) {
return (
<div>
<h3>MCP Resource: {mcpResource.resource.uri}</h3>
<UIResourceRenderer
resource={mcpResource.resource}
onUIAction={handleUIAction}
supportedContentTypes={['rawHtml', 'externalUrl']} // Restrict types
/>
</div>
);
}
return <p>This resource is not a UI resource</p>;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
When unsupported content types are encountered, UIResourceRenderer will display appropriate error messages:
"Unsupported content type: {type}.""Unsupported resource type."
Security Considerations
- Sandboxing: HTML and URL resources are rendered in sandboxed iframes
- Content Restrictions: Use
supportedContentTypesto limit allowed resource types - Origin Validation: Always validate
postMessageorigins in production - Content Sanitization: Consider sanitizing HTML content for untrusted sources
Examples
See Client SDK Usage & Examples for complete working examples.

