Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Rendered using the `<HTMLResourceRenderer />` component, which displays content

Rendered using the internal `<RemoteDOMResourceRenderer />` component, which utilizes Shopify's [`remote-dom`](https://github.com/Shopify/remote-dom). The server responds with a script that describes the UI and events. On the host, the script is securely rendered in a sandboxed iframe, and the UI changes are communicated to the host in JSON, where they're rendered using the host's component library. This is more flexible than iframes and allows for UIs that match the host's look-and-feel.

* **`mimeType`**: `application/vnd.mcp-ui.remote-dom; flavor={react | webcomponents}`
* **`mimeType`**: `application/vnd.mcp-ui.remote-dom; framework={react | webcomponents}`

### UI Action

Expand Down Expand Up @@ -139,14 +139,14 @@ You can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to
const htmlResource = createUIResource({
uri: 'ui://greeting/1',
content: { type: 'rawHtml', htmlString: '<p>Hello, MCP UI!</p>' },
delivery: 'text',
encoding: 'text',
});

// External URL
const externalUrlResource = createUIResource({
uri: 'ui://greeting/1',
content: { type: 'externalUrl', iframeUrl: 'https://example.com' },
delivery: 'text',
encoding: 'text',
});

// remote-dom
Expand All @@ -162,9 +162,9 @@ You can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to
});
root.appendChild(button);
`,
flavor: 'react', // or 'webcomponents'
framework: 'react', // or 'webcomponents'
},
delivery: 'text',
encoding: 'text',
});
```

Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/client/remote-dom-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This ensures that no arbitrary code from the server runs in the main application

## Props

- **`resource`**: The `resource` object from an MCP message. The `mimeType` must be `application/vnd.mcp-ui.remote-dom+javascript; flavor={react | webcomponents}`.
- **`resource`**: The `resource` object from an MCP message. The `mimeType` must be `application/vnd.mcp-ui.remote-dom+javascript; framework={react | webcomponents}`.
- **`library`**: A component library that maps remote element tag names (e.g., "button") to your host's React components.
- **`onUIAction`**: A callback function to handle events (e.g., button clicks) initiated from the remote UI.

Expand Down
4 changes: 2 additions & 2 deletions docs/src/guide/client/usage-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ const remoteDomResource = {
type: 'resource',
resource: {
uri: 'ui://remote-component/action-button',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; flavor=react',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; framework=react',
text: remoteDomScript,
},
};
Expand Down Expand Up @@ -130,7 +130,7 @@ const fetchMcpResource = async (id: string): Promise<any> => {
type: 'resource',
resource: {
uri: 'ui://remote-component/action-button',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; flavor=react',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; framework=react',
text: remoteDomScript,
},
};
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const myHtmlPayload = `<h1>Hello from Server!</h1><p>Timestamp: ${new Date().toI
const resourceBlock = createUIResource({
uri: 'ui://server-generated/item1',
content: { type: 'rawHtml', htmlString: myHtmlPayload },
delivery: 'text',
encoding: 'text',
});


Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import { createUIResource } from '@mcp-ui/server';
const resource = createUIResource({
uri: 'ui://my-tool/dashboard',
content: { type: 'rawHtml', htmlString: '<h1>Dashboard</h1>' },
delivery: 'text'
encoding: 'text'
});

// Return in MCP response
Expand Down
2 changes: 1 addition & 1 deletion docs/src/guide/protocol-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface UIResource {
- URL content: Embedding a Grafana dashboard, a third-party widget, a mini-application
- RemoteDOM content: A component to be rendered with the host's look-and-feel (component library)

## Content Delivery: `text` vs. `blob`
## Content encoding: `text` vs. `blob`

- **`text`**: Simple, direct string. Good for smaller, less complex content.
- **`blob`**: Base64 encoded string.
Expand Down
4 changes: 2 additions & 2 deletions docs/src/guide/server/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ The `@mcp-ui/server` package provides server-side utilities to help construct `U
## Key Exports

- **`createUIResource(options: CreateUIResourceOptions): UIResource`**:
The primary function for creating UI snippets. It takes an options object to define the URI, content (direct HTML or external URL), and delivery method (text or blob).
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).

## Purpose

- **Ease of Use**: Simplifies the creation of valid `UIResource` objects.
- **Validation**: Includes basic validation (e.g., URI prefixes matching content type).
- **Encoding**: Handles Base64 encoding when `delivery: 'blob'` is specified.
- **Encoding**: Handles Base64 encoding when `encoding: 'blob'` is specified.

## Building

