Skip to content

Bug Report: MCP Apps UI Resource Not Rendering in Claude Desktop / claude.ai #671

Description

@ernst-von

Bug Report: MCP Apps UI Resource Not Rendering in Claude Desktop / claude.ai
Reporter: Ernst Weyhausen (AMFXT — amfxt.com)
Date: 27 May 2026
Severity: Feature-blocking
Component: Claude Desktop (Windows) / claude.ai — MCP Apps iframe rendering


Summary
Claude (both Desktop and claude.ai) successfully negotiates io.modelcontextprotocol/ui capability with our MCP server, receives structuredContent alongside content in tool results, fetches the UI resource via resources/read, but does not render the HTML resource in an iframe. Instead, the text-only fallback message is shown:
[This tool call rendered an interactive widget in the chat. The user can already see the result — do not repeat it in text or with another visualization tool.]
No iframe or embedded UI is visible to the user.


Environment
• Claude Desktop: Latest Windows version (installed via winget install Anthropic.Claude, confirmed up to date 27 May 2026)
• claude.ai: Web interface, same account
• MCP Server: Custom Node.js/Express JSON-RPC handler at https://amfxt.com (also tested on http://localhost:1000)
• MCP Protocol Version: 2025-11-25
• MCP Apps Spec: SEP-1865, stable 2026-01-26
• SDK reference version: @modelcontextprotocol/ext-apps@1.7.2
• Connection method (claude.ai): Connected via claude.ai MCP connector settings
• Connection method (Desktop): mcp-remote proxy in claude_desktop_config.json
• OS: Windows 11


What Works (verified independently)

  1. Capability Negotiation ✅
    Claude sends io.modelcontextprotocol/ui in the initialize request. Server logs confirm:
    Initialize [ui=true]
  2. Resource Discovery ✅
    Claude calls resources/list and receives the UI resource declaration:
    {
    "resources": [{
    "uri": "ui://amfxt/slidetune",
    "name": "SlideTune Flight Comparison",
    "description": "Interactive fare comparison with bags, pets, equipment pricing, sorting and filtering",
    "mimeType": "text/html;profile=mcp-app"
    }]
    }
  3. Tool Registration with _meta.ui ✅
    tools/list returns the tool with the correct UI linkage:
    {
    "name": "AMFXT",
    "_meta": {
    "ui": {
    "resourceUri": "ui://amfxt/slidetune"
    }
    }
    }
  4. Tool Result with content + structuredContent ✅
    tools/call returns both text fallback and structured data per spec:
    {
    "content": [{ "type": "text", "text": "{...full JSON...}" }],
    "structuredContent": { "route_summary": {...}, "Swiss (LX)": [...], ... },
    "isError": false
    }
  5. Resource Read ✅
    resources/read returns the HTML resource with correct MIME type and CSP:
    {
    "contents": [{
    "uri": "ui://amfxt/slidetune",
    "mimeType": "text/html;profile=mcp-app",
    "text": "...2200 lines of interactive UI...",
    "_meta": {
    "ui": {
    "csp": {
    "resourceDomains": ["https://fonts.googleapis.com", "https://fonts.gstatic.com"]
    },
    "prefersBorder": false
    }
    }
    }]
    }
  6. End-to-End Rendering in basic-host ✅
    The identical server + UI combination renders correctly in the SDK's basic-host test harness (@modelcontextprotocol/ext-apps examples). The HTML:
    • Receives structuredContent via postMessage (ui/notifications/tool-result)
    • Parses flight data via loadFlightData()
    • Renders interactive flight cards with sorting, filtering, bag sliders, price breakdowns
    • Responds to host theme changes
    Screenshot of working basic-host rendering attached separately.
  7. MCP Inspector ✅
    MCP Inspector confirms the full JSON-RPC exchange is correct — tools/call returns valid content + structuredContent, resources/read returns valid HTML.

What Does Not Work
Claude Desktop & claude.ai: No iframe rendered ❌
Despite the complete and correct protocol exchange above, no iframe appears in the conversation. The user sees only the text:
[This tool call rendered an interactive widget in the chat. The user can already see the result — do not repeat it in text or with another visualization tool.]
This message indicates the backend/model layer acknowledges a widget should be rendered, but the frontend does not display it.


HTML Resource Details
The UI resource (SlideTune) is a self-contained ~2200-line HTML file implementing the MCP Apps lifecycle per SEP-1865:
• Sends ui/initialize request with protocolVersion: '2026-01-26'
• Sends ui/notifications/initialized notification after handshake
• Handles ui/notifications/tool-input and ui/notifications/tool-result
• Handles ui/notifications/host-context-changed (theme, safe area insets)
• Responds to ui/resource-teardown
• Uses postMessage for all communication (no SDK dependency — raw JSON-RPC per spec)
• Has baked-data fallback: works both as standalone HTML (direct URL) and as MCP App (iframe with postMessage data delivery)
• CSP requirements: Google Fonts only (fonts.googleapis.com, fonts.gstatic.com)


Steps to Reproduce

  1. Connect to MCP server at https://amfxt.com (or http://localhost:1000 locally)
  2. Verify Initialize [ui=true] in server logs
  3. Ask: "Fly me from MLA to ZRH on 20 June"
  4. Observe: tool call succeeds, data returned, but no UI rendered

Expected Behavior
Per SEP-1865 §"Host calls tool → Host renders resource UI → Server returns result → UI receives result":

  1. Host calls tools/call → receives result with structuredContent
  2. Host fetches resource via resources/read using the resourceUri from _meta.ui
  3. Host renders HTML in a sandboxed iframe
  4. Host sends ui/notifications/tool-result to iframe via postMessage
  5. UI renders interactive flight comparison

Actual Behavior
Steps 1-2 occur (confirmed by server logs). Steps 3-5 do not occur. No iframe is created. The model's text response references a widget that the user cannot see.


Questions for Anthropic

  1. Is MCP Apps UI rendering enabled in Claude Desktop for Windows as of May 2026?
  2. Is there a feature flag, account setting, or plan tier requirement to enable it?
  3. Does mcp-remote proxy correctly forward the io.modelcontextprotocol/ui extension capability? (Claude Desktop config uses mcp-remote for the connection)
  4. The "url" key in claude_desktop_config.json (for direct Streamable HTTP without mcp-remote) was rejected with "not a valid MCP configuration" — is this supported?
  5. Is there a list of hosts that currently render MCP Apps? The spec's clients page references host support varying.

Attachments
• Server logs showing complete protocol exchange
• Screenshot of successful rendering in basic-host test harness
Initialize [ui=true]
===>>> method: [notifications/initialized] -> result: [{}]
===>>> method: [resources/list] -> result: [{"resources":[{"uri":"ui://amfxt/slidetune","name":"SlideTune Flight Comparison","description":"Interactive fare comparison with bags, pets, equipment pricing, sorting and filtering","mimeType":"text/html;profile=mcp-app"}]}]
===>>> method: [resources/list] -> result: [{"resources":[{"uri":"ui://amfxt/slidetune","name":"SlideTune Flight Comparison","description":"Interactive fare comparison with bags, pets, equipment pricing, sorting and filtering","mimeType":"text/html;profile=mcp-app"}]}]
===>>> method: [tools/list] -> result: [{"tools":[{"name":"AMFXT","annotations":{"title":"AMFXT","readOnlyHint":true,"openWorldHint":true,"destructiveHint":false,"idempotentHint":false},"description":"Unified flight search, airline equipment policy, and pet transport policy tool. One tool, one call.\n\nACTION DISPATCH — set 'action' to choose mode:\n\n FLIGHTS:\n flight_search → Search flights with all-inclusive pricing (bags, pets, equipment). Supports multiple extras in one search (e.g. pet + bicycle). Returns fares across 6 classes, on-time stats, star ratings, CO₂, booking links. Supports wildcard discovery ('---').\n\n EQUIPMENT POLICIES (29 types × 600+ airlines):\n equipment_lookup → One airline + one equipment type → full policy detail\n equipment_airline → One airline → all 29 equipment policies\n equipment_compare → One equipment type across 2-5 airlines\n equipment_search → All airlines for one equipment type (with optional filter)\n\n PET POLICIES (12 modes × 21 species × 626+ airlines):"}],"_meta":{"ui":{"resourceUri":"ui://amfxt/slidetune"}}}]}]
Initialize [ui=true]
===>>> method: [notifications/initialized] -> result: [{}]
===>>> method: [tools/list] -> result: [{"tools":[{"name":"AMFXT","annotations":{"title":"AMFXT","readOnlyHint":true,"openWorldHint":true,"destructiveHint":false,"idempotentHint":false},"description":"Unified flight search, airline equipment policy, and pet transport policy tool. One tool, one call.\n\nACTION DISPATCH — set 'action' to choose mode:\n\n FLIGHTS:\n flight_search → Search flights with all-inclusive pricing (bags, pets, equipment). Supports multiple extras in one search (e.g. pet + bicycle). Returns fares across 6 classes, on-time stats, star ratings, CO₂, booking links. Supports wildcard discovery ('---').\n\n EQUIPMENT POLICIES (29 types × 600+ airlines):\n equipment_lookup → One airline + one equipment type → full policy detail\n equipment_airline → One airline → all 29 equipment policies\n equipment_compare → One equipment type across 2-5 airlines\n equipment_search → All airlines for one equipment type (with optional filter)\n\n PET POLICIES (12 modes × 21 species × 626+ airlines):"}],"_meta":{"ui":{"resourceUri":"ui://amfxt/slidetune"}}}]}]
===>>> method: [resources/list] -> result: [{"resources":[{"uri":"ui://amfxt/slidetune","name":"SlideTune Flight Comparison","description":"Interactive fare comparison with bags, pets, equipment pricing, sorting and filtering","mimeType":"text/html;profile=mcp-app"}]}]

Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions