diff --git a/README.md b/README.md index 96e5a14b..28d07c6a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ **`mcp-ui`** brings interactive web components to the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). Deliver rich, dynamic UI resources directly from your MCP server to be rendered by the client. Take AI interaction to the next level! -> *This project is an experimental playground for MCP UI ideas. Expect rapid iteration and community-driven enhancements!* +> *This project is an experimental community playground for MCP UI ideas. Expect rapid iteration and enhancements!* @@ -33,6 +33,11 @@ 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. +**North star** - +* Enable servers to deliver rich, interactive UIs with ergonomic APIs +* Allow any host to support UI with its own look-and-feel +* Eliminate security concerns (limit/remove local code execution) + ## ✨ Core Concepts @@ -44,7 +49,7 @@ The primary payload exchanged between the server and the client: interface HtmlResourceBlock { type: 'resource'; resource: { - uri: string; // ui://component/id" + 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 @@ -59,8 +64,15 @@ interface HtmlResourceBlock { * **`text` vs. `blob`**: Choose `text` for simple strings; use `blob` for larger or encoded content. It's rendered in the client with the `` React component. +The component accepts the following props: + +* **`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. -The HTML method is limited, and the external app method isn't secure enough for untrusted 3rd party sites. We need a better method. Some ideas we should explore: RSC, remotedom, etc. +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. ### UI Action @@ -115,6 +127,7 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { console.log('Action:', result); return { status: 'ok' }; @@ -147,11 +160,12 @@ Drop those URLs into any MCP-compatible host to see `mcp-ui` in action. ## 🛣️ Roadmap - [X] Add online playground -- [ ] Support React Server Components -- [ ] Support Remote-DOM -- [ ] Support additional client-side libraries (e.g., Vue) -- [ ] Expand UI Action API (beyond tool calls) -- [ ] Do more with Resources and Sampling +- [X] Expand UI Action API (beyond tool calls) +- [ ] Add +- [ ] Support Web Components (in progress) +- [ ] Support Remote-DOM (in progress) +- [ ] Add component libraries (in progress) +- [ ] Support additional client-side libraries and render engines (e.g., Vue, TUI, etc.) ## 🤝 Contributing diff --git a/docs/src/guide/client/html-resource.md b/docs/src/guide/client/html-resource.md index 3dee89ed..de0019d7 100644 --- a/docs/src/guide/client/html-resource.md +++ b/docs/src/guide/client/html-resource.md @@ -14,6 +14,8 @@ export interface HtmlResourceProps { } ``` +The component accepts the following props: + - **`resource`**: The resource object from an `HtmlResourceBlock`. It should include `uri`, `mimeType`, and either `text` or `blob`. - **`onUiAction`**: An optional callback that fires when the iframe content (for `ui://` resources) posts a message to your app. The message should look like: ```typescript @@ -24,7 +26,9 @@ export interface HtmlResourceProps { { type: 'link', payload: { url: string } } | ``` If you don't provide a callback for a specific type, the default handler will be used. -- **`style`** (optional): Custom styles for the iframe. +- **`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 props for the iframe. ## How It Works @@ -101,6 +105,6 @@ The `HtmlResource` component maintains backwards compatibility with the legacy ` ## Security Notes -- **`sandbox` attribute**: Restricts what the iframe can do. `allow-scripts` is needed for interactivity. `allow-same-origin` is external apps. Caution - the external app method isn's not a secure way to render untrusted code. We're working on new methods to alleviate security concerns. +- **`sandbox` attribute**: Restricts what the iframe can do. `allow-scripts` is needed for interactivity. `allow-same-origin` is external apps. Caution - the external app method isn't a secure way to render untrusted code. We're working on new methods to alleviate security concerns. - **`postMessage` origin**: When sending messages from the iframe, always specify the target origin for safety. The component listens globally, so your iframe content should be explicit. - **Content Sanitization**: HTML is rendered as-is. If you don't fully trust the source, sanitize the HTML before passing it in, or rely on the iframe's sandboxing. diff --git a/examples/server/README.md b/examples/server/README.md index 68adfc94..b1d167fc 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -20,7 +20,7 @@ **`mcp-ui`** brings interactive web components to the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). Deliver rich, dynamic UI resources directly from your MCP server to be rendered by the client. Take AI interaction to the next level! -> *This project is an experimental playground for MCP UI ideas. Expect rapid iteration and community-driven enhancements!* +> *This project is an experimental community playground for MCP UI ideas. Expect rapid iteration and enhancements!* @@ -33,6 +33,11 @@ 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. +**North star** - +* Enable servers to deliver rich, interactive UIs with ergonomic APIs +* Allow any host to support UI with its own look-and-feel +* Eliminate security concerns (limit/remove local code execution) + ## ✨ Core Concepts @@ -44,7 +49,7 @@ The primary payload exchanged between the server and the client: interface HtmlResourceBlock { type: 'resource'; resource: { - uri: string; // e.g. "ui://component/id" + 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 @@ -59,8 +64,15 @@ interface HtmlResourceBlock { * **`text` vs. `blob`**: Choose `text` for simple strings; use `blob` for larger or encoded content. It's rendered in the client with the `` React component. +The component accepts the following props: + +* **`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. -The HTML method is limited, and the external app method isn't secure enough for untrusted 3rd party sites. We need a better method. Some ideas we should explore: RSC, remotedom, etc. +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. ### UI Action @@ -115,6 +127,7 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { console.log('Action:', result); return { status: 'ok' }; diff --git a/packages/client/README.md b/packages/client/README.md index 98d2c9c0..28d07c6a 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -20,7 +20,7 @@ **`mcp-ui`** brings interactive web components to the [Model Context Protocol](https://modelcontextprotocol.io/introduction) (MCP). Deliver rich, dynamic UI resources directly from your MCP server to be rendered by the client. Take AI interaction to the next level! -> *This project is an experimental playground for MCP UI ideas. Expect rapid iteration and community-driven enhancements!* +> *This project is an experimental community playground for MCP UI ideas. Expect rapid iteration and enhancements!* @@ -33,6 +33,11 @@ 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. +**North star** - +* Enable servers to deliver rich, interactive UIs with ergonomic APIs +* Allow any host to support UI with its own look-and-feel +* Eliminate security concerns (limit/remove local code execution) + ## ✨ Core Concepts @@ -44,10 +49,10 @@ The primary payload exchanged between the server and the client: interface HtmlResourceBlock { type: 'resource'; resource: { - uri: string; // e.g. "ui://component/id" + 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 (for large payloads) + blob?: string; // Base64-encoded HTML or URL }; } ``` @@ -59,8 +64,15 @@ interface HtmlResourceBlock { * **`text` vs. `blob`**: Choose `text` for simple strings; use `blob` for larger or encoded content. It's rendered in the client with the `` React component. +The component accepts the following props: + +* **`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. -The HTML method is limited, and the external app method isn't secure enough for untrusted 3rd party sites. We need a better method. Some ideas we should explore: RSC, remotedom, etc. +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. ### UI Action @@ -115,6 +127,7 @@ yarn add @mcp-ui/server @mcp-ui/client return ( { console.log('Action:', result); return { status: 'ok' }; @@ -147,11 +160,12 @@ Drop those URLs into any MCP-compatible host to see `mcp-ui` in action. ## 🛣️ Roadmap - [X] Add online playground -- [ ] Support React Server Components -- [ ] Support Remote-DOM -- [ ] Support additional client-side libraries (e.g., Vue) -- [ ] Expand UI Action API (beyond tool calls) -- [ ] Do more with Resources and Sampling +- [X] Expand UI Action API (beyond tool calls) +- [ ] Add +- [ ] Support Web Components (in progress) +- [ ] Support Remote-DOM (in progress) +- [ ] Add component libraries (in progress) +- [ ] Support additional client-side libraries and render engines (e.g., Vue, TUI, etc.) ## 🤝 Contributing diff --git a/packages/client/src/components/HtmlResource.tsx b/packages/client/src/components/HtmlResource.tsx index 3f1b6985..dc67c484 100644 --- a/packages/client/src/components/HtmlResource.tsx +++ b/packages/client/src/components/HtmlResource.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useImperativeHandle, useMemo, useRef } from 'react'; import type { Resource } from '@modelcontextprotocol/sdk/types.js'; -import { UiActionResult } from '../types'; +import { UiActionResult, ResourceContentType } from '../types'; import { processResource } from '../utils/processResource'; export type RenderHtmlResourceProps = { @@ -11,84 +11,94 @@ export type RenderHtmlResourceProps = { React.HTMLAttributes, 'src' | 'srcDoc' | 'ref' | 'style' >; + supportedContentTypes?: ResourceContentType[]; }; export const HtmlResource = React.forwardRef< HTMLIFrameElement | null, RenderHtmlResourceProps ->(({ resource, onUiAction, style, iframeProps }, ref) => { - const iframeRef = useRef(null); - useImperativeHandle(ref, () => iframeRef.current as HTMLIFrameElement); +>( + ( + { resource, onUiAction, style, iframeProps, supportedContentTypes }, + ref, + ) => { + const iframeRef = useRef(null); + useImperativeHandle(ref, () => iframeRef.current as HTMLIFrameElement); - const { error, iframeSrc, iframeRenderMode, htmlString } = useMemo( - () => processResource(resource), - [resource], - ); + const { error, iframeSrc, iframeRenderMode, htmlString } = useMemo( + () => processResource(resource, supportedContentTypes), + [resource, supportedContentTypes], + ); - useEffect(() => { - function handleMessage(event: MessageEvent) { - // Only process the message if it came from this specific iframe - if ( - iframeRef.current && - event.source === iframeRef.current.contentWindow - ) { - const uiActionResult = event.data as UiActionResult; - if (!uiActionResult) { - return; + useEffect(() => { + function handleMessage(event: MessageEvent) { + // Only process the message if it came from this specific iframe + if ( + iframeRef.current && + event.source === iframeRef.current.contentWindow + ) { + const uiActionResult = event.data as UiActionResult; + if (!uiActionResult) { + return; + } + onUiAction?.(uiActionResult)?.catch((err) => { + console.error( + 'Error handling UI action result in RenderHtmlResource:', + err, + ); + }); } - onUiAction?.(uiActionResult)?.catch((err) => { - console.error( - 'Error handling UI action result in RenderHtmlResource:', - err, - ); - }); } - } - window.addEventListener('message', handleMessage); - return () => window.removeEventListener('message', handleMessage); - }, [onUiAction]); + window.addEventListener('message', handleMessage); + return () => window.removeEventListener('message', handleMessage); + }, [onUiAction]); - if (error) return

{error}

; + if (error) return

{error}

; - if (iframeRenderMode === 'srcDoc') { - if (htmlString === null || htmlString === undefined) { - if (!error) { - return

No HTML content to display.

; + if (iframeRenderMode === 'srcDoc') { + if (htmlString === null || htmlString === undefined) { + if (!error) { + return

No HTML content to display.

; + } + return null; } - return null; - } - return ( -