experimental_MCPAppRenderer
Warning
experimental_MCPAppRenderer is experimental and may change in a future
release.
experimental_MCPAppRenderer renders an MCP App for an AI SDK tool UI part. It detects MCP App metadata on the tool part, loads the app resource, renders the app in a sandbox proxy iframe, and bridges MCP Apps JSON-RPC messages between the iframe and your host application.
For tool parts without MCP App metadata, the component renders the fallback.
Import
import { experimental_MCPAppRenderer as MCPAppRenderer } from "@ai-sdk/react"Example
'use client';
import {
experimental_MCPAppRenderer as MCPAppRenderer,
type MCPAppBridgeHandlers,
type MCPAppMetadata,
type MCPAppResource,
type MCPAppSandboxConfig,
} from '@ai-sdk/react';
import { isToolUIPart } from 'ai';
const sandbox = {
url: '/mcp-app-sandbox',
className: 'h-80 w-full rounded-lg border',
style: { border: 0 },
} satisfies MCPAppSandboxConfig;
async function loadResource(app: MCPAppMetadata): Promise<MCPAppResource> {
const response = await fetch('/api/mcp-app-host/read-resource', {
method: 'POST',
body: JSON.stringify({ uri: app.resourceUri }),
});
if (!response.ok) {
throw new Error('Failed to load MCP App resource');
}
return response.json();
}
const handlers: MCPAppBridgeHandlers = {
callTool: params =>
fetch('/api/mcp-app-host/call-tool', {
method: 'POST',
body: JSON.stringify(params),
}).then(response => response.json()),
openLink: ({ url }) => {
window.open(url, '_blank', 'noopener,noreferrer');
return {};
},
};
export function MessagePart({ part }: { part: unknown }) {
if (!isToolUIPart(part)) {
return null;
}
return (
<MCPAppRenderer
part={part}
loadResource={loadResource}
handlers={handlers}
sandbox={sandbox}
fallback={null}
/>
);
}Props
part(ToolUIPart<UITools> | DynamicToolUIPart): The AI SDK tool UI part. The renderer looks for MCP App metadata inpart.toolMetadata.mcp.app.sandbox(MCPAppSandboxConfig): Configuration for the outer sandbox proxy iframe used to host the app.resource?(MCPAppResource): A preloaded MCP App resource. Provide eitherresourceorloadResource.loadResource?((app: MCPAppMetadata) => Promise<MCPAppResource>): Loads the MCP App resource for the app metadata found on the tool part.handlers?(MCPAppBridgeHandlers): Callbacks used to handle iframe requests such astools/call,resources/read,ui/open-link, and display mode changes.hostInfo?({ name: string; version: string }): Host identity returned to the app during theui/initializehandshake.hostContext?(MCPAppHostContext): Host context sent to the app, such as theme, display mode, and available display modes.fallback?(ReactNode): Rendered while the resource is loading, when loading fails, or when the tool part is not an MCP App.
Sandbox Config
url(string | URL): The URL of the sandbox proxy iframe. The proxy receives app HTML from the host and creates the inner app iframe.title?(string): Accessible iframe title.className?(string): Class name applied to the outer iframe.style?(CSSProperties): Inline styles applied to the outer iframe.targetOrigin?(string): Target origin used forpostMessage. Defaults to*; set this to your sandbox origin in production.outerSandbox?(string): Sandbox attribute for the outer proxy iframe. Defaults toallow-scripts allow-same-origin allow-forms.innerSandbox?(string): Sandbox attribute sent to the proxy for the inner app iframe. Defaults toallow-scripts allow-forms.
Bridge Handlers
experimental_MCPAppRenderer uses these handlers to respond to iframe requests. In production, server-backed handlers should validate authorization and MCP Apps tool visibility before calling the MCP server.
allowedTools?(string[]): Optional client-side allowlist checked before forwardingtools/callrequests.callTool?((params: MCPAppToolCallParams) => Promise<unknown> | unknown): Handles app-initiatedtools/callrequests.readResource?((params: { uri: string }) => Promise<unknown> | unknown): Handles app-initiatedresources/readrequests.listResources?((params?: unknown) => Promise<unknown> | unknown): Handles app-initiatedresources/listrequests.openLink?((params: { url: string }) => Promise<unknown> | unknown): Handles app-initiatedui/open-linkrequests.sendMessage?((params: unknown) => Promise<unknown> | unknown): Handles app-initiatedui/messagerequests.updateModelContext?((params: unknown) => Promise<unknown> | unknown): Handles app-initiatedui/update-model-contextrequests.requestDisplayMode?((params: { mode: 'inline' | 'fullscreen' | 'pip' }) => Promise<{ mode: MCPAppDisplayMode }> | { mode: MCPAppDisplayMode }): Handles app-initiatedui/request-display-moderequests.onSizeChange?((params: { width?: number; height?: number }) => void): Called when the app sends a size change notification.onInitialized?(() => void): Called after the app sendsui/notifications/initialized.onRequestTeardown?((params: unknown) => void): Called when the app requests teardown.onLog?((params: unknown) => void): Called when the app sends a log notification.onError?((error: Error) => void): Called when a supported iframe request fails while being handled.