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

Files Modified:

  • client/src/App.tsx - Added Apps tab integration, auto-fetch logic, and resource wiring
  • client/package.json - Added @modelcontextprotocol/ext-apps^1.0.0 dependency
  • client/jest.config.cjs - Updated to handle ES modules from @modelcontextprotocol/ext-apps

Key Features

App Detection - Automatically identifies tools with MCP Apps extension metadata
Auto-population - Tools list loads when Apps tab becomes active
Resource Fetching - Fetches ui:// resources via MCP protocol
AppBridge Integration - Proxies tools, resources, and prompts to MCP server
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 (13 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 (17 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 478 tests pass successfully

Quality Checks

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

Closes #1041

Video

Screen.Recording.2026-01-26.at.5.30.06.PM.mov

Screenshot

Screenshot 2026-01-26 at 5 26 35 PM

Generated with Claude Code

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 <cliffhall@users.noreply.github.com>
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 <cliffhall@users.noreply.github.com>
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 <cliffhall@users.noreply.github.com>
- 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 <cliffhall@users.noreply.github.com>
- 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 <cliffhall@users.noreply.github.com>
@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?

// Create AppBridge with the MCP client
bridge = new AppBridge(
mcpClient,
{ name: "MCP Inspector", version: "0.19.0" },
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this version is hardcoded instead of matching the package version?

});
if (parsed.contents && Array.isArray(parsed.contents)) {
// MCP resource response format: TextResourceContents has uri, mimeType?, and text
const textContent = parsed.contents.find(
Copy link
Member

Choose a reason for hiding this comment

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

Should this be checking for text/html mimeType explicitly?

console.log("[AppRenderer] Iframe now visible, ready for PostMessage");

// Wait for iframe to load
await new Promise<void>((resolve) => {
Copy link
Member

Choose a reason for hiding this comment

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

Should there be a timeout here?

Copy link
Member Author

@cliffhall cliffhall Jan 27, 2026

Choose a reason for hiding this comment

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

We're using iframe.contentWindow.addEventListener("load", () => resolve(), { once: true }); to wait for the contentWindow to load (if it is present).

<div className="mt-6 border-t pt-6">
<div className="flex items-center justify-between mb-4">
<h3 className="text-xl font-semibold">{selectedTool.name}</h3>
<Button onClick={handleCloseApp} variant="ghost" size="sm">
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<Button onClick={handleCloseApp} variant="ghost" size="sm">
<Button onClick={handleCloseApp} variant="ghost" size="sm" aria-label="Close app">

@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 <cliffhall@users.noreply.github.com>
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!

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

3 participants