Skip to content

Conversation

@chelojimenez
Copy link
Contributor

@chelojimenez chelojimenez commented Nov 27, 2025

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.

  • Frontend
    • MCP Apps Renderer: Introduces MCPAppsRenderer using a reusable SandboxedIframe (double-iframe, JSON-RPC 2.0 over postMessage) to render widgets from ui/resourceUri.
    • Detection & Routing: Adds detectUIType and integrates MCP Apps detection in chat-v2/thread.tsx, prioritizing mcp-apps over OpenAI SDK and MCP-UI.
    • Tools & Results: Updates ToolsTab.tsx and ResultsPanel.tsx to recognize ui/resourceUri, default view toggles (Component/Raw JSON), and render MCP Apps components.
    • Server UI: Enhances ServerConnectionCard and ServerInfoModal to detect and indicate MCP Apps (icon), and generalize metadata tab to "Tools Metadata".
    • Version Inject: Client vite.config.ts now injects __APP_VERSION__ from package.json.
  • Backend
    • MCP Apps Routes: Adds /api/mcp/apps with POST /widget/store and GET /widget-content/:toolId, injecting a SEP-1865 client bridge script into served HTML.
    • Sandbox Proxy: Serves /api/mcp/sandbox-proxy with sandbox-proxy.html for the double-iframe architecture and wires into router.
  • Build/Packaging
    • Copies sandbox-proxy.html into 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.

@dosubot dosubot bot added the size:XXL This PR changes 1000+ lines, ignoring generated files. label Nov 27, 2025
@dosubot dosubot bot added the enhancement New feature or request label Nov 27, 2025
@coderabbitai
Copy link

coderabbitai bot commented Nov 27, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

Walkthrough

Adds 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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: Wildcard targetOrigin is intentional but merits a comment.

The "*" targetOrigin in postMessage is 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 the as any cast.

The cast on line 43 bypasses type safety. If isUIResource expects 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/client exports the expected parameter type and import it.

server/routes/mcp/apps.ts (2)

55-96: Input validation is minimal.

Required fields are checked, but resourceUri and serverId aren't validated for format (e.g., ensuring resourceUri starts with ui://). 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 after detectUIType.

Line 253 re-checks partToolMeta?.["ui/resourceUri"] despite detectUIType already 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 any cast 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, and onRequestPip are declared in MCPAppsRendererProps but 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 pendingRequests Map 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

📥 Commits

Reviewing files that changed from the base of the PR and between a8d3dca and 2f4adab.

📒 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.tsx
  • client/src/components/ui/sandboxed-iframe.tsx
  • client/src/components/tools/ResultsPanel.tsx
  • client/src/components/chat-v2/mcp-apps-renderer.tsx
  • client/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.tsx
  • client/src/components/ui/sandboxed-iframe.tsx
  • client/src/components/tools/ResultsPanel.tsx
  • client/src/components/chat-v2/mcp-apps-renderer.tsx
  • client/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.ts
  • server/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.tsx
  • server/routes/mcp/index.ts
  • client/src/lib/mcp-apps-utils.ts
  • client/src/components/tools/ResultsPanel.tsx
  • server/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.md
  • server/routes/mcp/index.ts
  • client/src/lib/mcp-apps-utils.ts
  • client/src/components/tools/ResultsPanel.tsx
  • client/src/components/chat-v2/mcp-apps-renderer.tsx
  • client/src/components/chat-v2/thread.tsx
  • server/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.ts
  • client/src/lib/mcp-apps-utils.ts
  • client/src/components/chat-v2/mcp-apps-renderer.tsx
  • client/src/components/chat-v2/thread.tsx
  • server/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.ts
  • client/src/components/chat-v2/thread.tsx
  • server/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.ts
  • server/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.tsx
  • 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 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 UIType union captures all routing destinations.


70-82: getUIResourceUri is concise and correct.

Returning null for mcp-ui aligns 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.mcpApp API 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, readFileSync is appropriate. The ESM __dirname derivation 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 onCallTool callback properly binds serverId for nested tool invocations.


281-298: I'll help you verify this review comment about the toolMetadata prop in the MCPAppsRenderer component. Let me search for the component definition and its props interface.
<function_calls>

