diff --git a/Dockerfile.multi b/Dockerfile.multi index ecff4370e5..71253ef86e 100644 --- a/Dockerfile.multi +++ b/Dockerfile.multi @@ -114,6 +114,7 @@ COPY --from=data-provider-build /app/packages/data-provider/dist ./packages/data COPY --from=data-schemas-build /app/packages/data-schemas/dist ./packages/data-schemas/dist COPY --from=api-package-build /app/packages/api/dist ./packages/api/dist COPY --from=client-build /app/client/dist ./client/dist +COPY --from=client-build /app/client/dist/mcp-sandbox.html ./client/public/mcp-sandbox.html # Propagate build metadata into runtime env so /api/config can expose it. # Declared here (after the heavy install/copy steps) so that commit/date # changing on every CI run does not bust the cache for those layers. diff --git a/client/public/mcp-sandbox.html b/client/public/mcp-sandbox.html index 27ca95f9b0..3652cfcdbb 100644 --- a/client/public/mcp-sandbox.html +++ b/client/public/mcp-sandbox.html @@ -16,6 +16,7 @@ let innerFrame = null; let innerFrameBlobUrl = null; let trustedOrigin = null; + let readyInterval = null; const SANDBOX_PREFIX = 'ui/notifications/sandbox-'; function notifyReady() { @@ -23,6 +24,16 @@ { jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} }, '*' ); + if (!readyInterval) { + readyInterval = setInterval(() => { + if (!innerFrame) { + window.parent.postMessage( + { jsonrpc: '2.0', method: 'ui/notifications/sandbox-proxy-ready', params: {} }, + '*' + ); + } + }, 500); + } } window.addEventListener('message', (event) => { @@ -42,6 +53,8 @@ } if (msg.method === 'ui/notifications/sandbox-resource-ready') { + clearInterval(readyInterval); + readyInterval = null; createInnerFrame(msg.params); return; } @@ -140,7 +153,7 @@ ("font-src " + (resourceDomains || "'none'")).trim(), "frame-src " + frameDomains, "object-src 'none'", - "base-uri 'self'" + "base-uri " + (toDomainList(csp.baseUriDomains) || "'self'") ].join('; '); } diff --git a/client/src/components/Chat/Messages/Content/ToolCall.tsx b/client/src/components/Chat/Messages/Content/ToolCall.tsx index dc95a98722..ed9cdad36b 100644 --- a/client/src/components/Chat/Messages/Content/ToolCall.tsx +++ b/client/src/components/Chat/Messages/Content/ToolCall.tsx @@ -51,9 +51,14 @@ const MCPAppView = React.memo(function MCPAppView({ const toolResult = useMemo(() => { const sc = app.structuredContent as Record | undefined | null; - if (!sc || typeof sc !== 'object' || Array.isArray(sc)) return undefined; - return { content: [] as [], structuredContent: sc }; - }, [app.structuredContent]); + const content = (app.content as [] | undefined) ?? []; + if ((!sc || typeof sc !== 'object' || Array.isArray(sc)) && content.length === 0) + return undefined; + return { + content, + ...(sc && typeof sc === 'object' && !Array.isArray(sc) ? { structuredContent: sc } : {}), + }; + }, [app.structuredContent, app.content]); const handleSizeChanged = useCallback((params: { height?: number; width?: number }) => { if (params.height && params.height > 0) { diff --git a/client/src/components/Chat/Messages/Content/UIResourceCarousel.tsx b/client/src/components/Chat/Messages/Content/UIResourceCarousel.tsx index 2c168888c3..2236a65540 100644 --- a/client/src/components/Chat/Messages/Content/UIResourceCarousel.tsx +++ b/client/src/components/Chat/Messages/Content/UIResourceCarousel.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import type { UIResource } from 'librechat-data-provider'; import { getMCPSandboxUrl } from '~/utils/mcpApps'; import { useAppBridge } from '~/hooks/MCP'; +import { useLocalize } from '~/hooks'; interface UIResourceCarouselProps { uiResources: UIResource[]; @@ -9,14 +10,20 @@ interface UIResourceCarouselProps { function MCPAppCard({ resource }: { resource: UIResource }) { const iframeRef = React.useRef(null); + const localize = useLocalize(); const [loaded, setLoaded] = useState(false); const sandboxUrl = React.useMemo(() => getMCPSandboxUrl(), []); const toolResult = React.useMemo(() => { const sc = resource.structuredContent as Record | undefined | null; - if (!sc || typeof sc !== 'object' || Array.isArray(sc)) return undefined; - return { content: [] as [], structuredContent: sc }; - }, [resource.structuredContent]); + const content = (resource.content as [] | undefined) ?? []; + if ((!sc || typeof sc !== 'object' || Array.isArray(sc)) && content.length === 0) + return undefined; + return { + content, + ...(sc && typeof sc === 'object' && !Array.isArray(sc) ? { structuredContent: sc } : {}), + }; + }, [resource.structuredContent, resource.content]); const handleSizeChanged = React.useCallback((params: { height?: number; width?: number }) => { if (params.height && params.height > 0) { @@ -31,7 +38,7 @@ function MCPAppCard({ resource }: { resource: UIResource }) { <> {!loaded && (
- Loading interactive view... + {localize('com_ui_loading_interactive_view')}
)}