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)
- Capability Negotiation ✅
Claude sends io.modelcontextprotocol/ui in the initialize request. Server logs confirm:
Initialize [ui=true]
- 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"
}]
}
- Tool Registration with _meta.ui ✅
tools/list returns the tool with the correct UI linkage:
{
"name": "AMFXT",
"_meta": {
"ui": {
"resourceUri": "ui://amfxt/slidetune"
}
}
}
- 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
}
- 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
}
}
}]
}
- 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.
- 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
- Connect to MCP server at https://amfxt.com (or http://localhost:1000 locally)
- Verify Initialize [ui=true] in server logs
- Ask: "Fly me from MLA to ZRH on 20 June"
- 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":
- Host calls tools/call → receives result with structuredContent
- Host fetches resource via resources/read using the resourceUri from _meta.ui
- Host renders HTML in a sandboxed iframe
- Host sends ui/notifications/tool-result to iframe via postMessage
- 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
- Is MCP Apps UI rendering enabled in Claude Desktop for Windows as of May 2026?
- Is there a feature flag, account setting, or plan tier requirement to enable it?
- Does mcp-remote proxy correctly forward the io.modelcontextprotocol/ui extension capability? (Claude Desktop config uses mcp-remote for the connection)
- 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?
- 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"}]}]

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)
Claude sends io.modelcontextprotocol/ui in the initialize request. Server logs confirm:
Initialize [ui=true]
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"
}]
}
tools/list returns the tool with the correct UI linkage:
{
"name": "AMFXT",
"_meta": {
"ui": {
"resourceUri": "ui://amfxt/slidetune"
}
}
}
tools/call returns both text fallback and structured data per spec:
{
"content": [{ "type": "text", "text": "{...full JSON...}" }],
"structuredContent": { "route_summary": {...}, "Swiss (LX)": [...], ... },
"isError": false
}
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
}
}
}]
}
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.
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
Expected Behavior
Per SEP-1865 §"Host calls tool → Host renders resource UI → Server returns result → UI receives result":
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
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"}]}]