Expand Down
26 changes: 13 additions & 13 deletions docs/src/guide/server/usage-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ console.log('Shared Enum from server usage:', PlaceholderEnum.FOO);
const resource1 = createUIResource({
uri: 'ui://my-component/instance-1',
content: { type: 'rawHtml', htmlString: '<p>Hello World</p>' },
delivery: 'text',
encoding: 'text',
});
console.log('Resource 1:', JSON.stringify(resource1, null, 2));
/* Output for Resource 1:
Expand All @@ -44,7 +44,7 @@ console.log('Resource 1:', JSON.stringify(resource1, null, 2));
const resource2 = createUIResource({
uri: 'ui://my-component/instance-2',
content: { type: 'rawHtml', htmlString: '<h1>Complex HTML</h1>' },
delivery: 'blob',
encoding: 'blob',
});
console.log(
'Resource 2 (blob will be Base64):',
Expand All @@ -61,12 +61,12 @@ console.log(
}
*/

// Example 3: External URL, text delivery
// Example 3: External URL, text encoding
const dashboardUrl = 'https://my.analytics.com/dashboard/123';
const resource3 = createUIResource({
uri: 'ui://analytics-dashboard/main',
content: { type: 'externalUrl', iframeUrl: dashboardUrl },
delivery: 'text',
encoding: 'text',
});
console.log('Resource 3:', JSON.stringify(resource3, null, 2));
/* Output for Resource 3:
Expand All @@ -80,12 +80,12 @@ console.log('Resource 3:', JSON.stringify(resource3, null, 2));
}
*/

