You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The specification/2026-01-26/apps.mdx contains a code example (around lines 455–465, in the "Transport Layer" section) that is framed as the View→Host handshake but uses the
regular MCP initialize method and clientInfo field. The rest of the same spec — and the current draft — define the Apps-dialect handshake as ui/initialize with appInfo /
appCapabilities. The misleading example leads implementers to send a schema-invalid request that compliant hosts (Claude web and Desktop) reject; the iframe container stays at
visibility: hidden and the user only sees the [This tool call rendered an interactive widget…] placeholder.
To Reproduce
Steps to reproduce the behavior:
Register an MCP Apps tool with _meta.ui.resourceUri and its UI resource with mimeType: "text/html;profile=mcp-app".
In the UI HTML, implement the handshake exactly as the 2026-01-26 example shows:
window.parent.postMessage({
jsonrpc: "2.0", id: 1, method: "initialize",
params: {
protocolVersion: "2026-01-26",
capabilities: {},
clientInfo: { name: "My UI", version: "1.0.0" }
}
}, "*");
Add the server as a custom connector in Claude (web or Desktop).
Call the tool. No iframe appears.
Try again using the names from the draft spec / the rest of 2026-01-26 (ui/initialize, appInfo, appCapabilities). Handshake succeeds, iframe becomes visible.
Expected behavior
The example in 2026-01-26 should match the method and field names used elsewhere in the same document (and in the current draft):
Also worth a one-line note clarifying that regular MCP initialize + clientInfo describes a different protocol layer (server ↔ client), distinct from the Apps-dialect View ↔ Host
handshake.
Logs
Request sent (following the misleading example — note the method and clientInfo):
After renaming clientInfo → appInfo, the host returns a normal McpUiInitializeResult and, after ui/notifications/initialized + ui/notifications/size-changed, the iframe becomes
visible.
Correct shape is already documented elsewhere in the same spec ("App Capabilities in ui/initialize" section, the mermaid lifecycle diagram) and in specification/draft/apps.mdx
(section "App (Guest UI) Capabilities", line ~2233).
Side observation that made this hard to diagnose: @modelcontextprotocol/ext-apps imported via https://esm.sh/@modelcontextprotocol/ext-apps throws TypeError: t.custom is not a
function in App.connect() (looks like a zod version mismatch in the CDN bundle). That error fires before ui/initialize is sent, masking the real spec/host mismatch. Only a
hand-rolled ~40-line postMessage handshake surfaces the Zod error above from Claude.
Verified against Claude.ai and Claude Desktop custom connector over Streamable HTTP on 2026-04-22.
Describe the bug
The specification/2026-01-26/apps.mdx contains a code example (around lines 455–465, in the "Transport Layer" section) that is framed as the View→Host handshake but uses the
regular MCP initialize method and clientInfo field. The rest of the same spec — and the current draft — define the Apps-dialect handshake as ui/initialize with appInfo /
appCapabilities. The misleading example leads implementers to send a schema-invalid request that compliant hosts (Claude web and Desktop) reject; the iframe container stays at
visibility: hidden and the user only sees the [This tool call rendered an interactive widget…] placeholder.
To Reproduce
Steps to reproduce the behavior:
window.parent.postMessage({
jsonrpc: "2.0", id: 1, method: "initialize",
params: {
protocolVersion: "2026-01-26",
capabilities: {},
clientInfo: { name: "My UI", version: "1.0.0" }
}
}, "*");
Expected behavior
The example in 2026-01-26 should match the method and field names used elsewhere in the same document (and in the current draft):
window.parent.postMessage({
jsonrpc: "2.0", id: 1, method: "ui/initialize",
params: {
protocolVersion: "2026-01-26",
appInfo: { name: "My UI", version: "1.0.0" },
appCapabilities: { availableDisplayModes: ["inline"] }
}
}, "*");
Also worth a one-line note clarifying that regular MCP initialize + clientInfo describes a different protocol layer (server ↔ client), distinct from the Apps-dialect View ↔ Host
handshake.
Logs
Request sent (following the misleading example — note the method and clientInfo):
{
"jsonrpc": "2.0", "id": 1, "method": "ui/initialize",
"params": {
"protocolVersion": "2026-01-26",
"clientInfo": { "name": "ANDY Hello Test", "version": "1.0.0" },
"appCapabilities": { "availableDisplayModes": ["inline"] }
}
}
Host response (Claude web):
{
"jsonrpc": "2.0", "id": 1,
"error": {
"code": -32603,
"message": "[{"expected":"object","code":"invalid_type","path":["params","appInfo"],"message":"Invalid input"}]"
}
}
After renaming clientInfo → appInfo, the host returns a normal McpUiInitializeResult and, after ui/notifications/initialized + ui/notifications/size-changed, the iframe becomes
visible.
Additional context
(section "App (Guest UI) Capabilities", line ~2233).
function in App.connect() (looks like a zod version mismatch in the CDN bundle). That error fires before ui/initialize is sent, masking the real spec/host mismatch. Only a
hand-rolled ~40-line postMessage handshake surfaces the Zod error above from Claude.