import { UIResourceRenderer } from '@mcp-ui/client'; import type { UIResourcePart } from '@dexto/core'; import { AlertTriangle } from 'lucide-react'; interface UIResourceRendererWrapperProps { resource: UIResourcePart; /** Callback when the UI resource triggers an action */ onAction?: (action: { type: string; payload?: unknown }) => void; } /** * Wrapper component that adapts Dexto's UIResourcePart to @mcp-ui/client's UIResourceRenderer. * Renders interactive MCP-UI resources (HTML, external URLs, Remote DOM) in sandboxed iframes. */ export function UIResourceRendererWrapper({ resource, onAction }: UIResourceRendererWrapperProps) { // Map UIResourcePart to the format expected by @mcp-ui/client // MCP SDK uses discriminated unions - either text OR blob, not both // Store metadata in _meta since annotations has a specific schema in MCP SDK const mcpResource = resource.blob ? { type: 'resource' as const, resource: { uri: resource.uri, blob: resource.blob, ...(resource.mimeType ? { mimeType: resource.mimeType } : {}), _meta: { ...(resource.metadata?.title ? { title: resource.metadata.title } : {}), ...(resource.metadata?.preferredSize ? { preferredSize: resource.metadata.preferredSize } : {}), }, }, } : { type: 'resource' as const, resource: { uri: resource.uri, text: resource.content || '', ...(resource.mimeType ? { mimeType: resource.mimeType } : {}), _meta: { ...(resource.metadata?.title ? { title: resource.metadata.title } : {}), ...(resource.metadata?.preferredSize ? { preferredSize: resource.metadata.preferredSize } : {}), }, }, }; // Handle UI actions from the rendered component const handleUIAction = async (result: { type: string; payload?: unknown }) => { if (onAction) { onAction(result); } // Return undefined to acknowledge the action return undefined; }; return (