@mcp-ui/client Usage & Examples
Here's how to use the <UIResourceRenderer />
component from @mcp-ui/client
.
Installation
Make sure you have @mcp-ui/client
and its peer dependencies installed in your project:
bash
npm i @mcp-ui/client
1
Rendering Remote DOM Resources
This example shows how to render a remoteDom
resource. This requires a remoteElements
and componentLibrary
(minimal default provided) to be passed to the UIResourceRenderer
.
tsx
import React, { useState } from 'react';
import {
UIResourceRenderer,
UIActionResult,
basicComponentLibrary,
remoteTextDefinition,
remoteButtonDefinition
} from '@mcp-ui/client';
const remoteDomScript = `
const button = document.createElement('ui-button');
button.setAttribute('label', 'Click me for a tool call!');
button.addEventListener('press', () => {
window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'remote-dom' } } }, '*');
});
root.appendChild(button);
`;
// This mocks the resource as received from the server SDK
const remoteDomResource = {
type: 'resource',
resource: {
uri: 'ui://remote-component/action-button',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; flavor=react',
text: remoteDomScript,
},
};
const AppWithRemoteDOM: React.FC = () => {
const [lastAction, setLastAction] = useState<any>(null);
const handleGenericMcpAction = async (result: UIActionResult) => {
if (result.type === 'tool') {
setLastAction({ tool: result.payload.toolName, params: result.payload.params });
}
return { status: 'Action handled' };
};
return (
<div>
<UIResourceRenderer
resource={remoteDomResource.resource}
onUIAction={handleGenericMcpAction}
remoteDomProps={{
library: basicComponentLibrary,
remoteElements: [remoteButtonDefinition, remoteTextDefinition],
}}
/>
{lastAction && (
<div style={{ marginTop: 20, border: '1px solid green', padding: 10 }}>
<h3>Last Action Received by Host:</h3>
<pre>{JSON.stringify(lastAction, null, 2)}</pre>
</div>
)}
</div>
);
};
## Rendering HTML Resources
```tsx
import React, { useState } from 'react';
import {
UIResourceRenderer,
UIActionResult,
basicComponentLibrary,
remoteTextDefinition,
remoteButtonDefinition
} from '@mcp-ui/client';
// Simulate fetching an MCP UI resource
const fetchMcpResource = async (id: string): Promise<any> => {
if (id === 'raw') {
return {
type: 'resource',
resource: {
uri: 'ui://example/raw-html',
mimeType: 'text/html',
text: "<h1>raw HTML via Text</h1><p>Content loaded rawly.</p><button onclick=\"window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'rawClick', value: Date.now() } } }, '*')\">Click Me (raw)</button>",
},
};
} else if (id === 'blob') {
const html =
"<h1>HTML from Blob</h1><p>Content was Base64 encoded.</p><button onclick=\"window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'blobClick', value: 'test' } } }, '*')\">Click Me (Blob)</button>";
return {
type: 'resource',
resource: {
uri: 'ui://example/blob-html',
mimeType: 'text/html',
blob: btoa(html),
},
};
} else if (id === 'external') {
return {
type: 'resource',
resource: {
uri: 'ui://example/external-site',
mimeType: 'text/uri-list',
text: 'https://vitepress.dev',
},
};
}
if (id === 'remote') {
const remoteDomScript = `
const button = document.createElement('ui-button');
button.setAttribute('label', 'Click me for a tool call!');
button.addEventListener('press', () => {
window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'remote-dom' } } }, '*');
});
root.appendChild(button);
`;
return {
type: 'resource',
resource: {
uri: 'ui://remote-component/action-button',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; flavor=react',
text: remoteDomScript,
},
};
}
throw new Error('Unknown resource ID');
};
const App: React.FC = () => {
const [uiResource, setUIResource] = useState<UIResource | null>(
null,
);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [lastAction, setLastAction] = useState<any>(null);
const loadResource = async (id: string) => {
setLoading(true);
setError(null);
setUIResource(null);
try {
const block = await fetchMcpResource(id);
setUIResource(block);
} catch (e: any) {
setError(e.message);
}
setLoading(false);
};
const handleGenericMcpAction = async (result: UIActionResult) => {
if (result.type === 'tool') {
console.log(`Action received in host app - Tool: ${result.payload.toolName}, Params:`, result.payload.params);
setLastAction({ tool: result.payload.toolName, params: result.payload.params });
} else if (result.type === 'prompt') {
console.log(`Prompt received in host app:`, result.payload.prompt);
setLastAction({ prompt: result.payload.prompt });
} else if (result.type === 'link') {
console.log(`Link received in host app:`, result.payload.url);
setLastAction({ url: result.payload.url });
} else if (result.type === 'intent') {
console.log(`Intent received in host app:`, result.payload.intent);
setLastAction({ intent: result.payload.intent });
} else if (result.type === 'notification') {
console.log(`Notification received in host app:`, result.payload.message);
setLastAction({ message: result.payload.message });
}
return {
status: 'Action handled by host application',
};
};
return (
<div>
<h1>MCP-UI Client Demo</h1>
<button onClick={() => loadResource('raw')}>
Load raw HTML (Text)
</button>
<button onClick={() => loadResource('blob')}>
Load raw HTML (Blob)
</button>
<button onClick={() => loadResource('external')}>
Load External App (URL)
</button>
<button onClick={() => loadResource('remote')}>
Load Remote DOM
</button>
{loading && <p>Loading resource...</p>}
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{uiResource && uiResource.resource && (
<div style={{ marginTop: 20, border: '2px solid blue', padding: 10 }}>
<h2>Rendering Resource: {uiResource.resource.uri}</h2>
<UIResourceRenderer
resource={uiResource.resource}
onUIAction={handleGenericMcpAction}
remoteDomProps={{
library: basicComponentLibrary,
remoteElements: [remoteButtonDefinition, remoteTextDefinition],
}}
/>
</div>
)}
{lastAction && (
<div style={{ marginTop: 20, border: '1px solid green', padding: 10 }}>
<h3>Last Action Received by Host:</h3>
<pre>{JSON.stringify(lastAction, null, 2)}</pre>
</div>
)}
</div>
);
};
export default App;
1
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
That's it! Just use <UIResourceRenderer />
with the right props and you're ready to render interactive HTML from MCP resources in your React app. The UIResourceRenderer
automatically detects the resource type and renders the appropriate component internally. If you need more details, check out the UIResourceRenderer Component page.