modelcontextprotocol / modelcontextprotocol/ext-apps

McpUiHostCapabilities missing tools field — widget-declared tools unimplementable by hosts

Open
#655 0 comments 0 reactions 0 assignees View on GitHub

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:

  1. A widget sends tools: {} in ui/initialize to declare it exposes tools
  2. The host has no spec'd way to advertise it will honor that capability
  3. 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:

  1. Call bridge.listTools() after the widget connects
  2. Merge widget-declared tools into the LLM's available tool set
  3. Route LLM tool calls matching widget tool names back through bridge to the widget's oncalltool handler
  4. 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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.