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 used
ui/close corrects 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
preload or ui/close remain compliant
- Existing
resourceUri behavior 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.
Is your feature request related to a problem? Please describe.
MCP Apps currently binds a tool to a UI resource through
_meta.ui.resourceUriattools/listtime. When the tool is invoked, the host may preload or mount the associated UI in parallel withtools/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:
Describe the solution you'd like
We propose a two-part, minimal extension to the existing model:
1. Simplified preload control in
tools/listIntroduce a single explicit opt-out mode while keeping the default behavior implicit:
"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 UIThis 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/closeboolean 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:
2. Widget URI only in
tools/callresponseInstead of declaring
resourceUriintools/list, the server could return it dynamically intools/call.However:
3. Full widget content in
tools/callresponseInstead of a URI, the tool could return the full UI payload.
However:
Additional context
The key tension in MCP Apps is between:
tools/list→ UI discovery), andtools/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 neededTogether, they provide a minimal, backward-compatible lifecycle control mechanism without introducing new RPC methods or breaking existing tool discovery semantics.
Importantly:
preloadorui/closeremain compliantresourceUribehavior is unchangedThis combination avoids the UX issue of “widget flashes then disappears” while preserving the performance benefits of preloading when appropriate.