Skip to content

Add UI "preload" Control and "ui/close" Signal for MCP Apps Tool-Driven Widgets #744

Description

@sunnymitramsft

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions