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
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ dist
coverage
*.log
**/*.js
packages/client/src/iframe-bundle.ts

examples
79 changes: 39 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

`mcp-ui` is a TypeScript SDK comprising two packages:

* **`@mcp-ui/server`**: Utilities to generate `HtmlResourceBlock` objects on your MCP server.
* **`@mcp-ui/client`**: UI components (e.g., `<HtmlResource />`) to render those blocks in the browser and handle their events.
* **`@mcp-ui/server`**: Utilities to generate UI resource objects (`HtmlResourceBlock`) on your MCP server.
* **`@mcp-ui/client`**: UI components (e.g., `<ResourceRenderer />`) to render those blocks in the browser and handle their events.

Together, they let you define reusable UI resource blocks on the server side, seamlessly display them in the client, and react to their actions in the MCP host environment.

Expand All @@ -41,38 +41,33 @@ Together, they let you define reusable UI resource blocks on the server side, se

## ✨ Core Concepts

### HtmlResource
The primary component for rendering MCP resources is `<ResourceRenderer />`. It automatically detects the resource type and renders the appropriate component.

The primary payload exchanged between the server and the client:
### Supported Resource Types

```ts
interface HtmlResourceBlock {
type: 'resource';
resource: {
uri: string; // ui://component/id
mimeType: 'text/html' | 'text/uri-list'; // text/html for HTML content, text/uri-list for URL content
text?: string; // Inline HTML or external URL
blob?: string; // Base64-encoded HTML or URL
};
}
```
#### HTML (`text/html` and `text/uri-list`)

Rendered using the `<HtmlResource />` component, which displays content inside an `<iframe>`. This is suitable for self-contained HTML or embedding external sites.

* **`uri`**: Unique identifier for caching and routing
* `ui://…` — UI resources (rendering method determined by mimeType)
* **`mimeType`**: `text/html` for HTML content (iframe srcDoc), `text/uri-list` for URL content (iframe src)
* **MCP-UI requires a single URL**: While `text/uri-list` format supports multiple URLs, MCP-UI uses only the first valid URL and logs others
* **`text` vs. `blob`**: Choose `text` for simple strings; use `blob` for larger or encoded content.
* **`mimeType`**:
* `text/html`: Renders inline HTML content.
* `text/uri-list`: Renders an external URL. MCP-UI uses the first valid URL.
* **Props**:
* **`resource`**: The `resource` object from an MCP message.
* **`onUiAction`**: A callback function to handle events.
* **`supportedContentTypes`**: (Optional) Array to filter content types (`'rawHtml'`, `'externalUrl'`).
* **`style`**: (Optional) Custom styles for the iframe.
* **`iframeProps`**: (Optional) Custom iframe props.

It's rendered in the client with the `<HtmlResource>` React component.
The component accepts the following props:
#### Remote DOM (`application/vnd.mcp-ui.remote-dom`)

* **`resource`**: The `resource` object from an MCP message.
* **`onUiAction`**: A callback function to handle events from the resource.
* **`supportedContentTypes`**: (Optional) An array of content types to allow. Can include `'rawHtml'` and/or `'externalUrl'`. If omitted, all supported types are rendered. This is useful for restricting content types due to capability or security considerations.
* **`style`**: (Optional) Custom styles for the iframe.
* **`iframeProps`**: (Optional) Custom iframe props.
Rendered using the `<RemoteDomResource />` 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.

The HTML method is limited, and the external app method isn't secure enough for untrusted sites. We need a better method. We're exploring web components and remote-dom as alternatives that can allow the servers to render their components with the host's look-and-feel without local code execution.
* **`mimeType`**: `application/vnd.mcp-ui.remote-dom; flavor={react | webcomponents}`
* **Props**:
* **`resource`**: The `resource` object from an MCP message.
* **`library`**: A component library that maps remote element names (e.g., "button") to actual React or web components. `mcp-ui` provides a `basicComponentLibrary` for common HTML elements, and you can provide your own for custom components.
* **`onUiAction`**: A callback function to handle events.

### UI Action

Expand All @@ -97,18 +92,23 @@ yarn add @mcp-ui/server @mcp-ui/client

```ts
import { createHtmlResource } from '@mcp-ui/server';
import {
createRemoteComponent,
createRemoteDocument,
createRemoteText,
} from '@remote-dom/core';

// Inline HTML
const direct = createHtmlResource({
const htmlResource = createHtmlResource({
uri: 'ui://greeting/1',
content: { type: 'rawHtml', htmlString: '<p>Hello, MCP UI!</p>' },
delivery: 'text',
});

// External URL
const external = createHtmlResource({
uri: 'ui://widget/session-42',
content: { type: 'externalUrl', iframeUrl: 'https://example.com/widget' },
const externalUrlResource = createHtmlResource({
uri: 'ui://greeting/1',
content: { type: 'externalUrl', iframeUrl: 'https://example.com' },
delivery: 'text',
});
```
Expand All @@ -117,17 +117,16 @@ yarn add @mcp-ui/server @mcp-ui/client

```tsx
import React from 'react';
import { HtmlResource } from '@mcp-ui/client';
import { ResourceRenderer } from '@mcp-ui/client';

function App({ mcpResource }) {
if (
mcpResource.type === 'resource' &&
mcpResource.resource.uri?.startsWith('ui://')
) {
return (
<HtmlResource
<ResourceRenderer
resource={mcpResource.resource}
supportedContentTypes={['rawHtml']}
onUiAction={(result) => {
console.log('Action:', result);
return { status: 'ok' };
Expand All @@ -144,8 +143,9 @@ yarn add @mcp-ui/server @mcp-ui/client
## 🌍 Examples

**Client example**
* [ui-inspector](https://github.com/idosal/ui-inspector) - inspect local `mcp-ui`-enabled servers. Check out the [hosted version](https://scira-mcp-chat-git-main-idosals-projects.vercel.app/)!
* [MCP-UI Chat](https://github.com/idosal/scira-mcp-ui-chat) - interactive chat built with the `mcp-ui` client.
* [ui-inspector](https://github.com/idosal/ui-inspector) - inspect local `mcp-ui`-enabled servers.
* [MCP-UI Chat](https://github.com/idosal/scira-mcp-ui-chat) - interactive chat built with the `mcp-ui` client. Check out the [hosted version](https://scira-mcp-chat-git-main-idosals-projects.vercel.app/)!
* MCP-UI RemoteDOM Playground - local demo app to test RemoteDOM resources (intended for hosts)

**Server example**
Try out the hosted app -
Expand All @@ -161,9 +161,8 @@ Drop those URLs into any MCP-compatible host to see `mcp-ui` in action.

- [X] Add online playground
- [X] Expand UI Action API (beyond tool calls)
- [ ] Add
- [ ] Support Web Components (in progress)
- [ ] Support Remote-DOM (in progress)
- [X] Support Web Components
- [X] Support Remote-DOM
- [ ] Add component libraries (in progress)
- [ ] Support additional client-side libraries and render engines (e.g., Vue, TUI, etc.)

Expand Down
4 changes: 4 additions & 0 deletions docs/src/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export default defineConfig({
text: 'HtmlResource Component',
link: '/guide/client/html-resource',
},
{
text: 'RemoteDomResource Component',
link: '/guide/client/remote-dom-resource',
},
{ text: 'Usage & Examples', link: '/guide/client/usage-examples' },
// { text: 'API', link: '/guide/client/api' } // Placeholder
],
Expand Down
20 changes: 9 additions & 11 deletions docs/src/guide/client/overview.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
# @mcp-ui/client Overview

The `@mcp-ui/client` package helps you render HTML resource sent from an MCP-enabled server. For example, `HtmlResource` is a React component that handles the logic.
The `@mcp-ui/client` package helps you render UI resources sent from an MCP-enabled server. The primary component for this is `<ResourceRenderer />`, which automatically detects the resource type and renders the appropriate component for it.

## What’s Included?

- **`HtmlResource` (React Component)**:
The primary component for rendering an interactive HTML resource. It handles:
- Decoding Base64 blobs if necessary.
- Rendering content using `srcDoc` for the `text/html` mimeType.
- Rendering content using `src` for the `text/uri-list` mimeType.
- Setting up a `message` event listener to receive actions from `ui://` iframes.
- **(Other potential exports might include context providers or hooks if the client SDK grows more complex).**
- **`<ResourceRenderer />`**: The main component you'll use. It inspects the resource's `mimeType` and renders either `<HtmlResource />` or `<RemoteDomResource />`.
- **`<HtmlResource />`**: Renders traditional HTML content, either from a string or an external URL, inside a sandboxed `<iframe>`.
- **`<RemoteDomResource />`**: Renders a UI described by `remote-dom`, allowing for host-native components that match your application's look and feel.
- **Component Libraries**: A mapping of remote element names to your own React components, used by `<RemoteDomResource />`.

## Purpose

- **Simplified Rendering**: Abstract away the complexities of handling different URI schemes and content delivery methods (`text` vs. `blob`).
- **Security**: Encourages rendering user-provided HTML within sandboxed iframes.
- **Interactivity**: Provides a basic mechanism (`onUiAction` prop) for iframe content to communicate back to the host application.
- **Simplified Rendering**: Abstract away the complexities of handling different resource types.
- **Security**: Encourages rendering user-provided HTML and scripts within sandboxed iframes and web workers.
- **Interactivity**: Provides a unified mechanism (`onUiAction` prop) for UI resources to communicate back to the host application.

## Building

Expand All @@ -31,4 +28,5 @@ pnpm build --filter @mcp-ui/client
See the following pages for more details:

- [HtmlResource Component](./html-resource.md)
- [RemoteDomResource Component](./remote-dom-resource.md)
- [Client SDK Usage & Examples](./usage-examples.md)
67 changes: 67 additions & 0 deletions docs/src/guide/client/remote-dom-resource.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# RemoteDomResource Component

The `<RemoteDomResource />` component is used to render UI resources with the `application/vnd.mcp-ui.remote-dom` mime type. It leverages Shopify's [`remote-dom`](https://github.com/Shopify/remote-dom) library to securely render host-native components from a server-provided UI description.

This approach offers greater flexibility and security compared to `<iframe>`-based HTML resources, enabling UIs that seamlessly integrate with the host application's look and feel.

## How It Works

1. The MCP server sends a resource containing a script that builds a "remote" DOM structure.
2. The `@mcp-ui/client` securely executes this script in a sandboxed environment (a Web Worker inside an iframe).
3. As the remote DOM is manipulated, a series a JSON messages describing the changes are sent to the host window.
4. `<RemoteDomResource />` receives these messages and translates them into React component tree updates.

This ensures that no arbitrary code from the server runs in the main application thread, maintaining security while allowing dynamic and interactive UIs.

## Props

- **`resource`**: The `resource` object from an MCP message. The `mimeType` must be `application/vnd.mcp-ui.remote-dom+javascript; flavor={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.

## Component Libraries

A component library is a `Map` where keys are remote element tag names (as strings) and values are the corresponding React components that should render them.

`@mcp-ui/client` exports a `basicComponentLibrary` that maps standard HTML tags like `<p>`, `<div>`, `<h1>`, and `<button>` to simple React equivalents.

### Example: Custom Library

```tsx
import { MyButton, MyCard } from './MyComponents';

const customLibrary = new Map([
['fancy-button', MyButton],
['info-card', MyCard],
]);

<RemoteDomResource resource={resource} library={customLibrary} />
```

If the remote DOM contains `<fancy-button>`, it will be rendered using your `MyButton` component.

## Usage

The `<ResourceRenderer />` component automatically handles rendering `<RemoteDomResource />` when it detects the correct mime type, so you typically won't use this component directly.

```tsx
import React from 'react';
import { ResourceRenderer } from '@mcp-ui/client';

function App({ mcpResource }) {
if (
mcpResource.type === 'resource' &&
mcpResource.resource.uri?.startsWith('ui://')
) {
// ResourceRenderer will instantiate RemoteDomResource internally
// if the resource mimeType is 'application/vnd.mcp-ui.remote-dom'.
return (
<ResourceRenderer
resource={mcpResource.resource}
onUiAction={(action) => console.log('UI Action:', action)}
/>
);
}
return <p>Unsupported resource</p>;
}
```
24 changes: 24 additions & 0 deletions examples/client-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
54 changes: 54 additions & 0 deletions examples/client-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default tseslint.config({
extends: [
// Remove ...tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
],
languageOptions: {
// other options...
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
},
});
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x';
import reactDom from 'eslint-plugin-react-dom';

export default tseslint.config({
plugins: {
// Add the react-x and react-dom plugins
'react-x': reactX,
'react-dom': reactDom,
},
rules: {
// other rules...
// Enable its recommended typescript rules
...reactX.configs['recommended-typescript'].rules,
...reactDom.configs.recommended.rules,
},
});
```
28 changes: 28 additions & 0 deletions examples/client-demo/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import js from '@eslint/js';
import globals from 'globals';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
import tseslint from 'typescript-eslint';

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
);
13 changes: 13 additions & 0 deletions examples/client-demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading