Skip to content

Conversation

@cliffhall
Copy link
Member

@cliffhall cliffhall commented Jan 26, 2026

Summary

Implements complete MCP Apps support in the Inspector, enabling detection, listing, and interactive rendering of MCP apps with full bidirectional communication.

Implementation

New Components:

  • AppsTab - Detects and displays tools with _meta.ui.resourceUri field
  • AppRenderer - Handles full MCP App lifecycle with AppBridge integration

Files Added:

  • client/src/components/AppsTab.tsx
  • client/src/components/AppRenderer.tsx
  • client/src/components/__tests__/AppsTab.test.tsx
  • client/src/components/__tests__/AppRenderer.test.tsx
  • server/static/sandbox_proxy.html - sandbox proxy similar to In sandbox_proxy.html
    • refactor to match basic-host example, creates an nested iframe for security. In this implementation, for simplicity the sandbox.ts is inlined in sandbox_proxy.html as javascript example. Served on a different port via the proxy server.

Files Modified:

  • client/src/App.tsx - Added Apps tab integration, auto-fetch logic, and resource wiring
  • client/src/components/ListPane.tsx - Only show Clear button if a clearItems function was passed in
  • client/src/components/__tests__/ListPane.test.tsx - Make sure Clear button is not shown if no clearItems function was passed in
  • client/src/components/__tests__/AuthDebugger.test.tsx - Fix @ imports that were not resolving to be relative
  • client/package.json - Added @modelcontextprotocol/ext-apps^1.0.0, @mcp-ui/client^6.0.0 dependencies
  • client/jest.config.cjs - Updated to handle ES modules from @modelcontextprotocol/ext-apps
  • server/src/index.ts - add /sandbox endpoint which reads and serves static/sandbox_proxy.html with no-cache header and rate limiting
  • server/package.json - Added shx^0.3.4 and express-rate-limit:^8.2.1 dependencies. Copy static folder to build in build script.

Key Features

App Detection - Automatically identifies tools with MCP Apps extension metadata
Auto-population - Apps list loads when Apps tab becomes active
Resource Fetching - Fetches ui:// resources via MCP protocol
MCP-UI AppRenderer Integration - Renders the app, given an MCP client, tool name, resource content, and host context (custom settings like theme)
PostMessage Transport - Secure JSON-RPC communication between iframe and host
Sandboxed Rendering - Secure iframe rendering with configurable permissions
Theme Support - Passes Inspector's light/dark theme to apps
Error Handling - Comprehensive error states and user feedback

Architecture

┌─────────────────────────────────────────────────────────┐
│                      Inspector Host                     │
│  ┌──────────────┐    ┌────────────┐   ┌──────────────┐  │
│  │   AppsTab    │───→│ AppRenderer│───│   MCP Client │  │
│  └──────────────┘    └────────────┘   └──────────────┘  │
│                            │                 │          │
│                            ↓                 ↓          │
│                      ┌────────────┐    ┌────────────┐   │
│                      │ AppBridge  │←───│ MCP Server │   │
│                      └────────────┘    └────────────┘   │
│                            │                            │
│                            ↓                            │
│                   ┌─────────────────┐                   │
│                   │ PostMessage     │                   │
│                   │ Transport       │                   │
│                   └─────────────────┘                   │
│                            │                            │
└────────────────────────────┼────────────────────────────┘
                             ↓
                    ┌─────────────────┐
                    │  Sandboxed      │
                    │  iframe         │
                    │  (MCP App UI)   │
                    └─────────────────┘

Test Coverage

AppsTab Tests (18 tests):

  • App detection and filtering
  • Grid display of multiple apps
  • Refresh functionality
  • Error handling
  • App selection and deselection
  • Dynamic tool list updates
  • Resource content handling

AppRenderer Tests (8 tests):

  • Loading states
  • Resource fetching logic
  • Error states
  • Iframe rendering with proper attributes
  • AppBridge initialization and connection
  • JSON and HTML content parsing
  • Custom permissions handling
  • Component lifecycle

All 477 tests pass successfully

Quality Checks

  • ✅ Prettier formatting applied
  • ✅ ESLint passing with no errors
  • ✅ All tests passing (477/477)
  • ✅ Build successful

Closes #1041

Videos

Map Server

map.mov

Cohort Heatmap Server

cohort.mov

Wiki Explorer Server - Navigation

wiki.mov

Wiki Explorer Server - Open Links

wiki-open-link.mov

Generated with Claude Code
Fixed with Junie and plenty of old-school hacking.

github-actions bot and others added 5 commits January 26, 2026 20:30
- Fixed AppRenderer useEffect dependency array to include resourceContent
  This ensures the component re-evaluates when resource content arrives
- Added detailed console logging throughout the app lifecycle:
  * Resource fetch and response tracking in App.tsx
  * Setup conditions and AppBridge creation in AppRenderer.tsx
  * HTML parsing and iframe rendering steps
  * PostMessageTransport and AppBridge connection status
  * App tool filtering and selection in AppsTab.tsx
- Refactored AppsTab selectedTool rendering for better tracking

The issue was that resourceContent prop updates weren't triggering
the AppRenderer setup effect. Now the effect properly responds to
both resourceUri and resourceContent changes.

Co-authored-by: Cliff Hall <[email protected]>
The app was getting stuck on 'Loading MCP App...' because the iframe was
hidden (display: none) until the oninitialized event fired. However, the
PostMessage handshake requires the iframe to be visible to complete.

This fix:
- Sets loading to false immediately after writing HTML to the iframe
- Makes the iframe visible before establishing PostMessage transport
- Allows the AppBridge initialization handshake to complete successfully
- Removes redundant setLoading(false) from oninitialized callback

The iframe is now visible and ready for PostMessage communication before
the AppBridge connect() call, enabling proper initialization.

Co-authored-by: Cliff Hall <[email protected]>
The AppRenderer was incorrectly checking for a 'type' field in resource
contents, but TextResourceContents objects only have uri, mimeType, and
text fields according to the MCP specification.

Fixed by checking for the presence of the 'text' field directly instead
of checking a non-existent 'type' field. This allows the HTML content
to be properly extracted and rendered in the iframe.

Co-authored-by: Cliff Hall <[email protected]>
- Add tests for AppsTab component (13 tests)
- Add tests for AppRenderer component (17 tests)
- Update jest.config.cjs to handle ES modules from @modelcontextprotocol/ext-apps
- All 478 tests pass successfully

Co-authored-by: Cliff Hall <[email protected]>
- Add AppsTab component for detecting and listing MCP apps
- Add AppRenderer component with full AppBridge integration
- Implement UI resource fetching and sandboxed iframe rendering
- Add PostMessage transport for bidirectional JSON-RPC communication
- Include comprehensive test coverage (30 new tests)
- Auto-populate apps when tab becomes active
- Support theme awareness and configurable permissions

Co-authored-by: Cliff Hall <[email protected]>
@cliffhall cliffhall marked this pull request as ready for review January 26, 2026 22:27
@cliffhall cliffhall mentioned this pull request Jan 26, 2026
9 tasks
Copy link
Member

@olaservo olaservo left a comment

Choose a reason for hiding this comment

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

Added a few suggestions.

I'm still testing with a few more servers too, since I still can't tell if some weirdness is due to the servers or the inspector yet. For example, some servers are rendering with a black background (this one) or just render a black rectangle for me (like this one). So far I think its the servers that need some adjustment?

It also looks like there are a lot of console.log statements, so I wasn't sure if we should keep all those as-is or if they were meant to be more temporary?

@cliffhall
Copy link
Member Author

For example, some servers are rendering with a black background (this one)

@olaservo that's the cohort-heatmap-server, which was working for me in the screenshot and video above. I'm using Chrome on macOS. You?

just render a black rectangle for me (like this one). So far I think its the servers that need some adjustment?

Screenshot 2026-01-27 at 2 37 23 PM

I get the app rendering, and the scaling buttons are present, but it doesn't seem to load the data. When I run it in the ext-apps browser demo, I see the content...

Screenshot 2026-01-27 at 2 38 18 PM

It also looks like there are a lot of console.log statements, so I wasn't sure if we should keep all those as-is or if they were meant to be more temporary?

I sort of thought they might be useful for a time, until we are certain everything is working properly. I can take them out.

@cliffhall

This comment was marked as resolved.

@claude

This comment was marked as resolved.

github-actions bot and others added 2 commits January 27, 2026 19:50
Some MCP servers return different data in structuredContent vs content fields.
This change ensures that when structuredContent is present, it is used
exclusively for display instead of showing both fields.

Changes:
- Modified ToolResults.tsx to only show content when structuredContent is absent
- Removed unused checkContentCompatibility function
- Updated test cases to reflect new behavior

Co-authored-by: Cliff Hall <[email protected]>
This reverts commit 3b054b4.

Claude did not do the right thing.
@olaservo
Copy link
Member

For example, some servers are rendering with a black background (this one)

@olaservo that's the cohort-heatmap-server, which was working for me in the screenshot and video above. I'm using Chrome on macOS. You?

@cliffhall I'm using Chrome on Windows and have a dark system theme on, so I think that's why the heatmap hows with a dark theme. Otherwise I think that one is actually ok since the source code for that server seems to prefer the system theme.

I sort of thought they might be useful for a time, until we are certain everything is working properly. I can take them out.

Sure that makes sense!

@infoxicator
Copy link

@cliffhall did you try the React Renderer for hosts? It should be plug and play and has most of the spec implementations needed by hosts etc. It is what I have been using at Postman (soon to be used by MCPJam and Goose)

Let me know if you want to take a look https://github.com/MCP-UI-Org/mcp-ui/blob/main/sdks/typescript/client/src/components/AppRenderer.tsx

it is released on the latest version of the mcp-ui package so you can just npm install it

This commit implements the integration of `@mcp-ui/client` to handle the
rendering of Model Context Protocol (MCP) applications within the inspector.
It includes refactoring the rendering logic, improving resource fetching
in the main application state, and setting up a secure sandbox environment.

Changes per file:

- client/package.json:
  - Added `@mcp-ui/client` as a dependency.

- client/src/components/AppRenderer.tsx:
  - Refactored to use `McpUiAppRenderer` from `@mcp-ui/client`.
  - Implemented HTML parsing logic for MCP resource responses.
  - Configured host context (theme) and sandbox URL for the renderer.
  - Replaced iframe-based manual rendering with the official component.

- client/src/App.tsx:
  - Added `fetchingResources` state to prevent duplicate concurrent resource requests.
  - Enhanced `readResource` with better error handling and state tracking.
  - Optimized resource content mapping to better support application state.

- client/src/components/AppsTab.tsx:
  - Updated to use `getToolUiResourceUri` utility from `@modelcontextprotocol/ext-apps`
    instead of manual metadata property access.
  - Simplified tool filtering and selection logic.

- client/public/sandbox_proxy.html:
  - Added a sandbox proxy page to facilitate secure communication between
    the inspector and the rendered MCP applications.

- client/bin/client.js:
  - Added server rewrites to ensure `sandbox_proxy.html` is served correctly.
  - Implemented specific `Cache-Control` headers for `sandbox_proxy.html` to
    prevent stale cached versions.

- client/vite.config.ts:
  - Explicitly configured `publicDir: "public"` to ensure the sandbox proxy
    is included in the build output.

- client/src/components/__tests__/AppRenderer.test.tsx:
  - Updated tests to mock the new `@mcp-ui/client` component.
  - Adjusted assertions to verify correct props are passed to the renderer.
  - Enhanced mock MCP client to include required methods like `getServerCapabilities`.
@cliffhall cliffhall marked this pull request as draft January 29, 2026 20:41
…scripting

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
cliffhall and others added 2 commits January 29, 2026 15:49
 - npm install
 - npm audit fix
…scripting

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
cliffhall and others added 3 commits January 29, 2026 16:00
…scripting

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
In AppsTab.tsx
  - Add app window
    - when an app is selected,
      - show input form for apps that have an input schema
      - button shows app
      - if app is shown, show maximize button
        - if maximized,
          - show minimize button
          - hide app list
