modelcontextprotocol / modelcontextprotocol/ext-apps
Add UI "preload" Control and "ui/close" Signal for MCP Apps Tool-Driven Widgets
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.9k
- Forks
- 387
- Avg merge
- 3h 21m
- Merged PRs (30d)
- 6
Description
Is your feature request related to a problem? Please describe.
MCP Apps currently binds a tool to a UI resource through _meta.ui.resourceUri at tools/list time. When the tool is invoked, the host may preload or mount the associated UI in parallel with tools/call, before the tool result is available.
This eager preloading improves perceived performance when the widget is actually needed, but it creates a mismatch in cases where the tool later determines that no UI should be shown.
For example, a routing or classification tool may initially appear to support a widget, but at invocation time decide that a text-only response is more appropriate.
In these cases, the host may already have preloaded or even mounted the widget by the time the tool result arrives. Without a standard mechanism, the server cannot prevent or undo this, resulting in a brief flash of a UI that is immediately dismissed, which can feel like a rendering glitch.
Today, hosts can implement proprietary heuristics, but there is no host-neutral MCP Apps convention for either:
- preventing unnecessary preloading, or
- explicitly closing a preloaded widget after tool execution.
Describe the solution you'd like
We propose a two-part, minimal extension to the existing model:
1. Simplified preload control in tools/list
Introduce a single explicit opt-out mode while keeping the default behavior implicit:
- Default behavior:
"optional"(or omitted) → host may preload or defer at its discretion "disabled": host must not preload or mount the UI automatically
{
"name": "example_tool",
"_meta": {
"ui": {
"resourceUri": "https://example.com/widget",
"preload": "disabled"
}
}
}
This keeps the model simple and explicit:
"optional"(default, or omitted) → host decides (may preload or defer)"disabled"→ never preload or auto-mount UI
This avoids over-specifying host behavior while still giving servers a way to prevent wasted work and UI flicker when they know the UI is unlikely to be used.
2. Tool-result-driven UI close signal
Add a standard ui/close boolean to the tool result _meta.
{
"content": [
{
"type": "text",
"text": "The request was answered without using the widget."
}
],
"_meta": {
"ui/close": true
}
}
When the host receives _meta["ui/close"] = true, it should close/unmount any UI associated with that tool invocation.
This is necessary because even with preload control, the final decision to suppress UI may only be known after execution.
Describe alternatives you've considered
1. Host-specific metadata (e.g. openai/closeWidget)
A host could define proprietary signals such as:
{
"_meta": {
"openai/closeWidget": true
}
}
However:
- not interoperable across MCP hosts
- still suffers from the “flash then disappear” UX problem if preloading already occurred
- pushes protocol-level behavior into vendor-specific extensions
2. Widget URI only in tools/call response
Instead of declaring resourceUri in tools/list, the server could return it dynamically in tools/call.
However:
- defeats the purpose of preloading and caching widgets
- prevents hosts from preparing UI ahead of time
- makes tool capabilities opaque until invocation time
- breaks the current MCP Apps model where UI binding is discoverable
3. Full widget content in tools/call response
Instead of a URI, the tool could return the full UI payload.
However:
- prevents host-side caching and reuse of widgets
- increases response payload size significantly
- removes any ability for the host to reason about UI capabilities at discovery time
- makes it impossible to optimize rendering or prefetching strategies
Additional context
The key tension in MCP Apps is between:
- early binding for performance (
tools/list→ UI discovery), and - late decision-making in tool execution (
tools/call→ actual need for UI)
The proposed design addresses both sides:
"optional"(default) allows hosts to optimize by preloading or deferring"disabled"prevents unnecessary eager loading when the server knows UI is unlikely to be usedui/closecorrects cases where UI was still preloaded but ultimately not needed
Together, they provide a minimal, backward-compatible lifecycle control mechanism without introducing new RPC methods or breaking existing tool discovery semantics.
Importantly:
- Hosts that ignore
preloadorui/closeremain compliant - Existing
resourceUribehavior is unchanged - No new transport or handshake is required
- The model remains fully incremental
This combination avoids the UX issue of “widget flashes then disappears” while preserving the performance benefits of preloading when appropriate.
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 reviewing the existing tools/list UI binding through _meta.ui.resourceUri and the tools/call result metadata described in the issue. Trace how hosts handle preloading, mounting, and tool-result metadata; done means the proposed preload control and ui/close behavior are specified consistently without changing existing resource binding semantics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100