Summary
McpUiAppCapabilities lets a widget declare that it exposes tools (tools?: { listChanged?: boolean }), but McpUiHostCapabilities has no corresponding tools field. This means:
- A widget sends
tools: {} in ui/initialize to declare it exposes tools
- The host has no spec'd way to advertise it will honor that capability
- The widget cannot check
getHostCapabilities() to know whether the host will ever call bridge.listTools() or route tool calls back through AppBridge
The handshake is one-sided. The widget declares intent; the host has no mechanism to acknowledge it.
Evidence
Confirmed against Claude Desktop (May 2026) via getHostCapabilities():
{
"openLinks": {},
"downloadFile": {},
"serverTools": { "listChanged": true },
"serverResources": { "listChanged": true },
"logging": {},
"updateModelContext": { "text": {}, "image": {} },
"message": { "text": {} }
}
No tools field. The onlisttools handler registered on the widget never fires. The widget's tools: {} capability declaration is silently ignored.
Current type asymmetry
// Widget side — can declare tool capability ✅
interface McpUiAppCapabilities {
tools?: { listChanged?: boolean };
}
// Host side — no tools field ❌
interface McpUiHostCapabilities {
serverTools?: { listChanged?: boolean }; // MCP server → host direction, unrelated
// no field for: host supports calling widget-declared tools
}
What a fix would require
Spec change: Add tools?: { listChanged?: boolean } to McpUiHostCapabilities:
interface McpUiHostCapabilities {
// ...existing fields...
tools?: {
/** Host will call tools/list on the widget after connect and surface results to the LLM */
listChanged?: boolean;
};
}
Host implementation: A host advertising tools: {} would be expected to:
- Call
bridge.listTools() after the widget connects
- Merge widget-declared tools into the LLM's available tool set
- Route LLM tool calls matching widget tool names back through
bridge to the widget's oncalltool handler
- Re-query
bridge.listTools() when it receives notifications/tools/list_changed from the widget
Impact
Without this, oncalltool/onlisttools on the widget side are effectively dead code in any real-world deployment. The pattern enables compelling use cases — widgets exposing derived, context-friendly state to the LLM on demand rather than pushing raw state via updateModelContext — but no host can implement it without a spec'd capability to advertise.
Summary
McpUiAppCapabilitieslets a widget declare that it exposes tools (tools?: { listChanged?: boolean }), butMcpUiHostCapabilitieshas no correspondingtoolsfield. This means:tools: {}inui/initializeto declare it exposes toolsgetHostCapabilities()to know whether the host will ever callbridge.listTools()or route tool calls back throughAppBridgeThe handshake is one-sided. The widget declares intent; the host has no mechanism to acknowledge it.
Evidence
Confirmed against Claude Desktop (May 2026) via
getHostCapabilities():{ "openLinks": {}, "downloadFile": {}, "serverTools": { "listChanged": true }, "serverResources": { "listChanged": true }, "logging": {}, "updateModelContext": { "text": {}, "image": {} }, "message": { "text": {} } }No
toolsfield. Theonlisttoolshandler registered on the widget never fires. The widget'stools: {}capability declaration is silently ignored.Current type asymmetry
What a fix would require
Spec change: Add
tools?: { listChanged?: boolean }toMcpUiHostCapabilities:Host implementation: A host advertising
tools: {}would be expected to:bridge.listTools()after the widget connectsbridgeto the widget'soncalltoolhandlerbridge.listTools()when it receivesnotifications/tools/list_changedfrom the widgetImpact
Without this,
oncalltool/onlisttoolson the widget side are effectively dead code in any real-world deployment. The pattern enables compelling use cases — widgets exposing derived, context-friendly state to the LLM on demand rather than pushing raw state viaupdateModelContext— but no host can implement it without a spec'd capability to advertise.