claude-code - 💡(How to fix) Fix [BUG] Cowork renders MCP App iframes but never delivers tool results (ontoolresult never fires) [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#48194Fetched 2026-04-15 06:30:30
View on GitHub
Comments
0
Participants
1
Timeline
4
Reactions
0
Participants
Timeline (top)
labeled ×4

Cowork renders MCP App iframes (axes, threshold lines, search boxes all visible) but never delivers tool results to the app via ontoolresult. The app HTML loads and initializes, but the postMessage bridge between the Cowork host and the iframe never completes, so the app displays an empty visualization with no data.

Error Message

  • The user sees an empty chart frame with no data and no error message

Root Cause

From server logs, Cowork's MCP client:

  • Successfully completes the initialize handshake
  • Fetches ui:// resources via resources/read
  • Executes tool calls and receives valid JSON results

The failure is between the Cowork host receiving the tool result and delivering it to the iframe via the postMessage bridge. This is consistent with the race condition described in modelcontextprotocol/ext-apps#542, where PostMessageTransport requires iframe.contentWindow at construction time, but srcdoc iframes may start executing before the host's transport is listening.

RAW_BUFFERClick to expand / collapse

Summary

Cowork renders MCP App iframes (axes, threshold lines, search boxes all visible) but never delivers tool results to the app via ontoolresult. The app HTML loads and initializes, but the postMessage bridge between the Cowork host and the iframe never completes, so the app displays an empty visualization with no data.

Tool output in Cowork

<img width="781" height="595" alt="Image" src="https://github.com/user-attachments/assets/aac0f54e-c9ee-4e48-9932-8f787404251c" />

The plot is rendered, but not the data.

Same tool output in Chat

<img width="813" height="614" alt="Image" src="https://github.com/user-attachments/assets/997e2798-2067-4fc9-915a-6328bc65c5b9" />

The plot is rendered with the data.

Steps to reproduce

  1. Register an MCP server that exposes a tool with app=AppConfig(resourceUri="ui://...") (e.g., a volcano plot using Plotly via the MCP Apps ext-apps SDK)
  2. Call the tool from Claude Chat (Desktop) -- the app renders with data correctly
  3. Call the same tool with the same parameters from Cowork -- the app iframe renders (axes, grid lines, UI controls are visible) but no data points appear

Expected behavior

The MCP App iframe should either:

  • Receive the tool result via ontoolresult and render the data (same as Claude Chat), OR
  • Not render the iframe at all if Cowork doesn't support the full MCP Apps protocol, so the user sees the text fallback instead of a confusing empty visualization

Actual behavior

  • The app HTML is fetched via resources/read and rendered in an iframe
  • The tool executes successfully and returns valid JSON (confirmed via server logs: isError: false)
  • But ontoolresult never fires in the app JavaScript
  • The user sees an empty chart frame with no data and no error message

Environment

  • Claude Desktop: latest (auto-updated as of April 2026)
  • macOS (Apple Silicon)
  • MCP server: FastMCP 3.0.1, Python 3.12
  • ext-apps SDK: @modelcontextprotocol/[email protected]

Analysis

From server logs, Cowork's MCP client:

  • Successfully completes the initialize handshake
  • Fetches ui:// resources via resources/read
  • Executes tool calls and receives valid JSON results

The failure is between the Cowork host receiving the tool result and delivering it to the iframe via the postMessage bridge. This is consistent with the race condition described in modelcontextprotocol/ext-apps#542, where PostMessageTransport requires iframe.contentWindow at construction time, but srcdoc iframes may start executing before the host's transport is listening.

Related issues

  • modelcontextprotocol/ext-apps#542 -- PostMessageTransport race condition with srcdoc iframes (fix PR #543 pending)
  • modelcontextprotocol/ext-apps#482 -- Claude fetches ui:// resources but doesn't mount iframe / start bridge
  • #48132 -- Feature request for MCP Apps rendering in Claude Code Preview tool
  • #4496 -- structuredContent stripped from tool results in Claude Code
  • #22619 -- "The CLI has no iframe runtime, so MCP Apps simply don't render in Claude Code"

extent analysis

TL;DR

The issue is likely due to a race condition between the Cowork host receiving the tool result and delivering it to the iframe via the postMessage bridge, and can be fixed by applying a workaround for the PostMessageTransport issue.

Guidance

  • Verify that the PostMessageTransport issue is the root cause by checking if the iframe.contentWindow is available when the transport is constructed.
  • Apply the workaround from modelcontextprotocol/ext-apps#542 to ensure that the postMessage bridge is established correctly.
  • Check the server logs to confirm that the tool result is being received by the Cowork host and that the ontoolresult event is not being fired.
  • Consider updating to the latest version of the @modelcontextprotocol/ext-apps SDK, which may include the fix for the PostMessageTransport issue.

Example

No code snippet is provided as the issue is related to a specific library and its usage, and the fix is already described in the related issue.

Notes

The issue is specific to the Cowork environment and the PostMessageTransport implementation in the @modelcontextprotocol/ext-apps SDK. The workaround may not be applicable to other environments or versions of the SDK.

Recommendation

Apply the workaround for the PostMessageTransport issue, as described in modelcontextprotocol/ext-apps#542, to ensure that the postMessage bridge is established correctly and the tool result is delivered to the iframe.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The MCP App iframe should either:

  • Receive the tool result via ontoolresult and render the data (same as Claude Chat), OR
  • Not render the iframe at all if Cowork doesn't support the full MCP Apps protocol, so the user sees the text fallback instead of a confusing empty visualization

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [BUG] Cowork renders MCP App iframes but never delivers tool results (ontoolresult never fires) [1 participants]