* In AppsTab.test.tsx
  - test new layout and controls
* In AppRenderer.tsx
    - accept toolInput
    - pass to McpUiAppRenderer
… show the form and the "back to form" button when showing the app.

* In AppsTab.test.tsx
  - test new behavior

* In ListPane.tsx
  - remove the Clear button

* In ListPane.test.tsx
  - remove test for clear button
@cliffhall cliffhall requested a review from olaservo January 30, 2026 00:38
@cliffhall
Copy link
Member Author

cliffhall commented Jan 30, 2026

Quick update

  • I've refactored to use the mcp-ui AppRenderer and added a tool input form. It works much better now (see new videos at the top), but some example servers still don't seem to work, not certain why. e.g., budget-allocator-server takes no input but still doesn't render any data, though its UI is rendered.

  • I still need to figure out how / whether to implement the nested iframe model that the basic-host example uses, but with React and mcp-ui. Should I put an iframe around the mcp-ui AppRenderer instance? Shouldn't that be part of AppRenderer itself? Is it a required pattern?

  • Code QL is complaining about XSS threats in the sandbox_proxy.html file. Above here, here, and here, I had applied co-pilot's suggestions for sanitization, but it stopped the apps from rendering altogether, and I've since removed them. I need to understand what the best practice is for that sandbox html and if the nested iframe model mentioned above fixes the problem.

  - remove onReadResource and resourceContent, not needed since mcpClient is being passed

* In App.tsx and AppsTab.tsx,
  - remove the passing of these variables
  - remove tests that fail after having removed onReadResource and resourceContent
…instead of the webclient server.

* In App.tsx
  - pass the path to the sandbox endpoint on the proxy to the AppsTab

* In AppsTab.tsx
  - accept the sandboxPath property and pass it to the AppRenderer

* In AppRenderer.tsx
  - accept the sandboxPath property and pass it to the McpUiAppRenderer

* In client.js
  - remove the rewrite and header config for sandbox_proxy.html

* In server/src/index.ts
  - add a /sandbox endpoint that reads and returns the sandbox_proxy.html file with no-cache header

* In server/package.json
  - add shx for cross-platform copy function
  - in build script, copy static folder to build folder

* Moved sandbox_proxy.html from client/public to server/static
cliffhall and others added 3 commits January 30, 2026 16:18
  - return to original state
  - return to original state
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
className="flex-1 border rounded overflow-hidden"
style={{ minHeight: "400px" }}
>
<McpUiAppRenderer

Choose a reason for hiding this comment

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

@cliffhall I think the only thing missing is wiring the callbacks for the actions. Here's what we need to pass:

// log the ui event and open the link in a new tab
onOpenLink={async ({ url }) => {
if (url.startsWith('https://') || url.startsWith('http://')) {
window.open(url);
}
}}
// log the UI event (maybe logging it somehwere in the inspector so the message is visible and acknowledged?
onMessage={async (params) => {
console.log('Message from tool UI:', params);
return { isError: false };
}}

/** Handler for logging messages from the guest UI */
// this one should be sent to the inspector's notifications list
onLoggingMessage?: (params: LoggingMessageNotification['params']) => void;

You can test all this functionality with the ext-apps/react-server demo that has the get time to test tool calling, the send message, send notification etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

WIP. Added onOpenLink handler. Adding video to PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added support for showing handling McpUiMessageRequest messages and showing them as toasts.
Screenshot 2026-01-30 at 7 35 24 PM

Copy link
Member Author

Choose a reason for hiding this comment

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

Still need to handle logging messages

  - add open link handler that handles link requests from the UI. Makes sure that the URL starts with http or https
  - refactor to match [basic-host](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host) example
  - creates an nested iframe for security.
  - in this implementation, for simplicity the sandbox.ts is inlined in sandbox_proxy.html as javascript
  - add support for handling McpUiMessageRequest messages from the UI
  - show message in a toast
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add MCP Apps support

4 participants