-
Notifications
You must be signed in to change notification settings - Fork 151
Add MCP Apps Support #976
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add MCP Apps Support #976
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds SEP-1865 MCP Apps support across client and server. Client: new MCPAppsRenderer and SandboxedIframe components, detectUIType/isMCPApps utilities, and updates to ResultsPanel, ToolsTab, thread, ServerConnectionCard, and ServerInfoModal to detect and render MCP Apps alongside OpenAI components. Server: new MCP Apps routes to store widget state, serve widget HTML with an injected host script, and expose a sandbox-proxy endpoint plus static proxy HTML. Build tooling: post-build and Vite plugins/tsup hooks copy sandbox-proxy.html into distribution. Public API signatures are unchanged. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
🧹 Nitpick comments (11)
client/src/components/ui/sandboxed-iframe.tsx (1)
74-83: WildcardtargetOriginis intentional but merits a comment.The
"*"targetOrigin inpostMessageis necessary for cross-origin sandbox communication, yet future maintainers may flag it as a vulnerability. A brief inline comment clarifying this architectural decision would forestall confusion.useImperativeHandle( ref, () => ({ postMessage: (data: unknown) => { + // Wildcard origin required: proxy runs on different origin per SEP-1865 outerRef.current?.contentWindow?.postMessage(data, "*"); }, }), [], );client/src/lib/mcp-apps-utils.ts (1)
40-57: Consider narrowing theas anycast.The cast on line 43 bypasses type safety. If
isUIResourceexpects a specific shape, define a minimal interface to preserve some compile-time checks:- if (isUIResource(item as any)) { + if (isUIResource(item as { type?: string; resource?: unknown })) {Alternatively, verify
@mcp-ui/clientexports the expected parameter type and import it.server/routes/mcp/apps.ts (2)
55-96: Input validation is minimal.Required fields are checked, but
resourceUriandserverIdaren't validated for format (e.g., ensuringresourceUristarts withui://). Malformed URIs could cause downstream errors.if (!serverId || !resourceUri || !toolId || !toolName) { return c.json({ success: false, error: "Missing required fields" }, 400); } + + if (typeof resourceUri !== "string" || !resourceUri.startsWith("ui://")) { + return c.json({ success: false, error: "Invalid resourceUri format" }, 400); + }
152-159: HTML injection assumes well-formed markup.Case-insensitive matching (
<HEAD>,<HTML>) and<!DOCTYPE>aren't handled. If the resource HTML uses uppercase tags or a doctype, the script may be prepended incorrectly.- if (html.includes("<head>")) { - html = html.replace("<head>", `<head>${mcpAppsScript}`); - } else if (html.includes("<html>")) { - html = html.replace("<html>", `<html><head>${mcpAppsScript}</head>`); + const headMatch = html.match(/<head\b[^>]*>/i); + const htmlMatch = html.match(/<html\b[^>]*>/i); + if (headMatch) { + html = html.replace(headMatch[0], `${headMatch[0]}${mcpAppsScript}`); + } else if (htmlMatch) { + html = html.replace(htmlMatch[0], `${htmlMatch[0]}<head>${mcpAppsScript}</head>`);client/src/components/chat-v2/thread.tsx (1)
246-266: Redundant metadata check afterdetectUIType.Line 253 re-checks
partToolMeta?.["ui/resourceUri"]despitedetectUITypealready verifying this for the"mcp-apps"return value. Simplify:- if (uiType === "mcp-apps" && partToolMeta?.["ui/resourceUri"]) { + if (uiType === "mcp-apps") {server/routes/mcp/sandbox-proxy.html (1)
56-77: Consider origin validation for enhanced security posture.While
postMessage(event.data, '*')is typical for sandbox proxies, the code forwards messages without validating the incoming data structure. Malformed or malicious payloads pass through unchecked. Consider adding minimal validation before relay:} else if (event.source === inner.contentWindow) { - // Relay messages from inner (guest UI) to parent (host) - window.parent.postMessage(event.data, '*'); + // Relay messages from inner (guest UI) to parent (host) + // Basic structure check before forwarding + if (event.data && typeof event.data === 'object') { + window.parent.postMessage(event.data, '*'); + } }docs/contributing/mcp-apps-implementation.md (2)
97-102: Add language specifier to code block.Per markdownlint, fenced code blocks should declare their language for proper syntax highlighting and tooling support.
-``` +```text 1. MCP Apps → _meta["ui/resourceUri"] → MCPAppsRenderer 2. OpenAI SDK → _meta["openai/outputTemplate"] → OpenAIAppRenderer (existing)
1362-1384: Add language specifier to migration diagram.-``` +```text Current State ├── OpenAIAppRenderer (openai:* messages, single iframe)client/src/components/tools/ResultsPanel.tsx (1)
248-267: Consider typing the tool response more precisely.The
result as anycast on line 259 obscures the actual structure. A narrower type would improve maintainability:if (toolResponse.status === "completed") { - const result = toolResponse.result as any; + const result = toolResponse.result as { content?: unknown[]; structuredContent?: unknown } | undefined; return { - content: result?.content || [], - structuredContent: result?.structuredContent || result, + content: result?.content ?? [], + structuredContent: result?.structuredContent ?? result, }; }client/src/components/chat-v2/mcp-apps-renderer.tsx (2)
56-68: Unused props in destructuring.
toolMetadata,onWidgetStateChange, andonRequestPipare declared inMCPAppsRendererPropsbut not destructured or used. Either remove from props interface if unneeded, or implement their functionality:export function MCPAppsRenderer({ serverId, toolCallId, toolName, toolState, toolInput, toolOutput, resourceUri, + toolMetadata, onSendFollowUp, onCallTool, + onWidgetStateChange, pipWidgetId, + onRequestPip, onExitPip, }: MCPAppsRendererProps) {As per the coding guidelines requiring strict TypeScript types, unused interface members create API surface that may mislead consumers.
79-87: Pending requests lack cleanup on unmount.The
pendingRequestsMap accumulates unresolved promises if the component unmounts while awaiting responses. Consider clearing and rejecting pending requests:+ useEffect(() => { + return () => { + // Reject all pending requests on unmount + pendingRequests.current.forEach(({ reject }) => { + reject(new Error("Component unmounted")); + }); + pendingRequests.current.clear(); + }; + }, []);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
client/src/components/ToolsTab.tsx(1 hunks)client/src/components/chat-v2/mcp-apps-renderer.tsx(1 hunks)client/src/components/chat-v2/thread.tsx(2 hunks)client/src/components/tools/ResultsPanel.tsx(4 hunks)client/src/components/ui/sandboxed-iframe.tsx(1 hunks)client/src/lib/mcp-apps-utils.ts(1 hunks)docs/contributing/mcp-apps-implementation.md(1 hunks)server/routes/mcp/apps.ts(1 hunks)server/routes/mcp/index.ts(3 hunks)server/routes/mcp/sandbox-proxy.html(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
client/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
client/**/components/**/*.{ts,tsx}: Use functional React components with React.FC typing
Implement strict TypeScript types for React component props with Props interface definitions
Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Design responsive layouts using Tailwind breakpoint system, grid layouts, flex containers, and container queries
Ensure accessibility compliance with ARIA attributes, keyboard navigation, focus management, and screen reader support
Files:
client/src/components/ToolsTab.tsxclient/src/components/ui/sandboxed-iframe.tsxclient/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsxclient/src/components/chat-v2/thread.tsx
client/**/{components,hooks}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Files:
client/src/components/ToolsTab.tsxclient/src/components/ui/sandboxed-iframe.tsxclient/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsxclient/src/components/chat-v2/thread.tsx
server/**/{app,index,routes/**}.ts
📄 CodeRabbit inference engine (server/CLAUDE.md)
Use Hono.js for API routing and middleware in the backend
Files:
server/routes/mcp/index.ts
server/**/*.ts
📄 CodeRabbit inference engine (server/CLAUDE.md)
Implement TypeScript for type safety throughout the codebase
Files:
server/routes/mcp/index.tsserver/routes/mcp/apps.ts
🧠 Learnings (18)
📓 Common learnings
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/tools/**/*.ts : Validate tool schemas according to MCP spec including parameters and response formats
Applied to files:
client/src/components/ToolsTab.tsxserver/routes/mcp/index.tsclient/src/lib/mcp-apps-utils.tsclient/src/components/tools/ResultsPanel.tsxserver/routes/mcp/apps.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Applied to files:
docs/contributing/mcp-apps-implementation.mdserver/routes/mcp/index.tsclient/src/lib/mcp-apps-utils.tsclient/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsxclient/src/components/chat-v2/thread.tsxserver/routes/mcp/apps.ts
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: MCP server testing should include full spec validation covering tool schemas, resources, prompts, and response formats
Applied to files:
docs/contributing/mcp-apps-implementation.md
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/types/mcp.ts : Define MCP protocol types in TypeScript type definition files
Applied to files:
server/routes/mcp/index.tsclient/src/lib/mcp-apps-utils.tsclient/src/components/chat-v2/mcp-apps-renderer.tsxclient/src/components/chat-v2/thread.tsxserver/routes/mcp/apps.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
Applied to files:
server/routes/mcp/index.tsclient/src/components/chat-v2/thread.tsxserver/routes/mcp/apps.ts
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/{app,index,routes/**}.ts : Use Hono.js for API routing and middleware in the backend
Applied to files:
server/routes/mcp/index.tsserver/routes/mcp/apps.ts
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/routes/llm/openai/**/*.ts : Implement OpenAI API client setup with model management, stream processing, and error handling
Applied to files:
server/routes/mcp/index.ts
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/utils/validation.ts : Use schema validation for MCP protocol compliance in validation utility files
Applied to files:
client/src/lib/mcp-apps-utils.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Applied to files:
client/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/chat/**/*.{ts,tsx} : Implement message threading with thread components, collapse/expand functionality, navigation, and search capabilities
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/chat/**/*.{ts,tsx} : Build chat interface with message components, input handling, stream rendering, and history management
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/chat/**/*.{ts,tsx} : Implement code highlighting with syntax detection, theme support, copy functionality, and line numbers in code components
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{stores/chat,hooks/llm}/**/*.{ts,tsx} : Manage context with window size tracking, token counting, context pruning, and state persistence for chat sessions
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/chat/**/*.{ts,tsx} : Implement AI model state handling including model selection state, generation parameters, stream management, and history persistence
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{hooks/llm,components/chat}/**/*.{ts,tsx} : Implement response streaming with token processing, UI updates, cancel handling, and error states
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/resources/**/*.ts : Implement resource schema compliance checking and type validation in resource handlers
Applied to files:
server/routes/mcp/apps.ts
🧬 Code graph analysis (2)
client/src/components/tools/ResultsPanel.tsx (2)
client/src/components/ui/button.tsx (1)
Button(59-59)client/src/components/chat-v2/mcp-apps-renderer.tsx (1)
MCPAppsRenderer(56-412)
client/src/components/chat-v2/thread.tsx (3)
client/src/lib/mcp-apps-utils.ts (1)
detectUIType(24-61)client/src/lib/mcp-tools-api.ts (2)
getToolServerId(116-121)callTool(71-89)client/src/components/chat-v2/mcp-apps-renderer.tsx (1)
MCPAppsRenderer(56-412)
🪛 markdownlint-cli2 (0.18.1)
docs/contributing/mcp-apps-implementation.md
97-97: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
108-108: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
1136-1136: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
1362-1362: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🔍 Remote MCP
Summary of Additional Context for PR #976: MCP Apps Support
SEP-1865 Specification Overview
SEP-1865 proposes an extension to MCP that enables servers to deliver interactive user interfaces to hosts. MCP Apps introduces a standardized pattern for declaring UI resources via the ui:// URI scheme, associating them with tools through metadata, and facilitating bi-directional communication between the UI and the host using MCP's JSON-RPC base protocol. The SEP was authored by MCP Core Maintainers at OpenAI and Anthropic, together with the MCP-UI creators and lead maintainers of the MCP UI Community Working Group.
Security Architecture
Hosting interactive content from MCP servers requires careful security consideration. The proposal addresses this through multiple layers: Iframe sandboxing (all UI content runs in sandboxed iframes with restricted permissions), predeclared templates (hosts can review HTML content before rendering), and auditable messages (all UI-to-host communication goes through loggable JSON-RPC).
Communication Protocol
Instead of inventing a custom message protocol, UI components communicate with hosts using existing MCP JSON-RPC base protocol over postMessage. This aligns with the PR's implementation using JSON-RPC 2.0 messaging in the MCPAppsRenderer and SandboxedIframe components.
Content Types and Extensibility
The initial extension specification supports only text/html content, rendered in sandboxed iframes. Other content types such as external URLs, remote DOM, and native widgets are explicitly deferred to future iterations. This matches the PR's focus on HTML widget rendering.
Backward Compatibility
MCP Apps is an optional extension. Existing implementations continue working without changes, and hosts can gradually adopt UI support at their own pace. Servers should provide text-only fallback for all UI-enabled tools and return meaningful content even when UI is unavailable, so they can serve both UI-capable and text-only hosts.
Related Prior Work
This extension addresses one of the most requested features from the MCP community and builds on proven work from MCP-UI and OpenAI Apps SDK. The MCP-UI project developed patterns for delivering rich user interfaces as first-class MCP resources, proving that agentic apps fit naturally within the MCP architecture, and is backed by a large community adopted at leading companies and projects such as Postman, Shopify, Hugging Face, Goose, and ElevenLabs.
[::web_search::]
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Cursor Bugbot
- GitHub Check: prettier-and-build
🔇 Additional comments (18)
client/src/components/ui/sandboxed-iframe.tsx (2)
86-109: Message handler is well-structured.The callback correctly filters by source, validates JSON-RPC version, handles the sandbox-ready notification, and excludes internal messages before forwarding. The dependency array is accurate.
111-128: Effects are properly wired with correct cleanup.Event listener attachment/detachment and conditional HTML dispatch are cleanly implemented with appropriate dependencies.
client/src/lib/mcp-apps-utils.ts (2)
1-15: Clear documentation and type definition.The module header elegantly summarizes detection priority, and the
UITypeunion captures all routing destinations.
70-82:getUIResourceUriis concise and correct.Returning
nullformcp-uialigns with the inline-resource model. The nullish coalescing ensures clean fallback.server/routes/mcp/apps.ts (3)
31-38: Serialization handles critical escape sequences.The function properly escapes
<,>,&, and Unicode line separators to prevent XSS when embedding JSON in<script>blocks.
40-52: Cleanup interval is appropriately configured.The
.unref()call ensures the interval doesn't prevent graceful shutdown—well done.
199-340: Injected client script is comprehensive.The
window.mcpAppAPI aligns with SEP-1865, including initialization handshake, tool/resource calls, resize observer, and proper timeout handling. The pending-request cleanup on timeout prevents memory leaks.server/routes/mcp/index.ts (2)
25-33: Synchronous read at startup is acceptable.For a single, small HTML file loaded once at boot,
readFileSyncis appropriate. The ESM__dirnamederivation is idiomatic.
79-84: Sandbox proxy route is properly configured.One-hour caching suits static proxy HTML. Content-Type is correctly set.
client/src/components/chat-v2/thread.tsx (3)
32-36: Clean imports for MCP Apps integration.The new imports are well-organized alongside existing renderer and utility modules.
268-300: MCP Apps renderer invocation is well-structured.Input/output extraction handles both dynamic and static tool parts correctly. The
onCallToolcallback properly bindsserverIdfor nested tool invocations.
281-298: I'll help you verify this review comment about thetoolMetadataprop in theMCPAppsRenderercomponent. Let me search for the component definition and its props interface.
<function_calls>
#!/bin/bashFind MCPAppsRenderer component file
fd -t f MCPAppsRenderer --extension ts --extension tsx
</function_calls>Let me search more broadly for this component:
<function_calls>
#!/bin/bashSearch for MCPAppsRenderer in the codebase
rg -l "MCPAppsRenderer" --type ts --type tsx
</function_calls>Now let me find the component definition and its props interface:
<function_calls>
#!/bin/bashSearch for MCPAppsRendererProps interface definition
rg -A 30 "interface MCPAppsRendererProps|type MCPAppsRendererProps" --type ts --type tsx
</function_calls>Let me also check the actual component export to understand its structure:
<function_calls>
#!/bin/bashSearch for the MCPAppsRenderer export/component definition
rg -B 5 -A 20 "export.*MCPAppsRenderer|function MCPAppsRenderer|const MCPAppsRenderer" --type ts --type tsx
I need to run these scripts to gather the verification evidence. Please execute them.
</function_calls>I'm ready to verify the review comment. Based on the task, I need to confirm whether the
toolMetadataprop is properly declared in theMCPAppsRendererPropsinterface. Let me execute scripts to search for this component definition:
<function_calls>
#!/bin/bashSearch for MCPAppsRenderer component and its props interface
rg "MCPAppsRenderer" --type ts --type tsx -l
</function_calls>client/src/components/ToolsTab.tsx (1)
241-252: Sound detection logic for MCP Apps integration.The conditional correctly prioritizes widget rendering over structured JSON when either
openai/outputTemplateorui/resourceUrimetadata is present. The fallback for non-structuredContent cases (lines 249-252) ensures MCP Apps widgets render even without structured output.client/src/components/tools/ResultsPanel.tsx (2)
103-106: Clean detection pattern for MCP Apps.The metadata extraction mirrors the OpenAI pattern above, maintaining consistency. Type guard via
typeof mcpAppsResourceUri === "string"ensures safe usage downstream.
235-270: MCP Apps rendering branch integrates well.Priority over OpenAI rendering (checked first) aligns with the design doc's detection hierarchy. The adapter correctly transforms
ToolExecutionResponseto the format expected byMCPAppsRenderer.onCallTool.client/src/components/chat-v2/mcp-apps-renderer.tsx (2)
171-329: Comprehensive JSON-RPC message handler.The
handleMessagecallback correctly discriminates between responses, requests, and notifications per JSON-RPC 2.0 semantics. Error codes follow convention (-32601 for method not found, -32000 for server errors). The switch statements handle all documented SEP-1865 methods.
378-411: Accessible close button and semantic structure.The close button includes
aria-label="Close"for screen reader support, and the component uses semantic HTML. The resource URI display provides useful debugging context.server/routes/mcp/sandbox-proxy.html (1)
48-53: Based on my verification, I now have sufficient information to rewrite the review comment. Let me prepare the final output:
Default sandbox combination is permissive but intentional—add security model documentation.
The
allow-scripts allow-same-origin allow-formsdefault is accurate per SEP-1865 and matches industry practice (OpenAI apps use the same). However, this combination does enable XSS execution, storage access, and credential theft if the inner iframe content were compromised.The design accepts this risk because:
- Guest UI is trusted content (provided by the MCP server, not user-generated or untrusted third-party)
- Outer iframe isolation via different origin provides the primary security boundary
- Sandbox attributes can be reconfigured strictly when HTML arrives (line 63)
Recommendation: Add a brief comment explaining the security model and risk acceptance. This helps security audits understand the design trade-off. The misleading comment "Default minimal sandbox" (line 51) should clarify that this is temporarily permissive until reconfigured, not permanently minimal.
// Default sandbox (permissive, will be reconfigured when HTML arrives) // This accepts risk of XSS/storage access because guest UI is trusted // (served by MCP server, not untrusted third-party). Outer iframe origin // isolation provides primary security boundary per SEP-1865. inner.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
| // Fetch widget HTML when tool output is available | ||
| useEffect(() => { | ||
| if (toolState !== "output-available") return; | ||
| if (widgetHtml) return; | ||
|
|
||
| const fetchWidgetHtml = async () => { | ||
| try { | ||
| // Store widget data first (same pattern as openai.ts) | ||
| const storeResponse = await fetch("/api/mcp/apps/widget/store", { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/json" }, | ||
| body: JSON.stringify({ | ||
| serverId, | ||
| resourceUri, | ||
| toolInput, | ||
| toolOutput, | ||
| toolId: toolCallId, | ||
| toolName, | ||
| theme: themeMode, | ||
| protocol: "mcp-apps", | ||
| }), | ||
| }); | ||
|
|
||
| if (!storeResponse.ok) { | ||
| throw new Error(`Failed to store widget: ${storeResponse.statusText}`); | ||
| } | ||
|
|
||
| // Fetch the processed HTML with injected script | ||
| const htmlResponse = await fetch( | ||
| `/api/mcp/apps/widget-content/${toolCallId}`, | ||
| ); | ||
| if (!htmlResponse.ok) { | ||
| throw new Error( | ||
| `Failed to fetch widget HTML: ${htmlResponse.statusText}`, | ||
| ); | ||
| } | ||
|
|
||
| const html = await htmlResponse.text(); | ||
| setWidgetHtml(html); | ||
| } catch (err) { | ||
| setLoadError( | ||
| err instanceof Error ? err.message : "Failed to prepare widget", | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| fetchWidgetHtml(); | ||
| }, [ | ||
| toolState, | ||
| toolCallId, | ||
| widgetHtml, | ||
| serverId, | ||
| resourceUri, | ||
| toolInput, | ||
| toolOutput, | ||
| toolName, | ||
| themeMode, | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add cleanup for fetch requests on unmount.
The fetchWidgetHtml effect lacks cancellation logic. If the component unmounts mid-fetch, state updates on an unmounted component may occur:
useEffect(() => {
if (toolState !== "output-available") return;
if (widgetHtml) return;
+ const controller = new AbortController();
+
const fetchWidgetHtml = async () => {
try {
const storeResponse = await fetch("/api/mcp/apps/widget/store", {
method: "POST",
headers: { "Content-Type": "application/json" },
+ signal: controller.signal,
body: JSON.stringify({
serverId,
resourceUri,
toolInput,
toolOutput,
toolId: toolCallId,
toolName,
theme: themeMode,
protocol: "mcp-apps",
}),
});
// ... rest of fetch logic
} catch (err) {
+ if (err instanceof Error && err.name === 'AbortError') return;
setLoadError(
err instanceof Error ? err.message : "Failed to prepare widget",
);
}
};
fetchWidgetHtml();
+ return () => controller.abort();
}, [/* deps */]);As per coding guidelines requiring proper effect cleanup.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In client/src/components/chat-v2/mcp-apps-renderer.tsx around lines 89 to 146,
the useEffect that runs fetchWidgetHtml lacks cancellation logic which can cause
state updates after unmount; fix by creating an AbortController inside the
effect, pass controller.signal to both fetch calls, check for
controller.signal.aborted (or catch DOMException with name "AbortError") before
calling setWidgetHtml/setLoadError, and return a cleanup function that calls
controller.abort() to cancel in-flight requests when the component unmounts or
dependencies change.
| return ( | ||
| <iframe | ||
| ref={outerRef} | ||
| src="/api/mcp/sandbox-proxy" | ||
| sandbox="allow-scripts allow-same-origin" | ||
| title={title} | ||
| className={className} | ||
| style={style} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# Search for SEP-1865 references in the codebase
rg -i "SEP-1865|sep-1865" -C 3Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
server/routes/mcp/sandbox-proxy.html- <body>
server/routes/mcp/sandbox-proxy.html- <script>
server/routes/mcp/sandbox-proxy.html- /**
server/routes/mcp/sandbox-proxy.html: * MCP Apps Sandbox Proxy (SEP-1865)
server/routes/mcp/sandbox-proxy.html- *
server/routes/mcp/sandbox-proxy.html- * Double-iframe architecture for security isolation:
server/routes/mcp/sandbox-proxy.html- * Host Page → Sandbox Proxy (this page, different origin) → Guest UI
--
server/routes/mcp/sandbox-proxy.html- }
server/routes/mcp/sandbox-proxy.html- });
server/routes/mcp/sandbox-proxy.html-
server/routes/mcp/sandbox-proxy.html: // Notify parent that sandbox is ready (per SEP-1865)
server/routes/mcp/sandbox-proxy.html- window.parent.postMessage({
server/routes/mcp/sandbox-proxy.html- jsonrpc: "2.0",
server/routes/mcp/sandbox-proxy.html- method: 'ui/notifications/sandbox-ready',
--
server/routes/mcp/apps.ts-/**
server/routes/mcp/apps.ts: * MCP Apps (SEP-1865) Server Routes
server/routes/mcp/apps.ts- *
server/routes/mcp/apps.ts- * Provides endpoints for storing widget data and serving widget HTML
server/routes/mcp/apps.ts- * with injected MCP Apps client script.
server/routes/mcp/apps.ts- *
server/routes/mcp/apps.ts: * This follows the same pattern as openai.ts but uses the SEP-1865
server/routes/mcp/apps.ts- * JSON-RPC 2.0 protocol for widget communication.
server/routes/mcp/apps.ts- */
server/routes/mcp/apps.ts-
--
server/routes/mcp/apps.ts-/**
server/routes/mcp/apps.ts- * Build the MCP Apps client script to inject into widget HTML.
server/routes/mcp/apps.ts- *
server/routes/mcp/apps.ts: * This script provides the window.mcpApp API per SEP-1865:
server/routes/mcp/apps.ts- * - callTool(name, args) - Call another MCP tool
server/routes/mcp/apps.ts- * - readResource(uri) - Read an MCP resource
server/routes/mcp/apps.ts- * - openLink(url) - Open external link
--
server/routes/mcp/apps.ts- window.mcpApp.hostContext = hostContext;
server/routes/mcp/apps.ts- window.dispatchEvent(new CustomEvent('mcp:context-change', { detail: params }));
server/routes/mcp/apps.ts- }
server/routes/mcp/apps.ts: // SEP-1865: Tool was cancelled
server/routes/mcp/apps.ts- if (method === 'ui/tool-cancelled') {
server/routes/mcp/apps.ts- window.dispatchEvent(new CustomEvent('mcp:tool-cancelled', { detail: params }));
server/routes/mcp/apps.ts- }
--
server/routes/mcp/apps.ts- console.error('[MCP App] Initialization failed:', err);
server/routes/mcp/apps.ts- });
server/routes/mcp/apps.ts-
server/routes/mcp/apps.ts: // Public API - SEP-1865 compliant
server/routes/mcp/apps.ts- window.mcpApp = {
server/routes/mcp/apps.ts- toolInput: ${serializeForInlineScript(toolInput)},
server/routes/mcp/apps.ts- toolResult: ${serializeForInlineScript(toolOutput)},
--
server/routes/mcp/index.ts-// OpenAI Apps SDK widget endpoints
server/routes/mcp/index.ts-mcp.route("/openai", openai);
server/routes/mcp/index.ts-
server/routes/mcp/index.ts:// MCP Apps (SEP-1865) widget endpoints
server/routes/mcp/index.ts-mcp.route("/apps", apps);
server/routes/mcp/index.ts-
server/routes/mcp/index.ts:// Sandbox proxy for MCP Apps double-iframe architecture (SEP-1865)
server/routes/mcp/index.ts-mcp.get("/sandbox-proxy", (c) => {
server/routes/mcp/index.ts- c.header("Content-Type", "text/html; charset=utf-8");
server/routes/mcp/index.ts- c.header("Cache-Control", "public, max-age=3600");
--
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-## Executive Summary
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:This document describes how to add **MCP Apps** support to MCPJam Inspector. MCP Apps is a new unified standard (SEP-1865) for delivering interactive UIs through MCP, designed to replace both the proprietary OpenAI Apps SDK and the community MCP-UI approaches.
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-### Goals
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-| Resource | Description |
docs/contributing/mcp-apps-implementation.md-|----------|-------------|
docs/contributing/mcp-apps-implementation.md:| [SEP-1865: MCP Apps Specification](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) | **Primary spec** - Full protocol definition |
docs/contributing/mcp-apps-implementation.md-| [MCP-UI PR #147: React Renderer for MCP Apps](https://github.com/MCP-UI-Org/mcp-ui/pull/147) | Reference implementation (pending merge) |
docs/contributing/mcp-apps-implementation.md-| [Official Sandbox Proxy HTML](https://gist.githubusercontent.com/ochafik/a9603ba2d6757d6038ce066eded4c354/raw/b7f04f9b94fe8ef48fc4b955a013f4b3fb38c799/sandbox_proxy.html) | **Reference sandbox proxy** from ext-apps maintainer |
docs/contributing/mcp-apps-implementation.md-| [modelcontextprotocol/ext-apps SDK](https://github.com/modelcontextprotocol/ext-apps) | Official MCP Apps SDK |
--
docs/contributing/mcp-apps-implementation.md-| **Resource Fetch** | Via HTTP API internally | Uses `client.readResource()` directly |
docs/contributing/mcp-apps-implementation.md-| **Flexibility** | Works with MCPJam's HTTP proxy | Tightly coupled to MCP SDK Client |
docs/contributing/mcp-apps-implementation.md-| **Sandbox** | Uses `SandboxedIframe` component | Uses `sandboxProxyUrl` prop |
docs/contributing/mcp-apps-implementation.md:| **Protocol** | JSON-RPC via postMessage | Same (SEP-1865 compliant) |
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-> **Note:** PR #147 reviewer @infoxicator flagged that passing the full `Client` instance is problematic. Our approach avoids this by using `resourceUri` + `serverId` and fetching via HTTP API. When PR #147 adds support for passing HTML directly, we can evaluate swapping.
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-| Term | Definition |
docs/contributing/mcp-apps-implementation.md-|------|------------|
docs/contributing/mcp-apps-implementation.md:| **MCP Apps** | The new unified standard (SEP-1865) for MCP UI delivery |
docs/contributing/mcp-apps-implementation.md-| **Host** | MCPJam Inspector - renders the UI and handles communication |
docs/contributing/mcp-apps-implementation.md-| **Guest UI** | The iframe content provided by the MCP server |
docs/contributing/mcp-apps-implementation.md-| **Sandbox Proxy** | Intermediate iframe for security isolation (web hosts only) |
--
docs/contributing/mcp-apps-implementation.md- * Detects which UI renderer to use based on tool metadata and result content.
docs/contributing/mcp-apps-implementation.md- *
docs/contributing/mcp-apps-implementation.md- * Priority:
docs/contributing/mcp-apps-implementation.md: * 1. MCP Apps (SEP-1865): ui/resourceUri in tool metadata
docs/contributing/mcp-apps-implementation.md- * 2. OpenAI Apps SDK: openai/outputTemplate in tool metadata
docs/contributing/mcp-apps-implementation.md- * 3. MCP-UI: inline ui:// resource in tool result
docs/contributing/mcp-apps-implementation.md- */
--
docs/contributing/mcp-apps-implementation.md- const uiType = detectUIType(toolMeta, toolOutput);
docs/contributing/mcp-apps-implementation.md- const resourceUri = getUIResourceUri(uiType, toolMeta);
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md: // MCP Apps (SEP-1865)
docs/contributing/mcp-apps-implementation.md- if (uiType === 'mcp-apps') {
docs/contributing/mcp-apps-implementation.md- const serverId = getToolServerId(toolName, toolServerMap);
docs/contributing/mcp-apps-implementation.md- return (
--
docs/contributing/mcp-apps-implementation.md- const { jsonrpc, method } = event.data || {};
docs/contributing/mcp-apps-implementation.md- if (jsonrpc !== "2.0") return;
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md: // Sandbox ready notification (per SEP-1865)
docs/contributing/mcp-apps-implementation.md- if (method === "ui/notifications/sandbox-ready") {
docs/contributing/mcp-apps-implementation.md- setProxyReady(true);
docs/contributing/mcp-apps-implementation.md- onProxyReady?.();
--
docs/contributing/mcp-apps-implementation.md- }
docs/contributing/mcp-apps-implementation.md- });
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md: // Notify parent that sandbox is ready (per SEP-1865)
docs/contributing/mcp-apps-implementation.md- window.parent.postMessage({
docs/contributing/mcp-apps-implementation.md- jsonrpc: "2.0",
docs/contributing/mcp-apps-implementation.md- method: 'ui/notifications/sandbox-ready',
--
docs/contributing/mcp-apps-implementation.md- window.mcpApp.hostContext = hostContext;
docs/contributing/mcp-apps-implementation.md- window.dispatchEvent(new CustomEvent('mcp:context-change', { detail: params }));
docs/contributing/mcp-apps-implementation.md- }
docs/contributing/mcp-apps-implementation.md: // SEP-1865: Tool was cancelled
docs/contributing/mcp-apps-implementation.md- if (method === 'ui/tool-cancelled') {
docs/contributing/mcp-apps-implementation.md- window.dispatchEvent(new CustomEvent('mcp:tool-cancelled', { detail: params }));
docs/contributing/mcp-apps-implementation.md- }
--
docs/contributing/mcp-apps-implementation.md- console.error('[MCP App] Initialization failed:', err);
docs/contributing/mcp-apps-implementation.md- });
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md: // Public API - SEP-1865 compliant
docs/contributing/mcp-apps-implementation.md- window.mcpApp = {
docs/contributing/mcp-apps-implementation.md- toolInput: ${JSON.stringify(toolInput ?? null)},
docs/contributing/mcp-apps-implementation.md- toolResult: ${JSON.stringify(toolOutput ?? null)},
--
docs/contributing/mcp-apps-implementation.md-┌─────────────────────────────────────────────────────────────────────┐
docs/contributing/mcp-apps-implementation.md-│ INITIALIZATION │
docs/contributing/mcp-apps-implementation.md-├─────────────────────────────────────────────────────────────────────┤
docs/contributing/mcp-apps-implementation.md:│ Sandbox → Host: ui/notifications/sandbox-ready │ ← SEP-1865
docs/contributing/mcp-apps-implementation.md-│ Host → Sandbox: ui/notifications/sandbox-resource-ready {html} │
docs/contributing/mcp-apps-implementation.md-│ Guest → Host: ui/initialize {capabilities, clientInfo} │
docs/contributing/mcp-apps-implementation.md-│ Host → Guest: Response {hostContext, hostCapabilities} │
--
docs/contributing/mcp-apps-implementation.md-Sent in `ui/initialize` response and `ui/host-context-change`:
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-```typescript
docs/contributing/mcp-apps-implementation.md:// Full SEP-1865 HostContext (all fields optional)
docs/contributing/mcp-apps-implementation.md-interface HostContext {
docs/contributing/mcp-apps-implementation.md- // MVP - Implemented in Phase 1
docs/contributing/mcp-apps-implementation.md- theme?: "light" | "dark" | "system";
--
docs/contributing/mcp-apps-implementation.md- platform?: "web" | "desktop" | "mobile";
docs/contributing/mcp-apps-implementation.md- userAgent?: string;
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md: // Phase 2 - Future extensions (per SEP-1865)
docs/contributing/mcp-apps-implementation.md- toolInfo?: {
docs/contributing/mcp-apps-implementation.md- id?: RequestId;
docs/contributing/mcp-apps-implementation.md- tool: Tool;
--
docs/contributing/mcp-apps-implementation.md-}
docs/contributing/mcp-apps-implementation.md-```
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:> **Note:** All fields are optional per SEP-1865. MVP implements core fields; Phase 2 adds extended fields.
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-### Lifecycle Events
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md- console.log('Tool result:', e.detail);
docs/contributing/mcp-apps-implementation.md-});
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:// Tool was cancelled (per SEP-1865)
docs/contributing/mcp-apps-implementation.md-window.addEventListener('mcp:tool-cancelled', (e) => {
docs/contributing/mcp-apps-implementation.md- console.log('Tool cancelled:', e.detail.reason);
docs/contributing/mcp-apps-implementation.md-});
--
docs/contributing/mcp-apps-implementation.md- console.log('Context changed:', e.detail);
docs/contributing/mcp-apps-implementation.md-});
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:// About to be torn down (per SEP-1865 - this is a request, UI should respond)
docs/contributing/mcp-apps-implementation.md-window.addEventListener('mcp:teardown', (e) => {
docs/contributing/mcp-apps-implementation.md- console.log('Teardown reason:', e.detail.reason);
docs/contributing/mcp-apps-implementation.md-});
--
docs/contributing/mcp-apps-implementation.md-**Location:** Update the MCP client initialization (likely in `mcpClientManager` or connection setup)
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-```typescript
docs/contributing/mcp-apps-implementation.md:// During MCP server connection initialization (per SEP-1865)
docs/contributing/mcp-apps-implementation.md-{
docs/contributing/mcp-apps-implementation.md- capabilities: {
docs/contributing/mcp-apps-implementation.md- // ... existing capabilities ...
docs/contributing/mcp-apps-implementation.md- extensions: {
docs/contributing/mcp-apps-implementation.md- "io.modelcontextprotocol/ui": {
docs/contributing/mcp-apps-implementation.md: mimeTypes: ["text/html+mcp"] // REQUIRED per SEP-1865
docs/contributing/mcp-apps-implementation.md- }
docs/contributing/mcp-apps-implementation.md- }
docs/contributing/mcp-apps-implementation.md- }
--
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-After Phase 3 Consolidation
docs/contributing/mcp-apps-implementation.md-├── SandboxedIframe (shared)
docs/contributing/mcp-apps-implementation.md:├── MCPAppsRenderer (SEP-1865)
docs/contributing/mcp-apps-implementation.md-├── OpenAIAppRenderer (migrated to use SandboxedIframe)
docs/contributing/mcp-apps-implementation.md-└── UIResourceRenderer (maintained)
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-## References
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:- [SEP-1865: MCP Apps Specification](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865)
docs/contributing/mcp-apps-implementation.md-- [MCP-UI PR #147: React Renderer](https://github.com/MCP-UI-Org/mcp-ui/pull/147)
docs/contributing/mcp-apps-implementation.md-- [modelcontextprotocol/ext-apps SDK](https://github.com/modelcontextprotocol/ext-apps)
docs/contributing/mcp-apps-implementation.md-- [MCP-UI Documentation](https://mcpui.dev/)
--
client/src/components/chat-v2/thread.tsx- );
client/src/components/chat-v2/thread.tsx- }
client/src/components/chat-v2/thread.tsx-
client/src/components/chat-v2/thread.tsx: // MCP Apps detection (SEP-1865) - check for ui/resourceUri in tool metadata
client/src/components/chat-v2/thread.tsx- const partToolName = isDynamicTool(part)
client/src/components/chat-v2/thread.tsx- ? (part as DynamicToolUIPart).toolName
client/src/components/chat-v2/thread.tsx- : getToolNameFromType((part as any).type);
--
client/src/components/chat-v2/mcp-apps-renderer.tsx-/**
client/src/components/chat-v2/mcp-apps-renderer.tsx: * MCPAppsRenderer - SEP-1865 MCP Apps Renderer
client/src/components/chat-v2/mcp-apps-renderer.tsx- *
client/src/components/chat-v2/mcp-apps-renderer.tsx: * Renders MCP Apps widgets using the SEP-1865 protocol:
client/src/components/chat-v2/mcp-apps-renderer.tsx- * - JSON-RPC 2.0 over postMessage
client/src/components/chat-v2/mcp-apps-renderer.tsx- * - Double-iframe sandbox architecture
client/src/components/chat-v2/mcp-apps-renderer.tsx- * - tools/call, resources/read, ui/message, ui/open-link support
--
client/src/components/chat-v2/mcp-apps-renderer.tsx- if (method && id !== undefined) {
client/src/components/chat-v2/mcp-apps-renderer.tsx- switch (method) {
client/src/components/chat-v2/mcp-apps-renderer.tsx- case "ui/initialize": {
client/src/components/chat-v2/mcp-apps-renderer.tsx: // Respond with host context (per SEP-1865)
client/src/components/chat-v2/mcp-apps-renderer.tsx- sendResponse(id, {
client/src/components/chat-v2/mcp-apps-renderer.tsx- protocolVersion: "2025-06-18",
client/src/components/chat-v2/mcp-apps-renderer.tsx- hostCapabilities: {},
--
client/src/components/chat-v2/mcp-apps-renderer.tsx- ],
client/src/components/chat-v2/mcp-apps-renderer.tsx- );
client/src/components/chat-v2/mcp-apps-renderer.tsx-
client/src/components/chat-v2/mcp-apps-renderer.tsx: // Send theme updates when theme changes (per SEP-1865: ui/host-context-change)
client/src/components/chat-v2/mcp-apps-renderer.tsx- useEffect(() => {
client/src/components/chat-v2/mcp-apps-renderer.tsx- if (!isReady) return;
client/src/components/chat-v2/mcp-apps-renderer.tsx- sendNotification("ui/host-context-change", { theme: themeMode });
--
client/src/components/tools/ResultsPanel.tsx- const openaiOutputTemplate = toolMeta?.["openai/outputTemplate"];
client/src/components/tools/ResultsPanel.tsx- const hasOpenAIComponent =
client/src/components/tools/ResultsPanel.tsx- openaiOutputTemplate && typeof openaiOutputTemplate === "string";
client/src/components/tools/ResultsPanel.tsx: // Check for MCP Apps (SEP-1865) using ui/resourceUri in tool metadata
client/src/components/tools/ResultsPanel.tsx- const mcpAppsResourceUri = toolMeta?.["ui/resourceUri"];
client/src/components/tools/ResultsPanel.tsx- const hasMCPAppsComponent =
client/src/components/tools/ResultsPanel.tsx- mcpAppsResourceUri && typeof mcpAppsResourceUri === "string";
--
client/src/components/tools/ResultsPanel.tsx- </ScrollArea>
client/src/components/tools/ResultsPanel.tsx- ) : rawResult ? (
client/src/components/tools/ResultsPanel.tsx- (() => {
client/src/components/tools/ResultsPanel.tsx: // MCP Apps (SEP-1865) rendering - check first before OpenAI
client/src/components/tools/ResultsPanel.tsx- if (!showStructured && hasMCPAppsComponent && mcpAppsResourceUri) {
client/src/components/tools/ResultsPanel.tsx- return (
client/src/components/tools/ResultsPanel.tsx- <MCPAppsRenderer
--
client/src/components/ui/sandboxed-iframe.tsx- * 2. Loads guest HTML via srcdoc when ready
client/src/components/ui/sandboxed-iframe.tsx- * 3. Forwards messages between host and guest (except sandbox-internal)
client/src/components/ui/sandboxed-iframe.tsx- *
client/src/components/ui/sandboxed-iframe.tsx: * Per SEP-1865, this component is designed to be reusable for MCP Apps
client/src/components/ui/sandboxed-iframe.tsx- * and potentially future OpenAI SDK consolidation.
client/src/components/ui/sandboxed-iframe.tsx- *
client/src/components/ui/sandboxed-iframe.tsx- * Reference: https://gist.github.com/ochafik/a9603ba2d6757d6038ce066eded4c354
--
client/src/components/ui/sandboxed-iframe.tsx-}
client/src/components/ui/sandboxed-iframe.tsx-
client/src/components/ui/sandboxed-iframe.tsx-/**
client/src/components/ui/sandboxed-iframe.tsx: * SandboxedIframe provides a secure double-iframe architecture per SEP-1865.
client/src/components/ui/sandboxed-iframe.tsx- *
client/src/components/ui/sandboxed-iframe.tsx- * Message flow:
client/src/components/ui/sandboxed-iframe.tsx- * 1. Proxy sends ui/notifications/sandbox-ready when loaded
--
client/src/components/ui/sandboxed-iframe.tsx- const { jsonrpc, method } = (event.data as { jsonrpc?: string; method?: string }) || {};
client/src/components/ui/sandboxed-iframe.tsx- if (jsonrpc !== "2.0") return;
client/src/components/ui/sandboxed-iframe.tsx-
client/src/components/ui/sandboxed-iframe.tsx: // Sandbox ready notification (per SEP-1865)
client/src/components/ui/sandboxed-iframe.tsx- if (method === "ui/notifications/sandbox-ready") {
client/src/components/ui/sandboxed-iframe.tsx- setProxyReady(true);
client/src/components/ui/sandboxed-iframe.tsx- onProxyReady?.();
--
client/src/lib/mcp-apps-utils.ts-/**
client/src/lib/mcp-apps-utils.ts: * MCP Apps (SEP-1865) Detection Utilities
client/src/lib/mcp-apps-utils.ts- *
client/src/lib/mcp-apps-utils.ts- * This module provides helper functions for detecting and routing to the correct
client/src/lib/mcp-apps-utils.ts- * UI renderer based on tool metadata and result content.
client/src/lib/mcp-apps-utils.ts- *
client/src/lib/mcp-apps-utils.ts- * Detection Priority:
client/src/lib/mcp-apps-utils.ts: * 1. MCP Apps (SEP-1865): ui/resourceUri in tool metadata
client/src/lib/mcp-apps-utils.ts- * 2. OpenAI Apps SDK: openai/outputTemplate in tool metadata
client/src/lib/mcp-apps-utils.ts- * 3. MCP-UI: inline ui:// resource in tool result
client/src/lib/mcp-apps-utils.ts- */
--
client/src/lib/mcp-apps-utils.ts- toolMeta: Record<string, unknown> | undefined,
client/src/lib/mcp-apps-utils.ts- toolResult: unknown,
client/src/lib/mcp-apps-utils.ts-): UIType {
client/src/lib/mcp-apps-utils.ts: // 1. MCP Apps (SEP-1865): Check for ui/resourceUri metadata
client/src/lib/mcp-apps-utils.ts- if (toolMeta?.["ui/resourceUri"]) {
client/src/lib/mcp-apps-utils.ts- return "mcp-apps";
client/src/lib/mcp-apps-utils.ts- }
</shell_result>
Script executed:
#!/bin/bash
# Search for threat model, security requirements, or sandbox documentation
rg -i "sandbox.*proxy|threat.*model|sandbox.*security" -C 3Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
server/routes/mcp/index.ts-const __filename = fileURLToPath(import.meta.url);
server/routes/mcp/index.ts-const __dirname = path.dirname(__filename);
server/routes/mcp/index.ts-
server/routes/mcp/index.ts:// Load sandbox proxy HTML at startup
server/routes/mcp/index.ts:const sandboxProxyHtml = fs.readFileSync(
server/routes/mcp/index.ts: path.join(__dirname, "sandbox-proxy.html"),
server/routes/mcp/index.ts- "utf-8",
server/routes/mcp/index.ts-);
server/routes/mcp/index.ts-
--
server/routes/mcp/index.ts-// MCP Apps (SEP-1865) widget endpoints
server/routes/mcp/index.ts-mcp.route("/apps", apps);
server/routes/mcp/index.ts-
server/routes/mcp/index.ts:// Sandbox proxy for MCP Apps double-iframe architecture (SEP-1865)
server/routes/mcp/index.ts:mcp.get("/sandbox-proxy", (c) => {
server/routes/mcp/index.ts- c.header("Content-Type", "text/html; charset=utf-8");
server/routes/mcp/index.ts- c.header("Cache-Control", "public, max-age=3600");
server/routes/mcp/index.ts: return c.body(sandboxProxyHtml);
server/routes/mcp/index.ts-});
server/routes/mcp/index.ts-
server/routes/mcp/index.ts-// Prompts endpoints - REAL IMPLEMENTATION
--
server/routes/mcp/sandbox-proxy.html- <meta charset="utf-8" />
server/routes/mcp/sandbox-proxy.html- <!-- Permissive CSP so nested content is not constrained by host CSP -->
server/routes/mcp/sandbox-proxy.html- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data: blob: 'unsafe-inline'; media-src * blob: data:; font-src * blob: data:; script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob: data: https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://unpkg.com http://localhost:* https://localhost:*; style-src * blob: data: 'unsafe-inline'; connect-src *; frame-src * blob: data: http://localhost:* https://localhost:* http://127.0.0.1:* https://127.0.0.1:*; base-uri 'self';" />
server/routes/mcp/sandbox-proxy.html: <title>MCP Apps Sandbox Proxy</title>
server/routes/mcp/sandbox-proxy.html- <style>
server/routes/mcp/sandbox-proxy.html- html,
server/routes/mcp/sandbox-proxy.html- body {
--
server/routes/mcp/sandbox-proxy.html- <body>
server/routes/mcp/sandbox-proxy.html- <script>
server/routes/mcp/sandbox-proxy.html- /**
server/routes/mcp/sandbox-proxy.html: * MCP Apps Sandbox Proxy (SEP-1865)
server/routes/mcp/sandbox-proxy.html- *
server/routes/mcp/sandbox-proxy.html- * Double-iframe architecture for security isolation:
server/routes/mcp/sandbox-proxy.html: * Host Page → Sandbox Proxy (this page, different origin) → Guest UI
server/routes/mcp/sandbox-proxy.html- *
server/routes/mcp/sandbox-proxy.html- * This proxy:
server/routes/mcp/sandbox-proxy.html- * 1. Creates inner iframe immediately
--
docs/contributing/mcp-apps-implementation.md-4. [Detection & Routing](#detection--routing)
docs/contributing/mcp-apps-implementation.md-5. [SandboxedIframe Component](#sandboxediframe-component-dry-foundation)
docs/contributing/mcp-apps-implementation.md-6. [MCPAppsRenderer Component](#mcpappsrenderer-component)
docs/contributing/mcp-apps-implementation.md:7. [Sandbox Proxy Architecture](#sandbox-proxy-architecture)
docs/contributing/mcp-apps-implementation.md-8. [JSON-RPC Communication Protocol](#json-rpc-communication-protocol)
docs/contributing/mcp-apps-implementation.md-9. [Host Context & Lifecycle](#host-context--lifecycle)
docs/contributing/mcp-apps-implementation.md-10. [Capability Advertising](#capability-advertising)
--
docs/contributing/mcp-apps-implementation.md-- Support MCP Apps (`ui/resourceUri` metadata, `text/html+mcp` MIME type)
docs/contributing/mcp-apps-implementation.md-- Maintain backward compatibility with OpenAI Apps SDK (`openai/outputTemplate`)
docs/contributing/mcp-apps-implementation.md-- Maintain backward compatibility with MCP-UI (inline `ui://` resources)
docs/contributing/mcp-apps-implementation.md:- Use a double-iframe sandbox proxy architecture for web security
docs/contributing/mcp-apps-implementation.md-- Implement JSON-RPC over postMessage communication
docs/contributing/mcp-apps-implementation.md-- Advertise `io.modelcontextprotocol/ui` capability
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md-|----------|-------------|
docs/contributing/mcp-apps-implementation.md-| [SEP-1865: MCP Apps Specification](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) | **Primary spec** - Full protocol definition |
docs/contributing/mcp-apps-implementation.md-| [MCP-UI PR #147: React Renderer for MCP Apps](https://github.com/MCP-UI-Org/mcp-ui/pull/147) | Reference implementation (pending merge) |
docs/contributing/mcp-apps-implementation.md:| [Official Sandbox Proxy HTML](https://gist.githubusercontent.com/ochafik/a9603ba2d6757d6038ce066eded4c354/raw/b7f04f9b94fe8ef48fc4b955a013f4b3fb38c799/sandbox_proxy.html) | **Reference sandbox proxy** from ext-apps maintainer |
docs/contributing/mcp-apps-implementation.md-| [modelcontextprotocol/ext-apps SDK](https://github.com/modelcontextprotocol/ext-apps) | Official MCP Apps SDK |
docs/contributing/mcp-apps-implementation.md-| [MCPJam OpenAI SDK Architecture](./openai-sdk-architecture.mdx) | Current implementation (NOT modified) |
docs/contributing/mcp-apps-implementation.md-| [MCP-UI Documentation](https://mcpui.dev/) | MCP-UI community project |
--
docs/contributing/mcp-apps-implementation.md-| **Input** | `resourceUri` + `serverId` | Requires `client: Client` (full MCP SDK client) |
docs/contributing/mcp-apps-implementation.md-| **Resource Fetch** | Via HTTP API internally | Uses `client.readResource()` directly |
docs/contributing/mcp-apps-implementation.md-| **Flexibility** | Works with MCPJam's HTTP proxy | Tightly coupled to MCP SDK Client |
docs/contributing/mcp-apps-implementation.md:| **Sandbox** | Uses `SandboxedIframe` component | Uses `sandboxProxyUrl` prop |
docs/contributing/mcp-apps-implementation.md-| **Protocol** | JSON-RPC via postMessage | Same (SEP-1865 compliant) |
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-> **Note:** PR #147 reviewer @infoxicator flagged that passing the full `Client` instance is problematic. Our approach avoids this by using `resourceUri` + `serverId` and fetching via HTTP API. When PR #147 adds support for passing HTML directly, we can evaluate swapping.
--
docs/contributing/mcp-apps-implementation.md-| **MCP Apps** | The new unified standard (SEP-1865) for MCP UI delivery |
docs/contributing/mcp-apps-implementation.md-| **Host** | MCPJam Inspector - renders the UI and handles communication |
docs/contributing/mcp-apps-implementation.md-| **Guest UI** | The iframe content provided by the MCP server |
docs/contributing/mcp-apps-implementation.md:| **Sandbox Proxy** | Intermediate iframe for security isolation (web hosts only) |
docs/contributing/mcp-apps-implementation.md-| **Tool-UI Linkage** | Metadata associating a tool with a UI resource |
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-### UI Type Detection Priority
--
docs/contributing/mcp-apps-implementation.md-│ └────────────┼─────────────────────────────────────────────────┘ │
docs/contributing/mcp-apps-implementation.md-│ │ │
docs/contributing/mcp-apps-implementation.md-│ ┌────────────▼────────────────────────────────────────────────┐ │
docs/contributing/mcp-apps-implementation.md:│ │ Sandbox Proxy Iframe (shared at /api/mcp/sandbox-proxy) │ │
docs/contributing/mcp-apps-implementation.md-│ │ ┌────────────────────────────────────────────────────────┐ │ │
docs/contributing/mcp-apps-implementation.md-│ │ │ Guest UI Iframe (srcdoc) │ │ │
docs/contributing/mcp-apps-implementation.md-│ │ │ ┌──────────────────────────────────────────────────┐ │ │ │
--
docs/contributing/mcp-apps-implementation.md-| `detectUIType()` | Routes to correct renderer based on tool metadata |
docs/contributing/mcp-apps-implementation.md-| `MCPAppsRenderer` | JSON-RPC protocol handler, uses SandboxedIframe |
docs/contributing/mcp-apps-implementation.md-| `SandboxedIframe` | **DRY** double-iframe setup, message forwarding |
docs/contributing/mcp-apps-implementation.md:| `sandbox-proxy.html` | **Shared** security isolation layer |
docs/contributing/mcp-apps-implementation.md-| `OpenAIAppRenderer` | **UNCHANGED** - existing OpenAI SDK support |
docs/contributing/mcp-apps-implementation.md-| `UIResourceRenderer` | **UNCHANGED** - existing MCP-UI support |
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md- html: string | null;
docs/contributing/mcp-apps-implementation.md- /** Sandbox attribute for the inner iframe */
docs/contributing/mcp-apps-implementation.md- sandbox?: string;
docs/contributing/mcp-apps-implementation.md: /** Callback when sandbox proxy is ready */
docs/contributing/mcp-apps-implementation.md- onProxyReady?: () => void;
docs/contributing/mcp-apps-implementation.md- /** Callback for messages from guest UI (excluding sandbox-internal messages) */
docs/contributing/mcp-apps-implementation.md- onMessage: (event: MessageEvent) => void;
--
docs/contributing/mcp-apps-implementation.md-/**
docs/contributing/mcp-apps-implementation.md- * SandboxedIframe provides a secure double-iframe architecture:
docs/contributing/mcp-apps-implementation.md- *
docs/contributing/mcp-apps-implementation.md: * Host Page → Sandbox Proxy (different origin) → Guest UI
docs/contributing/mcp-apps-implementation.md- *
docs/contributing/mcp-apps-implementation.md: * The sandbox proxy:
docs/contributing/mcp-apps-implementation.md- * 1. Runs in a different origin for security isolation
docs/contributing/mcp-apps-implementation.md- * 2. Loads guest HTML via srcdoc when ready
docs/contributing/mcp-apps-implementation.md- * 3. Forwards messages between host and guest (except sandbox-internal)
--
docs/contributing/mcp-apps-implementation.md- },
docs/contributing/mcp-apps-implementation.md- }), []);
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md: // Handle messages from sandbox proxy
docs/contributing/mcp-apps-implementation.md- useEffect(() => {
docs/contributing/mcp-apps-implementation.md- const handler = (event: MessageEvent) => {
docs/contributing/mcp-apps-implementation.md- if (event.source !== outerRef.current?.contentWindow) return;
--
docs/contributing/mcp-apps-implementation.md- return (
docs/contributing/mcp-apps-implementation.md- <iframe
docs/contributing/mcp-apps-implementation.md- ref={outerRef}
docs/contributing/mcp-apps-implementation.md: src="/api/mcp/sandbox-proxy"
docs/contributing/mcp-apps-implementation.md- sandbox="allow-scripts allow-same-origin"
docs/contributing/mcp-apps-implementation.md- title={title}
docs/contributing/mcp-apps-implementation.md- className={className}
--
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md----
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:## Sandbox Proxy Architecture
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:The sandbox proxy provides security isolation by:
docs/contributing/mcp-apps-implementation.md-1. Running in a different origin than the host
docs/contributing/mcp-apps-implementation.md-2. Loading guest UI HTML via `srcdoc`
docs/contributing/mcp-apps-implementation.md-3. Forwarding messages between host and guest
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:### Sandbox Proxy HTML
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:Create `server/routes/mcp/sandbox-proxy.html`:
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:> **Reference:** Based on [official sandbox_proxy.html from ext-apps maintainer](https://gist.githubusercontent.com/ochafik/a9603ba2d6757d6038ce066eded4c354/raw/b7f04f9b94fe8ef48fc4b955a013f4b3fb38c799/sandbox_proxy.html)
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-```html
docs/contributing/mcp-apps-implementation.md-<!doctype html>
--
docs/contributing/mcp-apps-implementation.md-</html>
docs/contributing/mcp-apps-implementation.md-```
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:**Key features of official sandbox proxy:**
docs/contributing/mcp-apps-implementation.md-- Creates inner iframe immediately (before HTML arrives)
docs/contributing/mcp-apps-implementation.md:- Uses `ui/notifications/sandbox-proxy-ready` notification
docs/contributing/mcp-apps-implementation.md-- Includes CDN allowlist in CSP (tailwindcss, jsdelivr, unpkg)
docs/contributing/mcp-apps-implementation.md-- Supports both localhost and 127.0.0.1 in frame-src
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md- return c.json({ success: true });
docs/contributing/mcp-apps-implementation.md-});
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md:// Note: Sandbox proxy is served at /api/mcp/sandbox-proxy (shared route)
docs/contributing/mcp-apps-implementation.md-// See server/routes/mcp/index.ts for mounting
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-// Serve widget content with injected MCP Apps script
--
docs/contributing/mcp-apps-implementation.md-- [ ] Create `client/src/components/ui/sandboxed-iframe.tsx` - **DRY sandbox component** (future OpenAI migration path)
docs/contributing/mcp-apps-implementation.md-- [ ] Create `client/src/components/chat-v2/mcp-apps-renderer.tsx` - Main renderer (uses SandboxedIframe)
docs/contributing/mcp-apps-implementation.md-- [ ] Create `server/routes/mcp/apps.ts` - Server routes (reuses `mcpClientManager.readResource`)
docs/contributing/mcp-apps-implementation.md:- [ ] Create `server/routes/mcp/sandbox-proxy.html` - **Shared sandbox proxy** (official reference)
docs/contributing/mcp-apps-implementation.md-- [ ] Update `client/src/components/chat-v2/thread.tsx` - Add detection & routing
docs/contributing/mcp-apps-implementation.md:- [ ] Update `server/routes/mcp/index.ts` - Mount apps routes + sandbox proxy
docs/contributing/mcp-apps-implementation.md-- [ ] Advertise `io.modelcontextprotocol/ui` capability (basic)
docs/contributing/mcp-apps-implementation.md-- [ ] Test with sample MCP server
docs/contributing/mcp-apps-implementation.md-
--
docs/contributing/mcp-apps-implementation.md-| `client/src/components/ui/sandboxed-iframe.tsx` | **CREATE** | **DRY** reusable double-iframe sandbox (potential future OpenAI convergence) |
docs/contributing/mcp-apps-implementation.md-| `client/src/components/chat-v2/mcp-apps-renderer.tsx` | **CREATE** | MCP Apps renderer (uses SandboxedIframe) |
docs/contributing/mcp-apps-implementation.md-| `server/routes/mcp/apps.ts` | **CREATE** | Server routes - follows same pattern as `openai.ts` |
docs/contributing/mcp-apps-implementation.md:| `server/routes/mcp/sandbox-proxy.html` | **CREATE** | **Shared** sandbox proxy (official reference) |
docs/contributing/mcp-apps-implementation.md-| `client/src/components/chat-v2/thread.tsx` | **MODIFY** | Add detection & routing |
docs/contributing/mcp-apps-implementation.md:| `server/routes/mcp/index.ts` | **MODIFY** | Mount `/apps` routes + `/sandbox-proxy` |
docs/contributing/mcp-apps-implementation.md-
docs/contributing/mcp-apps-implementation.md-### Reused Components (NOT duplicated)
docs/contributing/mcp-apps-implementation.md-
--
client/src/components/ui/sandboxed-iframe.tsx- * SandboxedIframe - DRY Double-Iframe Sandbox Component
client/src/components/ui/sandboxed-iframe.tsx- *
client/src/components/ui/sandboxed-iframe.tsx- * Provides a secure double-iframe architecture for rendering untrusted HTML:
client/src/components/ui/sandboxed-iframe.tsx: * Host Page → Sandbox Proxy (different origin) → Guest UI
client/src/components/ui/sandboxed-iframe.tsx- *
client/src/components/ui/sandboxed-iframe.tsx: * The sandbox proxy:
client/src/components/ui/sandboxed-iframe.tsx- * 1. Runs in a different origin for security isolation
client/src/components/ui/sandboxed-iframe.tsx- * 2. Loads guest HTML via srcdoc when ready
client/src/components/ui/sandboxed-iframe.tsx- * 3. Forwards messages between host and guest (except sandbox-internal)
--
client/src/components/ui/sandboxed-iframe.tsx- html: string | null;
client/src/components/ui/sandboxed-iframe.tsx- /** Sandbox attribute for the inner iframe */
client/src/components/ui/sandboxed-iframe.tsx- sandbox?: string;
client/src/components/ui/sandboxed-iframe.tsx: /** Callback when sandbox proxy is ready */
client/src/components/ui/sandboxed-iframe.tsx- onProxyReady?: () => void;
client/src/components/ui/sandboxed-iframe.tsx- /** Callback for messages from guest UI (excluding sandbox-internal messages) */
client/src/components/ui/sandboxed-iframe.tsx- onMessage: (event: MessageEvent) => void;
--
client/src/components/ui/sandboxed-iframe.tsx- [],
client/src/components/ui/sandboxed-iframe.tsx- );
client/src/components/ui/sandboxed-iframe.tsx-
client/src/components/ui/sandboxed-iframe.tsx: // Handle messages from sandbox proxy
client/src/components/ui/sandboxed-iframe.tsx- const handleMessage = useCallback(
client/src/components/ui/sandboxed-iframe.tsx- (event: MessageEvent) => {
client/src/components/ui/sandboxed-iframe.tsx- if (event.source !== outerRef.current?.contentWindow) return;
--
client/src/components/ui/sandboxed-iframe.tsx- return (
client/src/components/ui/sandboxed-iframe.tsx- <iframe
client/src/components/ui/sandboxed-iframe.tsx- ref={outerRef}
client/src/components/ui/sandboxed-iframe.tsx: src="/api/mcp/sandbox-proxy"
client/src/components/ui/sandboxed-iframe.tsx- sandbox="allow-scripts allow-same-origin"
client/src/components/ui/sandboxed-iframe.tsx- title={title}
client/src/components/ui/sandboxed-iframe.tsx- className={className}
</shell_result>
Script executed:
#!/bin/bash
# Look for MCP or sandbox-related documentation files
fd -e md -e txt -e rst | xargs grep -l "sandbox\|SEP-1865" 2>/dev/null || echo "No matches found"Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
docs/contributing/mcp-apps-implementation.md
</shell_result>
SEP-1865 MCP Apps iframe sandbox attributes security requirements
<web_search_result>
Short summary of SEP‑1865 (MCP Apps) iframe sandbox / security requirements:
- MCP Apps require UI HTML to be rendered in sandboxed iframes — iframe sandboxing is mandatory and part of the spec’s security model. [1][2].
- Host control: hosts must enforce restrictive iframe permissions (deny elevated features by default) and only permit capabilities intentionally (and audibly) — e.g., only grant features after review/consent and only when the host’s policy/CSP allows. [1][2].
- Communication: all UI ↔ host communication uses MCP’s JSON‑RPC over postMessage so messages are auditable and proxied through the host (no direct privileged channel from iframe to host internals). Hosts must log / audit those JSON‑RPC messages and require user consent for UI‑initiated tool calls. [1][2][3].
- Predeclared templates: UI resources are predeclared (ui://) so hosts can prefetch and security‑review HTML before rendering (reduces runtime risk). [1][2].
- Recommended restrictions (explicitly called out by the spec as mitigations / best practices):
- Keep iframe sandboxed (no same-origin, no top‑navigation, no automatic popups) unless a narrowly scoped capability is required and explicitly allowed by host policy.
- Avoid granting allow‑same‑origin or other powerful flags unless absolutely necessary and after review. If allow‑scripts is needed, pair with strict Content‑Security‑Policy and host review.
- Treat external iframes / external URLs as higher risk (deferred to optional capabilities like externalIframes) and gate them with additional host controls. [1][2][3].
Sources:
- SEP‑1865 PR summary (MCP Apps) — mandatory iframe sandboxing, predeclared ui:// resources, JSON‑RPC postMessage communication, user consent & auditable messaging. [1].
- ext-apps repository (spec + SDK / examples) — implementation guidance, PostMessageTransport, hosting examples and sandboxing logic. [2].
- MCP blog announcement / community coverage summarizing security model (sandboxing, auditability, consent). [3].
References:
[1] SEP‑1865 PR: “SEP‑1865: MCP Apps — Interactive User Interfaces for MCP” (modelcontextprotocol/modelcontextprotocol).
[2] modelcontextprotocol/ext-apps repo (specification, PostMessageTransport, examples).
[3] MCP blog post “MCP Apps: Extending servers with interactive user interfaces.”
</web_search_result>
Script executed:
#!/bin/bash
# Read the sandbox-proxy.html to see the inner iframe configuration
head -100 server/routes/mcp/sandbox-proxy.html | tail -50Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
* This proxy:
* 1. Creates inner iframe immediately
* 2. Waits for HTML content via ui/notifications/sandbox-resource-ready
* 3. Loads HTML via srcdoc with configurable sandbox attributes
* 4. Forwards all non-sandbox messages between host and guest
*
* Reference: https://gist.github.com/ochafik/a9603ba2d6757d6038ce066eded4c354
*/
// Create inner iframe immediately (before HTML arrives)
const inner = document.createElement('iframe');
inner.style = 'width:100%; height:100%; border:none;';
// Default minimal sandbox before HTML arrives
inner.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
document.body.appendChild(inner);
// Handle messages from parent (host) and inner (guest UI)
window.addEventListener('message', async (event) => {
if (event.source === window.parent) {
// Message from host
if (event.data && event.data.method === 'ui/notifications/sandbox-resource-ready') {
// Load HTML into inner iframe
const { html, sandbox } = event.data.params || {};
if (typeof sandbox === 'string') {
inner.setAttribute('sandbox', sandbox);
}
if (typeof html === 'string') {
inner.srcdoc = html;
}
} else {
// Forward other messages to inner iframe (guest UI)
if (inner && inner.contentWindow) {
inner.contentWindow.postMessage(event.data, '*');
}
}
} else if (event.source === inner.contentWindow) {
// Relay messages from inner (guest UI) to parent (host)
window.parent.postMessage(event.data, '*');
}
});
// Notify parent that sandbox is ready (per SEP-1865)
window.parent.postMessage({
jsonrpc: "2.0",
method: 'ui/notifications/sandbox-ready',
params: {}
}, '*');
</script>
</body>
</html>
</shell_result>
Script executed:
#!/bin/bash
# Search for inner iframe configuration in sandbox-proxy.html
rg -A 5 -B 5 "innerFrame|inner.*iframe|guest.*iframe" server/routes/mcp/sandbox-proxy.htmlRepository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
*
* Double-iframe architecture for security isolation:
* Host Page → Sandbox Proxy (this page, different origin) → Guest UI
*
* This proxy:
* 1. Creates inner iframe immediately
* 2. Waits for HTML content via ui/notifications/sandbox-resource-ready
* 3. Loads HTML via srcdoc with configurable sandbox attributes
* 4. Forwards all non-sandbox messages between host and guest
*
* Reference: https://gist.github.com/ochafik/a9603ba2d6757d6038ce066eded4c354
*/
// Create inner iframe immediately (before HTML arrives)
const inner = document.createElement('iframe');
inner.style = 'width:100%; height:100%; border:none;';
// Default minimal sandbox before HTML arrives
inner.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
document.body.appendChild(inner);
// Handle messages from parent (host) and inner (guest UI)
window.addEventListener('message', async (event) => {
if (event.source === window.parent) {
// Message from host
if (event.data && event.data.method === 'ui/notifications/sandbox-resource-ready') {
// Load HTML into inner iframe
const { html, sandbox } = event.data.params || {};
if (typeof sandbox === 'string') {
inner.setAttribute('sandbox', sandbox);
}
if (typeof html === 'string') {
inner.srcdoc = html;
}
} else {
// Forward other messages to inner iframe (guest UI)
if (inner && inner.contentWindow) {
inner.contentWindow.postMessage(event.data, '*');
}
}
} else if (event.source === inner.contentWindow) {
</shell_result>
Script executed:
#!/bin/bash
# Check the MCPAppsRenderer to see if custom sandbox is passed
cat -n client/src/components/chat-v2/mcp-apps-renderer.tsx | head -150Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
1 /**
2 * MCPAppsRenderer - SEP-1865 MCP Apps Renderer
3 *
4 * Renders MCP Apps widgets using the SEP-1865 protocol:
5 * - JSON-RPC 2.0 over postMessage
6 * - Double-iframe sandbox architecture
7 * - tools/call, resources/read, ui/message, ui/open-link support
8 *
9 * Uses SandboxedIframe for DRY double-iframe setup.
10 */
11
12 import { useRef, useState, useEffect, useCallback } from "react";
13 import { usePreferencesStore } from "@/stores/preferences/preferences-provider";
14 import { X } from "lucide-react";
15 import {
16 SandboxedIframe,
17 SandboxedIframeHandle,
18 } from "@/components/ui/sandboxed-iframe";
19
20 type DisplayMode = "inline" | "pip" | "fullscreen";
21 type ToolState =
22 | "input-streaming"
23 | "input-available"
24 | "output-available"
25 | "output-error";
26
27 interface MCPAppsRendererProps {
28 serverId: string;
29 toolCallId: string;
30 toolName: string;
31 toolState?: ToolState;
32 toolInput?: Record<string, unknown>;
33 toolOutput?: unknown;
34 resourceUri: string;
35 toolMetadata?: Record<string, unknown>;
36 onSendFollowUp?: (text: string) => void;
37 onCallTool?: (
38 toolName: string,
39 params: Record<string, unknown>,
40 ) => Promise<unknown>;
41 onWidgetStateChange?: (toolCallId: string, state: unknown) => void;
42 pipWidgetId?: string | null;
43 onRequestPip?: (toolCallId: string) => void;
44 onExitPip?: (toolCallId: string) => void;
45 }
46
47 interface JSONRPCMessage {
48 jsonrpc: "2.0";
49 id?: number | string;
50 method?: string;
51 params?: Record<string, unknown>;
52 result?: unknown;
53 error?: { code: number; message: string };
54 }
55
56 export function MCPAppsRenderer({
57 serverId,
58 toolCallId,
59 toolName,
60 toolState,
61 toolInput,
62 toolOutput,
63 resourceUri,
64 onSendFollowUp,
65 onCallTool,
66 pipWidgetId,
67 onExitPip,
68 }: MCPAppsRendererProps) {
69 const sandboxRef = useRef<SandboxedIframeHandle>(null);
70 const themeMode = usePreferencesStore((s) => s.themeMode);
71
72 const [displayMode, setDisplayMode] = useState<DisplayMode>("inline");
73 const [contentHeight, setContentHeight] = useState<number>(400);
74 const [maxHeight] = useState<number>(600);
75 const [isReady, setIsReady] = useState(false);
76 const [loadError, setLoadError] = useState<string | null>(null);
77 const [widgetHtml, setWidgetHtml] = useState<string | null>(null);
78
79 const pendingRequests = useRef<
80 Map<
81 number | string,
82 {
83 resolve: (value: unknown) => void;
84 reject: (error: Error) => void;
85 }
86 >
87 >(new Map());
88
89 // Fetch widget HTML when tool output is available
90 useEffect(() => {
91 if (toolState !== "output-available") return;
92 if (widgetHtml) return;
93
94 const fetchWidgetHtml = async () => {
95 try {
96 // Store widget data first (same pattern as openai.ts)
97 const storeResponse = await fetch("/api/mcp/apps/widget/store", {
98 method: "POST",
99 headers: { "Content-Type": "application/json" },
100 body: JSON.stringify({
101 serverId,
102 resourceUri,
103 toolInput,
104 toolOutput,
105 toolId: toolCallId,
106 toolName,
107 theme: themeMode,
108 protocol: "mcp-apps",
109 }),
110 });
111
112 if (!storeResponse.ok) {
113 throw new Error(`Failed to store widget: ${storeResponse.statusText}`);
114 }
115
116 // Fetch the processed HTML with injected script
117 const htmlResponse = await fetch(
118 `/api/mcp/apps/widget-content/${toolCallId}`,
119 );
120 if (!htmlResponse.ok) {
121 throw new Error(
122 `Failed to fetch widget HTML: ${htmlResponse.statusText}`,
123 );
124 }
125
126 const html = await htmlResponse.text();
127 setWidgetHtml(html);
128 } catch (err) {
129 setLoadError(
130 err instanceof Error ? err.message : "Failed to prepare widget",
131 );
132 }
133 };
134
135 fetchWidgetHtml();
136 }, [
137 toolState,
138 toolCallId,
139 widgetHtml,
140 serverId,
141 resourceUri,
142 toolInput,
143 toolOutput,
144 toolName,
145 themeMode,
146 ]);
147
148 // JSON-RPC helpers
149 const postMessage = useCallback((data: unknown) => {
150 sandboxRef.current?.postMessage(data);
</shell_result>
Script executed:
#!/bin/bash
# Look for how the sandbox attribute is configured in the renderer
rg -A 10 "SandboxedIframe" client/src/components/chat-v2/mcp-apps-renderer.tsx | head -40Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
* Uses SandboxedIframe for DRY double-iframe setup.
*/
import { useRef, useState, useEffect, useCallback } from "react";
import { usePreferencesStore } from "@/stores/preferences/preferences-provider";
import { X } from "lucide-react";
import {
SandboxedIframe,
SandboxedIframeHandle,
} from "@/components/ui/sandboxed-iframe";
type DisplayMode = "inline" | "pip" | "fullscreen";
type ToolState =
| "input-streaming"
| "input-available"
| "output-available"
| "output-error";
interface MCPAppsRendererProps {
--
const sandboxRef = useRef<SandboxedIframeHandle>(null);
const themeMode = usePreferencesStore((s) => s.themeMode);
const [displayMode, setDisplayMode] = useState<DisplayMode>("inline");
const [contentHeight, setContentHeight] = useState<number>(400);
const [maxHeight] = useState<number>(600);
const [isReady, setIsReady] = useState(false);
const [loadError, setLoadError] = useState<string | null>(null);
const [widgetHtml, setWidgetHtml] = useState<string | null>(null);
const pendingRequests = useRef<
--
// Handle messages from guest UI (via SandboxedIframe)
const handleMessage = useCallback(
async (event: MessageEvent) => {
const { jsonrpc, id, method, params, result, error } =
event.data as JSONRPCMessage;
// Not a JSON-RPC message
if (jsonrpc !== "2.0") return;
</shell_result>
Script executed:
#!/bin/bash
# Check if there's any documentation explaining the threat model
rg -i "threat.*model|security.*sandbox|outer.*iframe.*origin" docs/contributing/mcp-apps-implementation.md | head -20Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
</shell_result>
Script executed:
#!/bin/bash
# Read the full SandboxedIframe component to understand how sandbox is handled
cat -n client/src/components/ui/sandboxed-iframe.tsxRepository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
1 /**
2 * SandboxedIframe - DRY Double-Iframe Sandbox Component
3 *
4 * Provides a secure double-iframe architecture for rendering untrusted HTML:
5 * Host Page → Sandbox Proxy (different origin) → Guest UI
6 *
7 * The sandbox proxy:
8 * 1. Runs in a different origin for security isolation
9 * 2. Loads guest HTML via srcdoc when ready
10 * 3. Forwards messages between host and guest (except sandbox-internal)
11 *
12 * Per SEP-1865, this component is designed to be reusable for MCP Apps
13 * and potentially future OpenAI SDK consolidation.
14 *
15 * Reference: https://gist.github.com/ochafik/a9603ba2d6757d6038ce066eded4c354
16 */
17
18 import {
19 useRef,
20 useState,
21 useEffect,
22 useCallback,
23 useImperativeHandle,
24 forwardRef,
25 } from "react";
26
27 export interface SandboxedIframeHandle {
28 postMessage: (data: unknown) => void;
29 }
30
31 interface SandboxedIframeProps {
32 /** HTML content to render in the sandbox */
33 html: string | null;
34 /** Sandbox attribute for the inner iframe */
35 sandbox?: string;
36 /** Callback when sandbox proxy is ready */
37 onProxyReady?: () => void;
38 /** Callback for messages from guest UI (excluding sandbox-internal messages) */
39 onMessage: (event: MessageEvent) => void;
40 /** CSS class for the outer iframe */
41 className?: string;
42 /** Inline styles for the outer iframe */
43 style?: React.CSSProperties;
44 /** Title for accessibility */
45 title?: string;
46 }
47
48 /**
49 * SandboxedIframe provides a secure double-iframe architecture per SEP-1865.
50 *
51 * Message flow:
52 * 1. Proxy sends ui/notifications/sandbox-ready when loaded
53 * 2. Host sends ui/notifications/sandbox-resource-ready with HTML
54 * 3. Guest UI initializes and communicates via JSON-RPC 2.0
55 */
56 export const SandboxedIframe = forwardRef<
57 SandboxedIframeHandle,
58 SandboxedIframeProps
59 >(function SandboxedIframe(
60 {
61 html,
62 sandbox = "allow-scripts allow-same-origin allow-forms allow-popups",
63 onProxyReady,
64 onMessage,
65 className,
66 style,
67 title = "Sandboxed Content",
68 },
69 ref,
70 ) {
71 const outerRef = useRef<HTMLIFrameElement>(null);
72 const [proxyReady, setProxyReady] = useState(false);
73
74 // Expose postMessage to parent
75 useImperativeHandle(
76 ref,
77 () => ({
78 postMessage: (data: unknown) => {
79 outerRef.current?.contentWindow?.postMessage(data, "*");
80 },
81 }),
82 [],
83 );
84
85 // Handle messages from sandbox proxy
86 const handleMessage = useCallback(
87 (event: MessageEvent) => {
88 if (event.source !== outerRef.current?.contentWindow) return;
89
90 const { jsonrpc, method } = (event.data as { jsonrpc?: string; method?: string }) || {};
91 if (jsonrpc !== "2.0") return;
92
93 // Sandbox ready notification (per SEP-1865)
94 if (method === "ui/notifications/sandbox-ready") {
95 setProxyReady(true);
96 onProxyReady?.();
97 return;
98 }
99
100 // Ignore other sandbox-internal messages
101 if (method?.startsWith("ui/notifications/sandbox-")) {
102 return;
103 }
104
105 // Forward all other messages to parent handler
106 onMessage(event);
107 },
108 [onMessage, onProxyReady],
109 );
110
111 useEffect(() => {
112 window.addEventListener("message", handleMessage);
113 return () => window.removeEventListener("message", handleMessage);
114 }, [handleMessage]);
115
116 // Send HTML to sandbox when ready
117 useEffect(() => {
118 if (!proxyReady || !html) return;
119
120 outerRef.current?.contentWindow?.postMessage(
121 {
122 jsonrpc: "2.0",
123 method: "ui/notifications/sandbox-resource-ready",
124 params: { html, sandbox },
125 },
126 "*",
127 );
128 }, [proxyReady, html, sandbox]);
129
130 return (
131 <iframe
132 ref={outerRef}
133 src="/api/mcp/sandbox-proxy"
134 sandbox="allow-scripts allow-same-origin"
135 title={title}
136 className={className}
137 style={style}
138 />
139 );
140 });
</shell_result>
Script executed:
#!/bin/bash
# Check how sandbox-proxy is served - look at the route configuration
cat -n server/routes/mcp/index.ts | head -50Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
1 import { Hono } from "hono";
2 import fs from "fs";
3 import path from "path";
4 import { fileURLToPath } from "url";
5 import connect from "./connect";
6 import servers from "./servers";
7 import tools from "./tools";
8 import resources from "./resources";
9 import resourceTemplates from "./resource-templates";
10 import prompts from "./prompts";
11 import chatV2 from "./chat-v2";
12 import oauth from "./oauth";
13 import exporter from "./export";
14 import evals from "./evals";
15 import { adapterHttp, managerHttp } from "./http-adapters";
16 import elicitation from "./elicitation";
17 import openai from "./openai";
18 import apps from "./apps";
19 import registry from "./registry";
20 import models from "./models";
21 import listTools from "./list-tools";
22 import tokenizer from "./tokenizer";
23 import tunnelsRoute from "./tunnels";
24
25 // ES module equivalent of __dirname
26 const __filename = fileURLToPath(import.meta.url);
27 const __dirname = path.dirname(__filename);
28
29 // Load sandbox proxy HTML at startup
30 const sandboxProxyHtml = fs.readFileSync(
31 path.join(__dirname, "sandbox-proxy.html"),
32 "utf-8",
33 );
34
35 const mcp = new Hono();
36
37 // Health check
38 mcp.get("/health", (c) => {
39 return c.json({
40 service: "MCP API",
41 status: "ready",
42 timestamp: new Date().toISOString(),
43 });
44 });
45
46 // Chat v2 endpoint
47 mcp.route("/chat-v2", chatV2);
48
49 // Elicitation endpoints
50 mcp.route("/elicitation", elicitation);
</shell_result>
Script executed:
#!/bin/bash
# Search for any CORS or origin configuration
rg -i "cors|origin|cross-origin" server/routes/mcp/index.tsRepository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 1
</shell_result>
Script executed:
#!/bin/bash
# Check the official reference sandbox proxy to see what it uses
# The gist is referenced in the code
curl -s "https://gist.githubusercontent.com/ochafik/a9603ba2d6757d6038ce066eded4c354/raw/b7f04f9b94fe8ef48fc4b955a013f4b3fb38c799/sandbox_proxy.html" 2>/dev/null | grep -A 5 -B 5 "sandbox=" | head -30Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
</shell_result>
Script executed:
#!/bin/bash
# Check if there's documentation about the threat model or security decisions
rg -i "threat|security|origin.*proxy|compromis" docs/contributing/mcp-apps-implementation.md | head -20Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
- Use a double-iframe sandbox proxy architecture for web security
| **Sandbox Proxy** | Intermediate iframe for security isolation (web hosts only) |
| `sandbox-proxy.html` | **Shared** security isolation layer |
* 1. Runs in a different origin for security isolation
The sandbox proxy provides security isolation by:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data: blob: 'unsafe-inline'; media-src * blob: data:; font-src * blob: data:; script-src 'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob: data: https://cdn.tailwindcss.com https://cdn.jsdelivr.net https://unpkg.com http://localhost:* https://localhost:*; style-src * blob: data: 'unsafe-inline'; connect-src *; frame-src * blob: data: http://localhost:* https://localhost:* http://127.0.0.1:* https://127.0.0.1:*; base-uri 'self';" />
</shell_result>
Script executed:
#!/bin/bash
# Look for any GitHub issues or discussions about this security concern
cd server && find . -name "*.md" -o -name "*.txt" | xargs grep -l "sandbox\|security" 2>/dev/null || echo "No matches"Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
No matches
</shell_result>
Script executed:
#!/bin/bash
# Check the outer iframe element more carefully - see if there's any justification in comments
rg -B 10 -A 5 'sandbox="allow-scripts allow-same-origin"' client/src/components/ui/sandboxed-iframe.tsxRepository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
params: { html, sandbox },
},
"*",
);
}, [proxyReady, html, sandbox]);
return (
<iframe
ref={outerRef}
src="/api/mcp/sandbox-proxy"
sandbox="allow-scripts allow-same-origin"
title={title}
className={className}
style={style}
/>
);
</shell_result>
Based on my verification of the codebase and SEP-1865 specification, I can now provide the rewritten review comment.
Outer iframe sandbox attribute needs clarification—possible security gap.
The outer iframe uses sandbox="allow-scripts allow-same-origin", but it is served from /api/mcp/sandbox-proxy (same-origin as the host). SEP-1865's threat model recommends avoiding allow-same-origin unless absolutely necessary. If the sandbox proxy is intended to run at a different origin for security isolation (as stated in the design doc), it should be served from a separate subdomain. If it remains same-origin, the allow-same-origin attribute should either be justified or removed, as it grants the trusted proxy access to parent context without an origin boundary. Verify this aligns with the intended threat model and update the code or documentation accordingly.
🤖 Prompt for AI Agents
client/src/components/ui/sandboxed-iframe.tsx around lines 130–139: the outer
iframe is created with sandbox="allow-scripts allow-same-origin" while
src="/api/mcp/sandbox-proxy" is same-origin, which defeats origin isolation per
SEP-1865; either host the sandbox-proxy on a separate origin/subdomain and
update src to that origin (and adjust CORS/cookie settings) or remove the
allow-same-origin token (and document/justify why same-origin is required), and
update the component docs and any deployment config to reflect the chosen threat
model.
| } catch (error) { | ||
| console.error("[MCP Apps] Error fetching resource:", error); | ||
| return c.html( | ||
| `<html><body>Error: ${error instanceof Error ? error.message : "Unknown error"}</body></html>`, | ||
| 500, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message may leak internal details.
Returning error.message directly to clients could expose stack traces or sensitive paths. Consider a generic message for 500 errors.
🤖 Prompt for AI Agents
In server/routes/mcp/apps.ts around lines 164 to 170, the catch block currently
returns error.message to the client which may leak internal details; instead,
log the full error server-side (including stack if available) and return a
generic 500 response body (e.g. "Internal server error") to the client; remove
any propagation of the raw error message into the HTML response and ensure the
console/process logger records the detailed error for debugging.
| sendResponse(id, { | ||
| protocolVersion: "2025-06-18", | ||
| hostCapabilities: {}, | ||
| hostInfo: { name: "mcpjam-inspector", version: "1.0.0" }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this version number accurate to where we will be with this deployment
khandrew1
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
|
run prettier and test on prod before merging |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
client/src/components/connection/ServerConnectionCard.tsx (1)
1-1: Run Prettier before merge.CI flagged formatting issues. As noted in PR comments, run
npm run prettier-fixto resolve.
♻️ Duplicate comments (5)
client/src/components/ui/sandboxed-iframe.tsx (1)
131-140: Outer iframe sandbox attribute warrants verification.The
sandbox="allow-scripts allow-same-origin"attribute on the outer iframe, combined with same-origin serving of/api/mcp/sandbox-proxy, has been flagged as a potential security gap in prior review. Please confirm this aligns with the intended threat model or move the proxy to a distinct origin.client/src/components/tools/ResultsPanel.tsx (1)
244-246: UnstabletoolCallIdfallback generates new ID on each render.Using
Date.now()directly in JSX creates a fresh ID per render. UnlikeOpenAIAppRendererwhich usesuseMemofor a stable fallback, this implementation will cause widget state key mismatches.Apply a stable fallback:
+import { useMemo } from "react"; + +// Inside component, before the render block: +const stableToolCallId = useMemo( + () => toolCallId || `tools-tab-${Date.now()}`, + [toolCallId] +); // Then use stableToolCallId in the renderer: - toolCallId={toolCallId || `tools-tab-${Date.now()}`} + toolCallId={stableToolCallId}client/src/components/chat-v2/mcp-apps-renderer.tsx (3)
89-148: Acknowledged: Missing fetch cancellation on unmount.This concern was raised in a previous review and remains valid. The effect should use an
AbortControllerto cancel in-flight requests when the component unmounts or dependencies change.
208-208: Acknowledged: Version number accuracy.This was flagged in a previous review comment requesting the version be updated to reflect the actual deployment version.
249-267: Acknowledged: Missing HTTP error check in resources/read.This was identified in a previous review. The handler calls
response.json()without checkingresponse.ok, causing HTTP error responses to be forwarded as successful results to the guest UI instead of being transformed into JSON-RPC errors.
🧹 Nitpick comments (2)
client/src/components/chat-v2/mcp-apps-renderer.tsx (2)
27-45: Consider adding JSDoc comments for the props interface.The
MCPAppsRendererPropsinterface would benefit from documentation comments describing each prop's purpose, especially for callbacks likeonCallToolandonWidgetStateChangewhose expected behavior and return values affect integration.
74-74: ReplaceuseStatewithconstfor immutable values.
maxHeightnever changes and should be a constant rather than state, avoiding unnecessary re-renders.Apply this diff:
- const [maxHeight] = useState<number>(600); + const maxHeight = 600;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
client/src/components/chat-v2/mcp-apps-renderer.tsx(1 hunks)client/src/components/chat-v2/thread.tsx(2 hunks)client/src/components/connection/ServerConnectionCard.tsx(2 hunks)client/src/components/connection/ServerInfoModal.tsx(3 hunks)client/src/components/tools/ResultsPanel.tsx(4 hunks)client/src/components/ui/sandboxed-iframe.tsx(1 hunks)server/routes/mcp/sandbox-proxy.html(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- server/routes/mcp/sandbox-proxy.html
🧰 Additional context used
📓 Path-based instructions (2)
client/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
client/**/components/**/*.{ts,tsx}: Use functional React components with React.FC typing
Implement strict TypeScript types for React component props with Props interface definitions
Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Design responsive layouts using Tailwind breakpoint system, grid layouts, flex containers, and container queries
Ensure accessibility compliance with ARIA attributes, keyboard navigation, focus management, and screen reader support
Files:
client/src/components/connection/ServerInfoModal.tsxclient/src/components/connection/ServerConnectionCard.tsxclient/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/thread.tsxclient/src/components/ui/sandboxed-iframe.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
client/**/{components,hooks}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Files:
client/src/components/connection/ServerInfoModal.tsxclient/src/components/connection/ServerConnectionCard.tsxclient/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/thread.tsxclient/src/components/ui/sandboxed-iframe.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
🧠 Learnings (16)
📓 Common learnings
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Applied to files:
client/src/components/connection/ServerInfoModal.tsxclient/src/components/connection/ServerConnectionCard.tsxclient/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/thread.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
Applied to files:
client/src/components/connection/ServerInfoModal.tsxclient/src/components/connection/ServerConnectionCard.tsxclient/src/components/chat-v2/thread.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/prompts/**/*.ts : Validate prompt context, token limits, and format in prompt processing handlers
Applied to files:
client/src/components/connection/ServerInfoModal.tsx
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/types/mcp.ts : Define MCP protocol types in TypeScript type definition files
Applied to files:
client/src/components/connection/ServerConnectionCard.tsxclient/src/components/chat-v2/thread.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/tools/**/*.ts : Validate tool schemas according to MCP spec including parameters and response formats
Applied to files:
client/src/components/tools/ResultsPanel.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Applied to files:
client/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/chat/**/*.{ts,tsx} : Implement message threading with thread components, collapse/expand functionality, navigation, and search capabilities
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/chat/**/*.{ts,tsx} : Build chat interface with message components, input handling, stream rendering, and history management
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/chat/**/*.{ts,tsx} : Implement code highlighting with syntax detection, theme support, copy functionality, and line numbers in code components
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{stores/chat,hooks/llm}/**/*.{ts,tsx} : Manage context with window size tracking, token counting, context pruning, and state persistence for chat sessions
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/chat/**/*.{ts,tsx} : Implement AI model state handling including model selection state, generation parameters, stream management, and history persistence
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{hooks/llm,components/chat}/**/*.{ts,tsx} : Implement response streaming with token processing, UI updates, cancel handling, and error states
Applied to files:
client/src/components/chat-v2/thread.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{components,hooks}/**/*.{ts,tsx} : Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Applied to files:
client/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/resources/**/*.ts : Implement resource schema compliance checking and type validation in resource handlers
Applied to files:
client/src/components/chat-v2/mcp-apps-renderer.tsx
🧬 Code graph analysis (3)
client/src/components/tools/ResultsPanel.tsx (1)
client/src/components/chat-v2/mcp-apps-renderer.tsx (1)
MCPAppsRenderer(56-422)
client/src/components/chat-v2/thread.tsx (3)
client/src/lib/mcp-apps-utils.ts (1)
detectUIType(24-61)client/src/lib/mcp-tools-api.ts (2)
getToolServerId(116-121)callTool(71-89)client/src/components/chat-v2/mcp-apps-renderer.tsx (1)
MCPAppsRenderer(56-422)
client/src/components/chat-v2/mcp-apps-renderer.tsx (2)
client/src/components/ui/sandboxed-iframe.tsx (2)
SandboxedIframeHandle(27-29)SandboxedIframe(56-141)client/src/stores/preferences/preferences-provider.tsx (1)
usePreferencesStore(30-36)
🪛 GitHub Actions: Prettier and Build Check
client/src/components/connection/ServerInfoModal.tsx
[warning] 1-1: Code style issues found by Prettier. Run 'npm run prettier-fix' to fix formatting.
client/src/components/connection/ServerConnectionCard.tsx
[warning] 1-1: Code style issues found by Prettier. Run 'npm run prettier-fix' to fix formatting.
🔍 Remote MCP Context7
Relevant findings to help review MCP Apps PR #976
- MCP-UI docs confirm the common pattern used in the PR: sandboxed iframe hosts UI HTML and communicates with host via postMessage JSON-RPC; supporting async actions using messageId for request/response tracking (examples, messageId pattern, Resize/size-change, tool-call flows). This supports the PR’s JSON-RPC over postMessage and pending-request handling in MCPAppsRenderer and the injected client script.
- MCP-UI docs describe three content types (raw HTML, external URLs, Remote DOM) and emphasize security via iframe sandboxing and proxying for restrictive CSPs — relevant to SandboxedIframe and sandbox-proxy.html usage and to review CSP/sandbox attributes and proxy readiness handling.
- Guidance on asynchronous actions (messageId) and using pending maps/timeouts matches the PR’s pending JSON-RPC request map and timeout handling; reviewers should verify consistent messageId handling, timeouts, and error codes between injected script, SandboxedIframe, and MCPAppsRenderer.
- The MCP-UI client README and protocol docs document patterns for forwarding iframe actions to host (tool calls, resource reads, open-link) and host replies — corroborates PR handlers (ui/initialize, tools/call, resources/read, ui/open-link, ui/message). Reviewers should check parity with MCP-UI semantics (method names, error codes, event names).
Tools/sources used
- Context7_resolve-library-id (lookup for "@mcp-ui/client") — returned MCP-UI candidates and IDs.
- Context7_get-library-docs (/idosal/mcp-ui, docs & README) — protocol details, async messageId guidance, sandboxing and rendering patterns.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (19)
client/src/components/chat-v2/thread.tsx (2)
32-36: Imports align with the new MCP Apps pathway.Clean additions that enable detection and rendering of MCP Apps widgets.
246-304: MCP Apps rendering pathway is well-structured.The detection, error handling, and renderer integration follow established patterns. The dual check on line 253 (
uiType === "mcp-apps" && partToolMeta?.["ui/resourceUri"]) is redundant sincedetectUITypealready verifies the presence ofui/resourceUri, but it adds defensive clarity.client/src/components/connection/ServerConnectionCard.tsx (2)
98-110: Detection flags correctly identify MCP Apps and OpenAI Apps.The logic appropriately prioritizes MCP Apps when both metadata types are present.
396-411: Icon precedence is correctly implemented.MCP App icon takes priority; OpenAI icon renders only when
!isMCPApp. This aligns with the detection hierarchy established elsewhere.client/src/components/connection/ServerInfoModal.tsx (2)
65-80: Unified widget metadata detection is well-designed.The
hasWidgetMetadataflag elegantly combines both app types for tab visibility decisions.
237-252: Icon and tab logic properly generalized.Renaming to "Tools Metadata" reflects the broader scope beyond just OpenAI apps.
Also applies to: 256-260
client/src/components/tools/ResultsPanel.tsx (3)
103-106: MCP Apps detection mirrors established pattern.Consistent with the approach used throughout this PR.
131-153: Toggle button labels appropriately generalized."Component" and "Raw JSON" labels now apply to both MCP Apps and OpenAI components.
253-277:onCallToolhandler correctly transforms response.Error cases throw exceptions as expected by the MCP Apps protocol. The structure returned for completed calls aligns with MCPAppsRenderer expectations.
client/src/components/ui/sandboxed-iframe.tsx (4)
1-16: Clear documentation establishes component purpose.The header succinctly describes the double-iframe architecture and SEP-1865 compliance.
56-70: Well-structured component signature with sensible defaults.The default
sandboxprop value includes necessary permissions for typical widget functionality.
74-83: Imperative handle correctly exposespostMessage.Empty dependency array is appropriate since
outerRefis stable.
85-110: Message handler implements SEP-1865 protocol correctly.The filtering of sandbox-internal messages (
ui/notifications/sandbox-*) before forwarding is precise.client/src/components/chat-v2/mcp-apps-renderer.tsx (6)
150-176: LGTM: JSON-RPC helpers are well-structured.The helper functions correctly implement JSON-RPC 2.0 patterns with proper notifications (no
id) and responses (withid), and use appropriateuseCallbackmemoization.
177-339: Message handler structure follows JSON-RPC 2.0 correctly.The handler properly discriminates between responses (id without method), requests (id with method), and notifications (method without id), and implements appropriate error codes per the JSON-RPC specification.
Note: This approval assumes the previously flagged issues (version number and resources/read error handling) will be addressed.
341-345: LGTM: Theme synchronization follows SEP-1865.The effect correctly notifies the guest UI of theme changes using
ui/host-context-change, guarded by theisReadyflag to ensure the guest is initialized first.
347-370: LGTM: Clear loading and error states.The early returns provide appropriate feedback for each stage (waiting, error, preparing) with consistent styling and clear messaging.
407-407: Verify sandbox attribute security for MCP Apps.The iframe uses
"allow-scripts allow-same-origin allow-forms allow-popups". Theallow-same-originflag permits the sandboxed content to access its own origin's storage and APIs, which could pose security risks if the content is not fully trusted. Confirm this aligns with the MCP Apps security model and CSP requirements.Based on external context highlighting the importance of sandbox attributes and CSP for MCP-UI security.
388-421: LGTM: Rendering logic handles all display modes correctly.The component appropriately handles inline, pip, and fullscreen display modes with proper styling, accessibility (aria-label, keyboard-accessible close button), and height constraints.
| const pendingRequests = useRef< | ||
| Map< | ||
| number | string, | ||
| { | ||
| resolve: (value: unknown) => void; | ||
| reject: (error: Error) => void; | ||
| } | ||
| > | ||
| >(new Map()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add cleanup for pending JSON-RPC requests on unmount.
If the component unmounts while requests are pending, the promises will never resolve or reject, causing memory leaks. The handlers in pendingRequests hold closure references that should be cleaned up.
Add a cleanup effect:
+ useEffect(() => {
+ return () => {
+ pendingRequests.current.forEach((pending, id) => {
+ pending.reject(new Error("Component unmounted"));
+ });
+ pendingRequests.current.clear();
+ };
+ }, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const pendingRequests = useRef< | |
| Map< | |
| number | string, | |
| { | |
| resolve: (value: unknown) => void; | |
| reject: (error: Error) => void; | |
| } | |
| > | |
| >(new Map()); | |
| const pendingRequests = useRef< | |
| Map< | |
| number | string, | |
| { | |
| resolve: (value: unknown) => void; | |
| reject: (error: Error) => void; | |
| } | |
| > | |
| >(new Map()); | |
| useEffect(() => { | |
| return () => { | |
| pendingRequests.current.forEach((pending, id) => { | |
| pending.reject(new Error("Component unmounted")); | |
| }); | |
| pendingRequests.current.clear(); | |
| }; | |
| }, []); |
🤖 Prompt for AI Agents
In client/src/components/chat-v2/mcp-apps-renderer.tsx around lines 79 to 87,
the pendingRequests Map created with useRef is not cleaned up on component
unmount, leaving unresolved promises and closure references; add a useEffect
with a cleanup function that iterates pendingRequests.current, calls each stored
reject handler with a descriptive Error (e.g. "Component unmounted") so promises
settle, clears the Map, and removes references to prevent leaks. Ensure the
cleanup runs only on unmount (empty dependency array) and that you handle
possible absent reject handlers safely.
Removed outdated reference link from comments. Signed-off-by: marcelo <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
client/src/components/ui/sandboxed-iframe.tsx(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
client/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
client/**/components/**/*.{ts,tsx}: Use functional React components with React.FC typing
Implement strict TypeScript types for React component props with Props interface definitions
Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Design responsive layouts using Tailwind breakpoint system, grid layouts, flex containers, and container queries
Ensure accessibility compliance with ARIA attributes, keyboard navigation, focus management, and screen reader support
Files:
client/src/components/ui/sandboxed-iframe.tsx
client/**/{components,hooks}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Files:
client/src/components/ui/sandboxed-iframe.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/types/mcp.ts : Define MCP protocol types in TypeScript type definition files
🪛 GitHub Actions: Prettier and Build Check
client/src/components/ui/sandboxed-iframe.tsx
[warning] 1-1: Code style issues found. Run Prettier with --write to fix.
🔍 Remote MCP Context7
Relevant protocol facts from the MCP-UI docs that affect review of this PR:
-
MCP-UI uses postMessage JSON-RPC-style messages with optional messageId for async request/response flows — hosts should implement pending-request maps and timeouts (aligns with PR’s pending JSON-RPC handling).,
-
UI resources may be delivered as HTML/URI; when using mimeType 'text/uri-list' MCP-UI requires a single http(s) URL (security requirement). Verify the PR’s resource handling and URL validation/constraints.
-
Sandboxed iframe + proxy patterns are recommended for restrictive CSPs and security; adapters translate mcp-ui postMessage semantics to host APIs (docs reference host adapters). Confirm PR’s SandboxedIframe + sandbox-proxy.html align with recommended double-iframe proxying and adapter expectations.
-
Docs explicitly document size/resize notifications and use of messageId for asynchronous tool calls and events — ensure PR’s ui/size-change, ResizeObserver usage, and messageId handling/error codes/timeouts are consistent across injected script, SandboxedIframe, and MCPAppsRenderer.
-
Security note: MCP-UI emphasizes secure loading (http/s), sandbox attributes/CSP, and careful injection of inline scripts. Review serializeForInlineScript, CSP headers, and sandbox attribute transitions in sandbox-proxy for XSS/escape correctness.
Sources:
- Context7 library list lookup for "mcp-ui" (selected candidate /idosal/mcp-ui) — Context7_resolve-library-id.
- MCP-UI docs (protocol details, embeddable UI, async messageId, sandboxing guidance) — Context7_get-library-docs (/idosal/mcp-ui, protocol/topic).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
| interface SandboxedIframeProps { | ||
| /** HTML content to render in the sandbox */ | ||
| html: string | null; | ||
| /** Sandbox attribute for the inner iframe */ | ||
| sandbox?: string; | ||
| /** Callback when sandbox proxy is ready */ | ||
| onProxyReady?: () => void; | ||
| /** Callback for messages from guest UI (excluding sandbox-internal messages) */ | ||
| onMessage: (event: MessageEvent) => void; | ||
| /** CSS class for the outer iframe */ | ||
| className?: string; | ||
| /** Inline styles for the outer iframe */ | ||
| style?: React.CSSProperties; | ||
| /** Title for accessibility */ | ||
| title?: string; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Script executed:
#!/bin/bash
# Read the sandboxed-iframe.tsx file to check imports and usage
cat -n client/src/components/ui/sandboxed-iframe.tsx | head -60Repository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
1 /**
2 * SandboxedIframe - DRY Double-Iframe Sandbox Component
3 *
4 * Provides a secure double-iframe architecture for rendering untrusted HTML:
5 * Host Page → Sandbox Proxy (different origin) → Guest UI
6 *
7 * The sandbox proxy:
8 * 1. Runs in a different origin for security isolation
9 * 2. Loads guest HTML via srcdoc when ready
10 * 3. Forwards messages between host and guest (except sandbox-internal)
11 *
12 * Per SEP-1865, this component is designed to be reusable for MCP Apps
13 * and potentially future OpenAI SDK consolidation.
14 */
15
16 import {
17 useRef,
18 useState,
19 useEffect,
20 useCallback,
21 useImperativeHandle,
22 forwardRef,
23 } from "react";
24
25 export interface SandboxedIframeHandle {
26 postMessage: (data: unknown) => void;
27 }
28
29 interface SandboxedIframeProps {
30 /** HTML content to render in the sandbox */
31 html: string | null;
32 /** Sandbox attribute for the inner iframe */
33 sandbox?: string;
34 /** Callback when sandbox proxy is ready */
35 onProxyReady?: () => void;
36 /** Callback for messages from guest UI (excluding sandbox-internal messages) */
37 onMessage: (event: MessageEvent) => void;
38 /** CSS class for the outer iframe */
39 className?: string;
40 /** Inline styles for the outer iframe */
41 style?: React.CSSProperties;
42 /** Title for accessibility */
43 title?: string;
44 }
45
46 /**
47 * SandboxedIframe provides a secure double-iframe architecture per SEP-1865.
48 *
49 * Message flow:
50 * 1. Proxy sends ui/notifications/sandbox-ready when loaded
51 * 2. Host sends ui/notifications/sandbox-resource-ready with HTML
52 * 3. Guest UI initializes and communicates via JSON-RPC 2.0
53 */
54 export const SandboxedIframe = forwardRef<
55 SandboxedIframeHandle,
56 SandboxedIframeProps
57 >(function SandboxedIframe(
58 {
59 html,
60 sandbox = "allow-scripts allow-same-origin allow-forms allow-popups",
</shell_result>
Script executed:
#!/bin/bash
# Check if there are other uses of React namespace in the file
rg "React\." client/src/components/ui/sandboxed-iframe.tsxRepository: MCPJam/inspector
<shell_result>
Repository: MCPJam/inspector
Exit code: 0
stdout:
style?: React.CSSProperties;
</shell_result>
Import CSSProperties type-only from React; remove React.CSSProperties reference.
The React namespace is not imported in this file (lines 16–23 only import specific exports). Using React.CSSProperties on line 41 will cause a TypeScript compilation error. Import CSSProperties as a type-only import instead:
+import type { CSSProperties } from "react";
import {
useRef,
useState,
useEffect,
useCallback,
useImperativeHandle,
forwardRef,
} from "react";Then update line 41:
- style?: React.CSSProperties;
+ style?: CSSProperties;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| interface SandboxedIframeProps { | |
| /** HTML content to render in the sandbox */ | |
| html: string | null; | |
| /** Sandbox attribute for the inner iframe */ | |
| sandbox?: string; | |
| /** Callback when sandbox proxy is ready */ | |
| onProxyReady?: () => void; | |
| /** Callback for messages from guest UI (excluding sandbox-internal messages) */ | |
| onMessage: (event: MessageEvent) => void; | |
| /** CSS class for the outer iframe */ | |
| className?: string; | |
| /** Inline styles for the outer iframe */ | |
| style?: React.CSSProperties; | |
| /** Title for accessibility */ | |
| title?: string; | |
| } | |
| import type { CSSProperties } from "react"; | |
| import { | |
| useRef, | |
| useState, | |
| useEffect, | |
| useCallback, | |
| useImperativeHandle, | |
| forwardRef, | |
| } from "react"; | |
| export interface SandboxedIframeHandle { | |
| postMessage: (data: unknown) => void; | |
| } | |
| interface SandboxedIframeProps { | |
| /** HTML content to render in the sandbox */ | |
| html: string | null; | |
| /** Sandbox attribute for the inner iframe */ | |
| sandbox?: string; | |
| /** Callback when sandbox proxy is ready */ | |
| onProxyReady?: () => void; | |
| /** Callback for messages from guest UI (excluding sandbox-internal messages) */ | |
| onMessage: (event: MessageEvent) => void; | |
| /** CSS class for the outer iframe */ | |
| className?: string; | |
| /** Inline styles for the outer iframe */ | |
| style?: CSSProperties; | |
| /** Title for accessibility */ | |
| title?: string; | |
| } |
🤖 Prompt for AI Agents
In client/src/components/ui/sandboxed-iframe.tsx around lines 29 to 44, the
props interface references React.CSSProperties but the React namespace isn't
imported, causing a TS compile error; import CSSProperties as a type-only import
from 'react' (e.g. `import type { CSSProperties } from 'react'`) at the top of
the file and change the style prop type to use that imported type (style?:
CSSProperties) so the file compiles without adding a runtime React import.
| sendRequest('ui/initialize', { | ||
| capabilities: {}, | ||
| clientInfo: { name: 'MCP App Widget', version: '1.0.0' }, | ||
| protocolVersion: '2025-06-18', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Protocol version mismatch between widget and host
The MCP Apps widget script (injected by the server) sends protocolVersion: '2025-06-18' during initialization, but the host in mcp-apps-renderer.tsx responds with protocolVersion: "2025-11-25". This mismatch between the widget's declared protocol version and the host's response could cause compatibility issues. The PR discussion specifically noted that this version number needs to be accurate for deployment.
Additional Locations (1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (3)
client/src/components/chat-v2/mcp-apps-renderer.tsx (3)
92-151: Add abort/cleanup tofetchWidgetHtmleffect to avoid stale state updatesThe widget-loading effect currently doesn’t cancel in‑flight fetches. If
toolStateor deps change, or the component unmounts mid‑request, the latesetWidgetHtml/setLoadErrorcalls can race against newer state.Adding an
AbortControllerper effect run and bailing onAbortErrorwould align with your hooks cleanup guidelines and keep this fetch strictly scoped to the active render:useEffect(() => { if (toolState !== "output-available") return; if (widgetHtml) return; - const fetchWidgetHtml = async () => { + const controller = new AbortController(); + + const fetchWidgetHtml = async () => { try { const storeResponse = await fetch("/api/mcp/apps/widget/store", { method: "POST", headers: { "Content-Type": "application/json" }, + signal: controller.signal, body: JSON.stringify({ serverId, resourceUri, toolInput, toolOutput, toolId: toolCallId, toolName, theme: themeMode, protocol: "mcp-apps", }), }); @@ - const htmlResponse = await fetch( - `/api/mcp/apps/widget-content/${toolCallId}`, - ); + const htmlResponse = await fetch( + `/api/mcp/apps/widget-content/${toolCallId}`, + { signal: controller.signal }, + ); @@ - } catch (err) { - setLoadError( - err instanceof Error ? err.message : "Failed to prepare widget", - ); - } + } catch (err) { + if (err instanceof Error && err.name === "AbortError") return; + setLoadError( + err instanceof Error ? err.message : "Failed to prepare widget", + ); + } }; fetchWidgetHtml(); - }, [ + return () => controller.abort(); + }, [ toolState, @@ themeMode, ]);
82-91: EnsurependingRequestsis cleaned up on unmount (and consider wiring it or removing it)
pendingRequestsis never cleared, so once you start populating it withresolve/rejecthandlers, an unmount with in‑flight JSON‑RPC calls would leave dangling promises and retained closures. A simple unmount cleanup both future‑proofs the Map and matches the intent of the existing structure:const pendingRequests = useRef< Map< number | string, { resolve: (value: unknown) => void; reject: (error: Error) => void; } > >(new Map()); + useEffect(() => { + return () => { + pendingRequests.current.forEach(({ reject }, id) => { + try { + reject(new Error("MCPAppsRenderer unmounted")); + } catch { + // Defensive: ignore any errors from userland reject handlers + } + }); + pendingRequests.current.clear(); + }; + }, []);If you don’t plan to send host‑initiated requests yet, an alternative is to remove
pendingRequestsentirely until it’s actually used.Also applies to: 180-342
252-270: Map HTTP failures from/api/mcp/resources/readto JSON‑RPC errors, not resultsRight now, any non‑2xx response is parsed and returned via
result, so HTTP‑level failures are indistinguishable from successful JSON‑RPC responses to the widget. This is inconsistent with your widget‑fetch path (which checksresponse.ok) and makes error handling in MCP Apps brittle.Treat non‑OK responses as JSON‑RPC errors and only send
resultwhen the HTTP call succeeds:case "resources/read": { try { const readParams = params as { uri: string }; const response = await fetch(`/api/mcp/resources/read`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ serverId, uri: readParams.uri }), }); - const result = await response.json(); - sendResponse(id, result); + if (!response.ok) { + let message = `Resource read failed (${response.status})`; + try { + const body = await response.json(); + if (body && typeof body.error === "string") { + message = body.error; + } + } catch { + // Non‑JSON error body; keep default message + } + sendResponse(id, undefined, { + code: -32000, + message, + }); + } else { + const result = await response.json(); + sendResponse(id, result); + } } catch (err) { sendResponse(id, undefined, { code: -32000, message: err instanceof Error ? err.message : "Resource read failed", }); } break; }
🧹 Nitpick comments (1)
client/vite.config.ts (1)
12-17: Version wiring viapackage.jsonis solid; consider a small guard for robustnessReading the version from the root
package.jsonand injecting it into__APP_VERSION__is a clean, build‑time‑only solution and fits Vite’sdefinepattern.If you want a bit more resilience against a missing or malformed
versionfield, you could add a defensive default:-const appVersion = packageJson.version; +const appVersion = + typeof packageJson.version === "string" && packageJson.version.length > 0 + ? packageJson.version + : "0.0.0-dev";(adjust the fallback string to whatever makes sense for your release process).
Also applies to: 115-115
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
client/src/components/chat-v2/mcp-apps-renderer.tsx(1 hunks)client/src/components/tools/ResultsPanel.tsx(4 hunks)client/vite.config.ts(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
client/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
client/**/components/**/*.{ts,tsx}: Use functional React components with React.FC typing
Implement strict TypeScript types for React component props with Props interface definitions
Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Design responsive layouts using Tailwind breakpoint system, grid layouts, flex containers, and container queries
Ensure accessibility compliance with ARIA attributes, keyboard navigation, focus management, and screen reader support
Files:
client/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
client/**/{components,hooks}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Files:
client/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
🧠 Learnings (9)
📓 Common learnings
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Applied to files:
client/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Applied to files:
client/src/components/tools/ResultsPanel.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/**/*.{ts,tsx} : Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Applied to files:
client/src/components/tools/ResultsPanel.tsxclient/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-25T19:59:26.527Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/types/mcp.ts : Define MCP protocol types in TypeScript type definition files
Applied to files:
client/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
Applied to files:
client/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{components,hooks}/**/*.{ts,tsx} : Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Applied to files:
client/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/resources/**/*.ts : Implement resource schema compliance checking and type validation in resource handlers
Applied to files:
client/src/components/chat-v2/mcp-apps-renderer.tsx
📚 Learning: 2025-11-24T17:54:18.310Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.310Z
Learning: Use Vite + React for fast development and optimized builds
Applied to files:
client/vite.config.ts
🧬 Code graph analysis (2)
client/src/components/tools/ResultsPanel.tsx (1)
client/src/components/chat-v2/mcp-apps-renderer.tsx (1)
MCPAppsRenderer(59-425)
client/src/components/chat-v2/mcp-apps-renderer.tsx (3)
client/src/components/ui/sandboxed-iframe.tsx (2)
SandboxedIframeHandle(25-27)SandboxedIframe(54-139)client/src/stores/preferences/preferences-provider.tsx (1)
usePreferencesStore(30-36)bin/start.js (1)
value(354-354)
🪛 GitHub Actions: Prettier and Build Check
client/vite.config.ts
[warning] 1-1: Code style issue detected by Prettier. Run 'npm run prettier-fix' to fix.
🔍 Remote MCP Context7
Relevant protocol and security facts to check during review (from MCP-UI docs):
-
UI resource delivery: when using mimeType 'text/uri-list', MCP-UI requires a single http(s) URL (security requirement). Verify server GET /widget-content/:toolId and any URI handling enforce/validate http(s) single-URL constraints.
-
JSON-RPC async flows: messages may include messageId for async request/response; hosts should implement pending-request maps and timeouts. Confirm injected script, SandboxedIframe, and MCPAppsRenderer maintain consistent messageId handling, pending maps, and timeouts across requests (tools/call, resources/read, etc.).
-
Sandbox/proxy pattern & adapters: docs recommend double-iframe proxying and host adapters that translate postMessage semantics to host APIs. Ensure sandbox-proxy.html + SandboxedIframe follow adapter expectations and that proxy sandbox attribute transitions / CSP align with recommendations.
-
Security guidance: validate escaping/serialization used for inline script injection (serializeForInlineScript), CSP handling, and sandbox attribute changes to reduce XSS risk. Confirm correct MIME, Content-Type, and caching headers when serving modified HTML.
-
Platform/adapters note: MCP-UI SDKs mention host-specific adapters for compatibility; review whether the injected window.mcpApp API and bridge match SDK expectations (methods, event names, error codes).
Tool resolution used to locate docs: resolved "mcp-ui" library candidates (picked /idosal/mcp-ui as relevant doc source).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (3)
client/vite.config.ts (1)
1-117: Address the Prettier failure reported by CIThe pipeline reports a Prettier issue on this file; it’s likely just formatting around the new import or version block. Please run your standard formatter (e.g.
npm run prettier-fix) to align with the repo’s style before merging.client/src/components/tools/ResultsPanel.tsx (2)
99-103: Stable fallbacktoolCallIdand unified toggle logic look solidThe memoized
resolvedToolCallIdgives you a stable, per-result fallback ID and avoids the prior re-render churn, while the updated toggle conditions and labels now sensibly treat MCP Apps as first-class “Component” renderers alongside OpenAI. The wiring is coherent and matches the intended UX.Also applies to: 110-113, 138-160
247-284: MCPAppsRenderer integration andonCallTooladapter are well-shapedPreferring MCPAppsRenderer when
hasMCPAppsComponentand!showStructuredmatches the spec intent, and theonCallTooladapter cleanly normalizesToolExecutionResponseinto{ content, structuredContent }while surfacing errors and non-completedstatuses as exceptions. This is a good, minimal bridge between the tools API and SEP‑1865.
| case "ui/open-link": { | ||
| const linkParams = params as { url?: string }; | ||
| if (linkParams.url) { | ||
| window.open(linkParams.url, "_blank", "noopener,noreferrer"); | ||
| } | ||
| sendResponse(id, {}); | ||
| break; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Read the specific file and lines in question
sed -n '260,290p' client/src/components/chat-v2/mcp-apps-renderer.tsxRepository: MCPJam/inspector
Length of output: 1033
🏁 Script executed:
#!/bin/bash
# Search for existing URL validation patterns or utilities
rg -n "protocol|url.*http|URL\(" client/src --type=ts --type=tsx -C 2 | head -100Repository: MCPJam/inspector
Length of output: 87
🏁 Script executed:
#!/bin/bash
# Search for existing URL validation or window.open patterns
rg -n "window\.open|new URL|protocol.*http|validateUrl" client/src -C 2 | head -150Repository: MCPJam/inspector
Length of output: 11040
🏁 Script executed:
#!/bin/bash
# Check for any URL security utility functions
rg -n "validateUrl|isValidUrl|isSafeUrl|sanitizeUrl" client/srcRepository: MCPJam/inspector
Length of output: 42
Validate URL schemes in ui/open-link to prevent execution of dangerous protocols
The ui/open-link handler currently forwards URLs from guest UI directly to window.open() without validation. A compromised or buggy MCP App could emit javascript:, data:, or other non-http(s) URLs, circumventing sandbox protections.
Constrain this to safe schemes (http/https) and ignore or log anything else:
case "ui/open-link": {
const linkParams = params as { url?: string };
- if (linkParams.url) {
- window.open(linkParams.url, "_blank", "noopener,noreferrer");
- }
+ if (linkParams.url) {
+ try {
+ const url = new URL(linkParams.url, window.location.origin);
+ if (url.protocol === "http:" || url.protocol === "https:") {
+ window.open(url.toString(), "_blank", "noopener,noreferrer");
+ } else {
+ console.warn(
+ "[MCP Apps] Blocked non-http(s) URL in ui/open-link:",
+ linkParams.url,
+ );
+ }
+ } catch {
+ console.warn(
+ "[MCP Apps] Invalid URL in ui/open-link:",
+ linkParams.url,
+ );
+ }
+ }
sendResponse(id, {});
break;
}The same pattern appears unvalidated in thread.tsx (line 665), openai-app-renderer.tsx (line 382), and ResultsPanel.tsx (line 361); consider applying similar validation across these renderers for consistency.
🤖 Prompt for AI Agents
In client/src/components/chat-v2/mcp-apps-renderer.tsx around lines 272-279, the
handler forwards guest-provided URLs directly to window.open which allows
dangerous schemes; update it to validate the URL by attempting to construct a
URL object (catching errors), then only call window.open if url.protocol is
exactly "http:" or "https:" (case-insensitive); otherwise log or silently ignore
the open attempt and still sendResponse(id, {}); apply the same URL parsing +
protocol whitelist pattern to the other spots mentioned (thread.tsx line 665,
openai-app-renderer.tsx line 382, ResultsPanel.tsx line 361) for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
client/src/components/connection/ServerInfoModal.tsx (1)
65-80: Replaceanytypes with proper TypeScript types.The metadata objects are typed as
any, which bypasses type safety. Consider defining a proper interface for tool metadata or importing the type from@/lib/mcp-tools-api.Apply this diff to improve type safety:
+interface ToolMetadata { + "ui/resourceUri"?: string; + "openai/outputTemplate"?: string; + [key: string]: unknown; +} + // Check if this is an MCP App (has tools with ui/resourceUri metadata) const isMCPApp = toolsData?.toolsMetadata && Object.values(toolsData.toolsMetadata).some( - (meta: any) => meta?.["ui/resourceUri"], + (meta: unknown) => { + const typedMeta = meta as ToolMetadata; + return typedMeta?.["ui/resourceUri"]; + }, ); // Check if this is an OpenAI app (has tools with openai/outputTemplate metadata) const isOpenAIApp = toolsData?.toolsMetadata && Object.values(toolsData.toolsMetadata).some( - (meta: any) => meta?.["openai/outputTemplate"], + (meta: unknown) => { + const typedMeta = meta as ToolMetadata; + return typedMeta?.["openai/outputTemplate"]; + }, );Alternatively, if tool metadata types are defined in
@/lib/mcp-tools-apior@/lib/mcp-apps-utils.ts, import and reuse those types.The PR introduces
detectUITypehelpers inlib/mcp-apps-utils.ts. Consider extracting this detection logic there for consistency across components.server/tsup.config.ts (1)
4-4: Post‑build copy hook looks solid; verify deployment origin and build behavior.The
onSuccesshook correctly ensuresserver/routes/mcp/sandbox-proxy.htmlis always copied intodist/server/sandbox-proxy.html, and thefsusage is straightforward and safe for repeated builds. The only substantive concern is operational rather than code: MCP‑UI guidance expects the sandbox proxy to be served from a distinct origin from the main app; bundling it intodist/servermakes same‑origin deployment easy by accident, so it would be wise to double‑check how this asset is actually hosted in each environment (and perhaps document that requirement alongside this build step).Also, because this relies on tsup’s
onSuccessbehavior, it’s worth confirming that all your server build commands (prod, watch, CI) execute this hook and thatserver/routes/mcp/sandbox-proxy.htmlexists in every build context.Also applies to: 57-67
vite.main.config.ts (1)
1-18: Copy plugin is correct and minimal; consider tightening outDir handling and confirm proxy origin.The
copySandboxProxyplugin is a clean way to ensuresandbox-proxy.htmllands in the final build output, and usingwriteBundlekeeps it confined to build time. From a robustness perspective, you might want to avoid the magic".vite/build"fallback and instead mirror Vite’s resolvedbuild.outDir(e.g., capture it inconfigResolvedand reuse it here), so the plugin can’t silently write to an unexpected directory ifoptions.diris ever unset or customized.As with the tsup hook, this wiring makes it easy to serve the sandbox proxy from the same origin as the main app; MCP‑UI security guidance, however, expects the proxy to live on a distinct origin. Please verify that your packaging/deployment pipeline either serves this copied file from a separate origin or clearly documents that requirement so operators don’t default to same‑origin hosting.
Also applies to: 22-22
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
client/src/components/ToolsTab.tsx(1 hunks)client/src/components/connection/ServerConnectionCard.tsx(2 hunks)client/src/components/connection/ServerInfoModal.tsx(3 hunks)client/src/components/ui/sandboxed-iframe.tsx(1 hunks)client/vite.config.ts(2 hunks)server/tsup.config.ts(2 hunks)vite.main.config.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- client/src/components/ToolsTab.tsx
- client/src/components/connection/ServerConnectionCard.tsx
- client/src/components/ui/sandboxed-iframe.tsx
- client/vite.config.ts
🧰 Additional context used
📓 Path-based instructions (3)
server/**/*.ts
📄 CodeRabbit inference engine (server/CLAUDE.md)
Implement TypeScript for type safety throughout the codebase
Files:
server/tsup.config.ts
client/**/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
client/**/components/**/*.{ts,tsx}: Use functional React components with React.FC typing
Implement strict TypeScript types for React component props with Props interface definitions
Follow React 19 patterns including hooks, Suspense boundaries, error boundaries, and concurrent features
Use Radix UI components for Dialog, Dropdown menus, Form controls, and Tooltips
Design responsive layouts using Tailwind breakpoint system, grid layouts, flex containers, and container queries
Ensure accessibility compliance with ARIA attributes, keyboard navigation, focus management, and screen reader support
Files:
client/src/components/connection/ServerInfoModal.tsx
client/**/{components,hooks}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (client/CLAUDE.md)
Use React hooks (useState, useReducer) for local state management with proper effect cleanup
Files:
client/src/components/connection/ServerInfoModal.tsx
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/stores/servers/**/*.{ts,tsx} : Synchronize MCP connection state, request tracking, response handling, and error management with global state
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.527Z
Learning: Applies to server/**/types/mcp.ts : Define MCP protocol types in TypeScript type definition files
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/{stores/servers,lib/api}/**/*.{ts,tsx} : Persist server configurations with local storage, export/import functionality, sync options, and backup/restore capabilities
Applied to files:
server/tsup.config.ts
📚 Learning: 2025-11-24T17:54:18.310Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.310Z
Learning: Use Vite + React for fast development and optimized builds
Applied to files:
vite.main.config.ts
📚 Learning: 2025-11-24T17:54:18.309Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: client/CLAUDE.md:0-0
Timestamp: 2025-11-24T17:54:18.309Z
Learning: Applies to client/**/components/servers/**/*.{ts,tsx} : Implement MCP server management UI with connection list, status indicators, quick actions, and group management
Applied to files:
client/src/components/connection/ServerInfoModal.tsx
📚 Learning: 2025-11-25T19:59:26.526Z
Learnt from: CR
Repo: MCPJam/inspector PR: 0
File: server/CLAUDE.md:0-0
Timestamp: 2025-11-25T19:59:26.526Z
Learning: Applies to server/**/routes/mcp/prompts/**/*.ts : Validate prompt context, token limits, and format in prompt processing handlers
Applied to files:
client/src/components/connection/ServerInfoModal.tsx
🔍 Remote MCP Context7
Relevant MCP-UI guidance for reviewing this PR
- Resource delivery: UI resources served as text/uri-list must be a single http(s) URL — verify server GET /widget-content/:toolId enforces/validates http(s) single-URL semantics.
- Sandbox/proxy origin: proxy script must NOT be hosted on the same origin as the main app (self-hosted proxy should be on a distinct origin). Confirm sandbox-proxy handling and any warnings/guardrails.
- Double-iframe sandbox / CSP: MCP-UI expects iframe sandboxing, proxying, and CSP to be used to limit widget capabilities. Review sandbox attribute transitions, CSP headers, and that the sandboxed proxy implementation follows the double-iframe pattern.
- PostMessage / JSON‑RPC semantics: Hosts should maintain pending-request maps, messageId-based request/response flows, and timeouts for JSON‑RPC over postMessage (tools/call, resources/read, ui/initialize, etc.). Verify consistent messageId handling, pending maps, and timeout behavior across injected script, SandboxedIframe, and MCPAppsRenderer.
- Security for inline injection: Inline script serialization must escape HTML/newline characters safely (serializeForInlineScript). Confirm escape logic, MIME/Content-Type, and caching headers when serving modified HTML.
- Proxy usage guidance: For environments with restrictive CSP, MCP-UI recommends a separate-origin proxy; confirm the repo’s copy/serve of sandbox-proxy.html and build-time copy into dist do not violate the same-origin requirement and that deployment instructions/documentation note this constraint.
Tools used
- Resolved MCP-UI library candidates: Context7 resolve search.
- MCP-UI docs (security, proxy, client/server overviews).
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: prettier-and-build
- GitHub Check: Cursor Bugbot
🔇 Additional comments (2)
client/src/components/connection/ServerInfoModal.tsx (2)
256-260: LGTM! Clean generalization from OpenAI-specific to multi-app support.The condition change from
isOpenAIApptohasWidgetMetadataand the tab rename from "OpenAI Metadata" to "Tools Metadata" correctly generalize the component to support both MCP Apps and OpenAI Apps.
237-252: Perfect! The icon files are found at./client/public/mcp.svgand./client/public/openai_logo.png. The root-relative paths (/mcp.svgand/openai_logo.png) in the code correctly resolve to these files since theclient/public/directory is served as the root in a typical frontend setup.Icon files exist and paths are correct; conditional logic properly prioritizes MCP badge over OpenAI badge.
The code at lines 237-252 correctly implements the badge rendering with proper accessibility attributes (alt text and title). Both icon files exist at the expected locations, and the conditional logic (
isOpenAIApp && !isMCPApp) ensures only one badge displays with MCP taking precedence.
| case "ui/initialize": { | ||
| // Respond with host context (per SEP-1865) | ||
| sendResponse(id, { | ||
| protocolVersion: "2025-11-25", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Protocol version mismatch between host and client script
The MCP Apps protocol versions are inconsistent. The host (in mcp-apps-renderer.tsx) responds to ui/initialize with protocolVersion: "2025-11-25", but the injected client script (in apps.ts) requests with protocolVersion: '2025-06-18'. These hardcoded version strings should likely match or follow a consistent versioning scheme to ensure proper protocol negotiation per SEP-1865.
Note
Adds MCP Apps (SEP-1865) end-to-end support: client renderer with double-iframe sandbox, server routes to serve widgets, detection/integration across chat, tools, and server info.
MCPAppsRendererusing a reusableSandboxedIframe(double-iframe, JSON-RPC 2.0 over postMessage) to render widgets fromui/resourceUri.detectUITypeand integrates MCP Apps detection inchat-v2/thread.tsx, prioritizingmcp-appsover OpenAI SDK and MCP-UI.ToolsTab.tsxandResultsPanel.tsxto recognizeui/resourceUri, default view toggles (Component/Raw JSON), and render MCP Apps components.ServerConnectionCardandServerInfoModalto detect and indicate MCP Apps (icon), and generalize metadata tab to "Tools Metadata".vite.config.tsnow injects__APP_VERSION__frompackage.json./api/mcp/appswithPOST /widget/storeandGET /widget-content/:toolId, injecting a SEP-1865 client bridge script into served HTML./api/mcp/sandbox-proxywithsandbox-proxy.htmlfor the double-iframe architecture and wires into router.sandbox-proxy.htmlinto build outputs (server/tsup.config.ts,vite.main.config.ts).Written by Cursor Bugbot for commit a9ee3bf. This will update automatically on new commits. Configure here.