// Example 4: External URL, blob delivery (URL is Base64 encoded)
// Example 4: External URL, blob encoding (URL is Base64 encoded)
const chartApiUrl = 'https://charts.example.com/api?type=pie&data=1,2,3';
const resource4 = createUIResource({
uri: 'ui://live-chart/session-xyz',
content: { type: 'externalUrl', iframeUrl: chartApiUrl },
delivery: 'blob',
encoding: 'blob',
});
console.log(
'Resource 4 (blob will be Base64 of URL):',
Expand All @@ -102,7 +102,7 @@ console.log(
}
*/

// Example 5: Remote DOM script, text delivery
// Example 5: Remote DOM script, text encoding
const remoteDomScript = `
const button = document.createElement('ui-button');
button.setAttribute('label', 'Click me for a tool call!');
Expand All @@ -117,17 +117,17 @@ const resource5 = createUIResource({
content: {
type: 'remoteDom',
script: remoteDomScript,
flavor: 'react', // or 'webcomponents'
framework: 'react', // or 'webcomponents'
},
delivery: 'text',
encoding: 'text',
});
console.log('Resource 5:', JSON.stringify(resource5, null, 2));
/* Output for Resource 5:
{
"type": "resource",
"resource": {
"uri": "ui://remote-component/action-button",
"mimeType": "application/vnd.mcp-ui.remote-dom+javascript; flavor=react",
"mimeType": "application/vnd.mcp-ui.remote-dom+javascript; framework=react",
"text": "\\n const button = document.createElement('ui-button');\\n button.setAttribute('label', 'Click me for a tool call!');\\n button.addEventListener('press', () => {\\n window.parent.postMessage({ type: 'tool', payload: { toolName: 'uiInteraction', params: { action: 'button-click', from: 'remote-dom' } } }, '*');\\n });\\n root.appendChild(button);\\n"
}
}
Expand All @@ -154,7 +154,7 @@ https://emergency.dashboard.example.com/main`;
const resource6 = createUIResource({
uri: 'ui://dashboard-with-fallbacks/session-123',
content: { type: 'externalUrl', iframeUrl: multiUrlContent },
delivery: 'text',
encoding: 'text',
});

/* The client will:
Expand All @@ -176,10 +176,10 @@ try {
createUIResource({
uri: 'invalid://should-be-ui',
content: { type: 'externalUrl', iframeUrl: 'https://example.com' },
delivery: 'text',
encoding: 'text',
});
} catch (e: any) {
console.error('Caught expected error:', e.message);
// MCP SDK: URI must start with 'ui://' when content.type is 'externalUrl'.
// MCP-UI SDK: URI must start with 'ui://' when content.type is 'externalUrl'.
}
```
2 changes: 1 addition & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const interactiveForm = createUIResource({
type: 'externalUrl',
iframeUrl: 'https://yourapp.com'
},
delivery: 'text',
encoding: 'text',
});
```

Expand Down
4 changes: 2 additions & 2 deletions examples/remote-dom-demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ function App() {

const mockResourceReact = useMemo(
() => ({
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; flavor=react',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; framework=react',
text: scriptContent,
}),
[scriptContent],
);

const mockResourceWebComponents = useMemo(
() => ({
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; flavor=webcomponents',
mimeType: 'application/vnd.mcp-ui.remote-dom+javascript; framework=webcomponents',
text: scriptContent,
}),
[scriptContent],
Expand Down
12 changes: 6 additions & 6 deletions examples/server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class MyMCP extends McpAgent {
const resourceBlock = createUIResource({
uri: uniqueUIAppUri,
content: { type: 'externalUrl', iframeUrl: pickerPageUrl },
delivery: 'text', // The URL itself is delivered as text
encoding: 'text', // The URL itself is delivered as text
});

return {
Expand All @@ -177,7 +177,7 @@ export class MyMCP extends McpAgent {
const resourceBlock = createUIResource({
uri: uniqueUIAppUri,
content: { type: 'externalUrl', iframeUrl: pickerPageUrl },
delivery: 'text', // The URL itself is delivered as text
encoding: 'text', // The URL itself is delivered as text
});

return {
Expand All @@ -189,10 +189,10 @@ export class MyMCP extends McpAgent {
this.server.tool('show_remote_dom_react', 'Shows a react remote-dom component', async () => {
const resourceBlock = createUIResource({
uri: `ui://remote-dom-react/${Date.now()}` as `ui://${string}`,
delivery: 'text',
encoding: 'text',
content: {
type: 'remoteDom',
flavor: 'react',
framework: 'react',
script: `
// Create a state variable to track the current logo
let isDarkMode = false;
Expand Down Expand Up @@ -262,10 +262,10 @@ export class MyMCP extends McpAgent {
async () => {
const resourceBlock = createUIResource({
uri: `ui://remote-dom-wc/${Date.now()}` as `ui://${string}`,
delivery: 'text',
encoding: 'text',
content: {
type: 'remoteDom',
flavor: 'webcomponents',
framework: 'webcomponents',
script: `
// Create a state variable to track the current logo
let isDarkMode = false;
Expand Down
10 changes: 5 additions & 5 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Rendered using the `<HTMLResourceRenderer />` component, which displays content

Rendered using the `<RemoteDOMResourceRenderer />` component, which uses Shopify's [`remote-dom`](https://github.com/Shopify/remote-dom). The server responds with a script that describes the UI and events. On the host, the script is securely rendered in a sandboxed iframe, and the UI changes are communicated to the host in JSON, where they're rendered using the host's component library. This is more flexible than iframes and allows for UIs that match the host's look-and-feel.

* **`mimeType`**: `application/vnd.mcp-ui.remote-dom; flavor={react | webcomponents}`
* **`mimeType`**: `application/vnd.mcp-ui.remote-dom+javascript; framework={react | webcomponents}`

### UI Action

Expand Down Expand Up @@ -139,14 +139,14 @@ You can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to
const htmlResource = createUIResource({
uri: 'ui://greeting/1',
content: { type: 'rawHtml', htmlString: '<p>Hello, MCP UI!</p>' },
delivery: 'text',
encoding: 'text',
});

// External URL
const externalUrlResource = createUIResource({
uri: 'ui://greeting/1',
content: { type: 'externalUrl', iframeUrl: 'https://example.com' },
delivery: 'text',
encoding: 'text',
});

// remote-dom
Expand All @@ -162,9 +162,9 @@ You can use [GitMCP](https://gitmcp.io/idosal/mcp-ui) to give your IDE access to
});
root.appendChild(button);
`,
flavor: 'react', // or 'webcomponents'
framework: 'react', // or 'webcomponents'
},
delivery: 'text',
encoding: 'text',
});
```

Expand Down
12 changes: 6 additions & 6 deletions packages/client/src/components/RemoteDOMResourceRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ export const RemoteDOMResourceRenderer: React.FC<RemoteDOMResourceProps> = ({
const threadRef = useRef<ThreadIframe<SandboxAPI> | null>(null);
const [error, setError] = useState<string | null>(null);

const flavor = useMemo(() => {
const framework = useMemo(() => {
const mimeType = resource.mimeType || '';
if (mimeType.includes('flavor=react')) {
if (mimeType.includes('framework=react')) {
return 'react';
}
// Default to webcomponents for legacy or unspecified
return 'webcomponents';
}, [resource.mimeType]);

const componentKey = `${library?.name}-${flavor}`;
const componentKey = `${library?.name}-${framework}`;

const { receiver, components } = useMemo(() => {
switch (flavor) {
switch (framework) {
case 'react': {
const reactReceiver = new RemoteReceiver();
const componentLibrary = library || basicComponentLibrary;
Expand Down Expand Up @@ -131,7 +131,7 @@ export const RemoteDOMResourceRenderer: React.FC<RemoteDOMResourceProps> = ({
const options = {
code,
remoteElements,
useReactRenderer: flavor === 'react',
useReactRenderer: framework === 'react',
componentLibrary: library?.name,
};
thread.imports
Expand All @@ -156,7 +156,7 @@ export const RemoteDOMResourceRenderer: React.FC<RemoteDOMResourceProps> = ({
onLoad={handleIframeLoad}
/>

{flavor === 'react' && components ? (
{framework === 'react' && components ? (
<RemoteRootRenderer receiver={receiver as RemoteReceiver} components={components} />
) : (
<RemoteDOMRenderer receiver={receiver as DOMRemoteReceiver} />
Expand Down
Loading