modelcontextprotocol / modelcontextprotocol/ext-apps
McpUiHostCapabilities missing tools field — widget-declared tools unimplementable by hosts
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 387
- Avg merge
- 3h 21m
- Merged PRs (30d)
- 6
Description
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: {}inui/initializeto 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 callbridge.listTools()or route tool calls back throughAppBridge
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
bridgeto the widget'soncalltoolhandler - Re-query
bridge.listTools()when it receivesnotifications/tools/list_changedfrom 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading the McpUiAppCapabilities and McpUiHostCapabilities definitions and tracing the ui/initialize and getHostCapabilities handshake. Compare the existing serverTools capability with the proposed tools field. Done means the host capability is specified and host behavior covers listing widget tools, routing calls, and handling tools/list_changed notifications.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend-api-design
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100