#!/bin/bash

Find MCPAppsRenderer component file

fd -t f MCPAppsRenderer --extension ts --extension tsx


</function_calls>

Let me search more broadly for this component:
<function_calls>

#!/bin/bash

Search 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/bash

Search 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/bash

Search for the MCPAppsRenderer export/component definition

rg -B 5 -A 20 "export.*MCPAppsRenderer|function MCPAppsRenderer|const MCPAppsRenderer" --type ts --type tsx


</function_calls>

I need to run these scripts to gather the verification evidence. Please execute them.

I'm ready to verify the review comment. Based on the task, I need to confirm whether the toolMetadata prop is properly declared in the MCPAppsRendererProps interface. Let me execute scripts to search for this component definition:
<function_calls>

#!/bin/bash

Search 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/outputTemplate or ui/resourceUri metadata 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 ToolExecutionResponse to the format expected by MCPAppsRenderer.onCallTool.

client/src/components/chat-v2/mcp-apps-renderer.tsx (2)

171-329: Comprehensive JSON-RPC message handler.

The handleMessage callback 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-forms default 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');

Comment on lines 89 to 146
// 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,
]);
Copy link

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.

Comment on lines +130 to +139
return (
<iframe
ref={outerRef}
src="/api/mcp/sandbox-proxy"
sandbox="allow-scripts allow-same-origin"
title={title}
className={className}
style={style}
/>
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Script executed:

#!/bin/bash
# Search for SEP-1865 references in the codebase
rg -i "SEP-1865|sep-1865" -C 3

Repository: 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 3

Repository: 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 -50

Repository: 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.html

Repository: 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 -150

Repository: 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 -40

Repository: 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 -20

Repository: 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.tsx

Repository: 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 -50

Repository: 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.ts

Repository: 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 -30

Repository: 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 -20

Repository: 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.tsx

Repository: 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.

Comment on lines +164 to +170
} 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,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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" },
Copy link
Collaborator

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

Copy link
Collaborator

@khandrew1 khandrew1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@dosubot dosubot bot added the lgtm This PR has been approved by a maintainer label Nov 27, 2025
@khandrew1
Copy link
Collaborator

run prettier and test on prod before merging

Copy link

@coderabbitai coderabbitai bot left a 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-fix to 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: Unstable toolCallId fallback generates new ID on each render.

Using Date.now() directly in JSX creates a fresh ID per render. Unlike OpenAIAppRenderer which uses useMemo for 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 AbortController to 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 checking response.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 MCPAppsRendererProps interface would benefit from documentation comments describing each prop's purpose, especially for callbacks like onCallTool and onWidgetStateChange whose expected behavior and return values affect integration.


74-74: Replace useState with const for immutable values.

maxHeight never 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2f4adab and 7ecba87.

📒 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.tsx
  • client/src/components/connection/ServerConnectionCard.tsx
  • client/src/components/tools/ResultsPanel.tsx
  • client/src/components/chat-v2/thread.tsx
  • client/src/components/ui/sandboxed-iframe.tsx
  • client/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.tsx
  • client/src/components/connection/ServerConnectionCard.tsx
  • client/src/components/tools/ResultsPanel.tsx
  • client/src/components/chat-v2/thread.tsx
  • client/src/components/ui/sandboxed-iframe.tsx
  • client/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.tsx
  • client/src/components/connection/ServerConnectionCard.tsx
  • client/src/components/tools/ResultsPanel.tsx
  • client/src/components/chat-v2/thread.tsx
  • 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/connection/ServerInfoModal.tsx
  • client/src/components/connection/ServerConnectionCard.tsx
  • client/src/components/chat-v2/thread.tsx
  • 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/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.tsx
  • client/src/components/chat-v2/thread.tsx
  • 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/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.tsx
  • 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 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 since detectUIType already verifies the presence of ui/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 hasWidgetMetadata flag 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: onCallTool handler 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 sandbox prop value includes necessary permissions for typical widget functionality.


74-83: Imperative handle correctly exposes postMessage.

