Skip to content

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

@ernst-von

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions