modelcontextprotocol / modelcontextprotocol/ext-apps

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

Open
#744 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
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 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.

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.