From 45e2521f8110b5472506ed57d4e998ffeb1f5f0e Mon Sep 17 00:00:00 2001 From: Liad Yosef Date: Wed, 13 Aug 2025 22:05:57 +0300 Subject: [PATCH 1/3] feat: use blob src as default --- docs/src/guide/client/html-resource.md | 2 + docs/src/guide/client/resource-renderer.md | 15 ++++++ .../src/components/HTMLResourceRenderer.tsx | 17 ++++++- .../__tests__/HTMLResourceRenderer.test.tsx | 47 ++++++++++++++++++- 4 files changed, 78 insertions(+), 3 deletions(-) diff --git a/docs/src/guide/client/html-resource.md b/docs/src/guide/client/html-resource.md index 10ddef44..c7ba316d 100644 --- a/docs/src/guide/client/html-resource.md +++ b/docs/src/guide/client/html-resource.md @@ -14,6 +14,7 @@ export interface HTMLResourceRendererProps { proxy?: string; iframeRenderData?: Record; autoResizeIframe?: boolean | { width?: boolean; height?: boolean }; + preferSrcDocOverBlob?: boolean; iframeProps?: Omit, 'src' | 'srcDoc' | 'ref' | 'style'>; } ``` @@ -40,6 +41,7 @@ The component accepts the following props: - **`proxy`**: (Optional) A URL to a proxy script. This is useful for hosts with a strict Content Security Policy (CSP). When provided, external URLs will be rendered in a nested iframe hosted at this URL. For example, if `proxy` is `https://my-proxy.com/`, the final URL will be `https://my-proxy.com/?url=`. For your convenience, mcp-ui hosts a proxy script at `https://proxy.mcpui.dev`, which you can use as a the prop value without any setup (see `examples/external-url-demo`). - **`iframeProps`**: (Optional) Custom props for the iframe. - **`autoResizeIframe`**: (Optional) When enabled, the iframe will automatically resize based on messages from the iframe's content. This prop can be a boolean (to enable both width and height resizing) or an object (`{width?: boolean, height?: boolean}`) to control dimensions independently. +- **`preferSrcDocOverBlob`**: (Optional) Defaults to `false`. When `false` (default), HTML content is rendered using a blob URL set as the iframe's `src` attribute. When `true`, HTML content is rendered using the `srcDoc` attribute with the raw HTML string. Setting this to `true` can be useful in environments with strict Content Security Policies that block blob URLs. ## How It Works diff --git a/docs/src/guide/client/resource-renderer.md b/docs/src/guide/client/resource-renderer.md index 327119eb..e7380282 100644 --- a/docs/src/guide/client/resource-renderer.md +++ b/docs/src/guide/client/resource-renderer.md @@ -63,6 +63,7 @@ interface UIResourceRendererProps { - **`ref`**: Optional React ref to access the underlying iframe element - **`iframeRenderData`**: Optional `Record` 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`**: Optional `boolean | { width?: boolean; height?: boolean }` to automatically resize the iframe to the size of the content. + - **`preferSrcDocOverBlob`**: Optional `boolean` (defaults to `false`). When `false` (default), HTML content is rendered using a blob URL set as the iframe's `src` attribute. When `true`, HTML content is rendered using the `srcDoc` attribute with the raw HTML string. Useful in environments with strict Content Security Policies that block blob URLs. - **`remoteDomProps`**: Optional props for the `` - **`library`**: Optional component library for Remote DOM resources (defaults to `basicComponentLibrary`) - **`remoteElements`**: Optional remote element definitions for Remote DOM resources. REQUIRED for Remote DOM snippets. @@ -151,6 +152,20 @@ function App({ mcpResource }) { /> ``` +### Using srcDoc Instead of Blob URLs + +By default, HTML content is rendered using blob URLs for better security. If you need to use `srcDoc` instead (e.g., for environments with strict CSP policies that block blob URLs): + +```tsx + +``` + ### 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. diff --git a/sdks/typescript/client/src/components/HTMLResourceRenderer.tsx b/sdks/typescript/client/src/components/HTMLResourceRenderer.tsx index c6d2460d..3544b2dc 100644 --- a/sdks/typescript/client/src/components/HTMLResourceRenderer.tsx +++ b/sdks/typescript/client/src/components/HTMLResourceRenderer.tsx @@ -10,6 +10,7 @@ export type HTMLResourceRendererProps = { proxy?: string; iframeRenderData?: Record; autoResizeIframe?: boolean | { width?: boolean; height?: boolean }; + preferSrcDocOverBlob?: boolean; iframeProps?: Omit, 'src' | 'srcDoc' | 'style'> & { ref?: React.RefObject; }; @@ -36,6 +37,7 @@ export const HTMLResourceRenderer = ({ proxy, iframeRenderData, autoResizeIframe, + preferSrcDocOverBlob, iframeProps, }: HTMLResourceRendererProps) => { const iframeRef = useRef(null); @@ -149,9 +151,22 @@ export const HTMLResourceRenderer = ({ } return null; } + let iframeSrcProp: { + srcDoc?: string; + } | { + src?: string; + } = { + srcDoc: htmlString, + } + if (!preferSrcDocOverBlob && typeof URL.createObjectURL === 'function') { + const blob = new Blob([htmlString], { type: 'text/html' }); + iframeSrcProp = { + src: URL.createObjectURL(blob), + }; + } return (