Empty dependency array is appropriate since outerRef is 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 (with id), and use appropriate useCallback memoization.


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 the isReady flag 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". The allow-same-origin flag 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.

Comment on lines +79 to +87
const pendingRequests = useRef<
Map<
number | string,
{
resolve: (value: unknown) => void;
reject: (error: Error) => void;
}
>
>(new Map());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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.

Suggested change
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.

Copy link

@coderabbitai coderabbitai bot left a 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7ecba87 and 0776a90.

📒 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

Comment on lines +29 to +44
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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 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 -60

Repository: 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.tsx

Repository: 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.

Suggested change
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',
Copy link

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)

Fix in Cursor Fix in Web

Copy link

@coderabbitai coderabbitai bot left a 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 to fetchWidgetHtml effect to avoid stale state updates

The widget-loading effect currently doesn’t cancel in‑flight fetches. If toolState or deps change, or the component unmounts mid‑request, the late setWidgetHtml/setLoadError calls can race against newer state.

Adding an AbortController per effect run and bailing on AbortError would 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: Ensure pendingRequests is cleaned up on unmount (and consider wiring it or removing it)

pendingRequests is never cleared, so once you start populating it with resolve/reject handlers, 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 pendingRequests entirely until it’s actually used.

Also applies to: 180-342


252-270: Map HTTP failures from /api/mcp/resources/read to JSON‑RPC errors, not results

Right 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 checks response.ok) and makes error handling in MCP Apps brittle.

Treat non‑OK responses as JSON‑RPC errors and only send result when 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 via package.json is solid; consider a small guard for robustness

Reading the version from the root package.json and injecting it into __APP_VERSION__ is a clean, build‑time‑only solution and fits Vite’s define pattern.

If you want a bit more resilience against a missing or malformed version field, 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0776a90 and 5f341c0.

📒 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.tsx
  • client/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.tsx
  • client/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.tsx
  • 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/**/*.{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.tsx
  • client/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 CI

The 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 fallback toolCallId and unified toggle logic look solid

The memoized resolvedToolCallId gives 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 and onCallTool adapter are well-shaped

Preferring MCPAppsRenderer when hasMCPAppsComponent and !showStructured matches the spec intent, and the onCallTool adapter cleanly normalizes ToolExecutionResponse into { content, structuredContent } while surfacing errors and non-completed statuses as exceptions. This is a good, minimal bridge between the tools API and SEP‑1865.

Comment on lines +272 to +279
case "ui/open-link": {
const linkParams = params as { url?: string };
if (linkParams.url) {
window.open(linkParams.url, "_blank", "noopener,noreferrer");
}
sendResponse(id, {});
break;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.tsx

Repository: 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 -100

Repository: 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 -150

Repository: MCPJam/inspector

Length of output: 11040


🏁 Script executed:

#!/bin/bash
# Check for any URL security utility functions
rg -n "validateUrl|isValidUrl|isSafeUrl|sanitizeUrl" client/src

Repository: 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.

Copy link

@coderabbitai coderabbitai bot left a 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: Replace any types 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-api or @/lib/mcp-apps-utils.ts, import and reuse those types.

The PR introduces detectUIType helpers in lib/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 onSuccess hook correctly ensures server/routes/mcp/sandbox-proxy.html is always copied into dist/server/sandbox-proxy.html, and the fs usage 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 into dist/server makes 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 onSuccess behavior, it’s worth confirming that all your server build commands (prod, watch, CI) execute this hook and that server/routes/mcp/sandbox-proxy.html exists 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 copySandboxProxy plugin is a clean way to ensure sandbox-proxy.html lands in the final build output, and using writeBundle keeps it confined to build time. From a robustness perspective, you might want to avoid the magic ".vite/build" fallback and instead mirror Vite’s resolved build.outDir (e.g., capture it in configResolved and reuse it here), so the plugin can’t silently write to an unexpected directory if options.dir is 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5f341c0 and a9ee3bf.

📒 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 isOpenAIApp to hasWidgetMetadata and 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.svg and ./client/public/openai_logo.png. The root-relative paths (/mcp.svg and /openai_logo.png) in the code correctly resolve to these files since the client/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",
Copy link

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.

Additional Locations (1)

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants