Skip to content

Safari/WebKit: sandbox_proxy drops inner iframe messages because event.source is window, breaking MCP App initialization #1203

@kentcdodds

Description

@kentcdodds

Inspector Version

  • 0.21.2

Describe the bug
A Safari/WebKit-specific MCP Apps rendering failure occurs because the Inspector sandbox relay in server/static/sandbox_proxy.html assumes inner iframe messages will always arrive with:

event.source === inner.contentWindow

In Safari/WebKit, we observed the inner iframe's postMessage(...) arriving at the outer sandbox with:

  • event.origin === OWN_ORIGIN
  • event.source === window

instead of event.source === inner.contentWindow.

Because of that, the sandbox drops the app's ui/initialize request instead of forwarding it to the host. The MCP App then never receives a JSON-RPC init response and remains blank or partially initialized.

The failing implementation appears to be in server/static/sandbox_proxy.html, not in app-side render logic.

To Reproduce
Steps to reproduce the behavior:

  1. Start @modelcontextprotocol/inspector.
  2. Connect it to an MCP server that returns an MCP App / generated UI.
  3. Open the same generated UI in a Chromium browser such as Brave or Chrome and confirm it renders.
  4. Open the same generated UI in Safari desktop or another WebKit-based host/surface.
  5. Observe that the app sends ui/initialize, but Safari/WebKit never completes the handshake and the UI remains blank.

Expected behavior
The same MCP App should initialize and render in Safari/WebKit just as it does in Chromium. The sandbox relay should forward valid same-origin inner iframe messages even when Safari reports them with event.source === window.

Screenshots
I do not have screenshots included in this report.

Environment (please complete the following information):

  • OS: macOS and iOS
  • Browser: Safari desktop, iOS WebKit surfaces, and Brave/Chrome for working comparison

Additional context
We debugged this with runtime instrumentation in Safari and Chromium side-by-side.

What we observed before the patch:

  • Chromium / Brave works:
    • app sends ui/initialize
    • sandbox relays it
    • host returns JSON-RPC response
    • app renders normally
  • Safari / WebKit fails:
    • app sends ui/initialize
    • outer sandbox receives the message
    • message is classified as unexpected because event.source !== inner.contentWindow
    • host never receives ui/initialize
    • app times out initialization and remains blank

What we observed after a local patch:

  • allowing the same-origin Safari case where event.source === window made the sandbox relay ui/initialize
  • the host returned the JSON-RPC response
  • the app initialized and rendered correctly

Current relay logic:

} else if (event.source === inner.contentWindow) {
  if (event.origin !== OWN_ORIGIN) {
    console.error(
      "[Sandbox] Rejecting message from inner iframe with unexpected origin:",
      event.origin,
      "expected:",
      OWN_ORIGIN,
    );
    return;
  }
  window.parent.postMessage(event.data, EXPECTED_HOST_ORIGIN);
}

Suggested fix:

} else if (
  event.source === inner.contentWindow ||
  (event.origin === OWN_ORIGIN && event.source === window)
) {
  if (event.origin !== OWN_ORIGIN) {
    console.error(
      "[Sandbox] Rejecting message from inner iframe with unexpected origin:",
      event.origin,
      "expected:",
      OWN_ORIGIN,
    );
    return;
  }
  window.parent.postMessage(event.data, EXPECTED_HOST_ORIGIN);
}

The important part is preserving the origin check while relaxing the brittle source identity assumption for Safari/WebKit.

There is a separate ext-apps transport race issue in modelcontextprotocol/ext-apps#542, but this bug appears distinct. In this case, the blocking failure was the sandbox relay dropping a valid inner-frame message on Safari/WebKit due to a strict event.source check.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions