[BUG] Cowork renders MCP App iframes but never delivers tool results (ontoolresult never fires)

Resolved 💬 1 comment Opened Apr 15, 2026 by nick-youngblut Closed May 26, 2026

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/ext-apps@0.4.0

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"

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