Skip to content

fix(core): advertise the MCP Apps UI extension only from a client that renders Apps - #2471

Merged
cliffhall merged 4 commits into
v2/mainfrom
v2/fix/2403-apps-extension-opt-in
Sep 24, 2026
Merged

cliffhall merged 4 commits into
v2/mainfrom
v2/fix/2403-apps-extension-opt-in

Conversation

@cliffhall

@cliffhall cliffhall commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Closes #2403

Problem

InspectorClient advertised io.modelcontextprotocol/ui by default in every client. The CLI and TUI cannot render an MCP App, yet they told servers they could — and servers use that advertisement to decide whether to return an App.

Change

  • core/mcp/extensions.ts: the UI registry entry is marked requiresAppRenderer. A new shared helper, isAdvertisedByDefault(ext, rendersApps), resolves the default: an entry that requires a renderer defaults off unless the client can render Apps. buildClientExtensions uses it, and an explicit advertisedExtensions override still wins. Tasks and Skills are unaffected.
  • InspectorClientOptions.rendersApps: new option, default false. Supplying an appElicitation renderer implies it, since that is already a claim to host an App.
  • Web: claims Apps rendering only when the backend supplied a sandbox URL, using the same confirmed-sandbox signal appElicitation already keys on (rendersApps: sandboxUrlRef.current !== undefined). With a sandbox, which is the normal setup, behavior is unchanged. Without one, the UI extension is no longer advertised by default.
  • Web Server Settings: App passes the same sandbox signal (rendersApps) through ServerSettingsModal to ServerSettingsForm, and both use isAdvertisedByDefault. So the MCP Apps UI checkbox shows what the client will actually declare (unchecked by default with no sandbox), and checking it with no sandbox saves a real true override instead of dropping it as "back to the default".
  • TUI: passes nothing, so it no longer claims the extension.
  • CLI: no longer claims it by default and gains an explicit opt-in, --advertise-apps, for probing a server that only exposes its App tools to a client that advertises App support (the issue's "explicit opt-in flag"). It is rejected on the commands that never connect (--list-stored-auth, --print-handoff, servers/list, servers/show), like --strict and --verify. It is documented in the CLI README next to --app-info.

Tests

  • extensions.test.ts: existing cases now pass rendersApps: true. New cases cover the non-rendering default (UI omitted), rendersApps: false, the explicit override, that Tasks and Skills are not gated, and isAdvertisedByDefault over the whole registry.
  • inspectorClient-app-elicitation.test.ts: a client with no renderer advertises no UI extension; rendersApps: true advertises the MIME type alone; an override turns it on.
  • useConnectionLifecycle.test.tsx: the UI extension is advertised with a sandbox and absent without one.
  • ServerSettingsForm.test.tsx / ServerSettingsModal.test.tsx: the checkbox state for both values of rendersApps, an explicit override shown with no renderer, and the modal saving {ui: true} with no renderer.
  • CLI app-info.test.ts: end to end against an HTTP test server that gates a tool on io.modelcontextprotocol/ui. The tool is absent by default and present with --advertise-apps, and the flag is rejected on all four paths that never connect.

Each fix was mutation-checked: reverting it fails its new tests. npm run local:gate passes.

Screenshots: none. The only visible web change is the MCP Apps UI checkbox starting unchecked when the backend supplies no sandbox URL; with a sandbox, the UI looks the same as before.

🤖 Generated with Claude Code

…nders Apps (#2403)

InspectorClient advertised io.modelcontextprotocol/ui by default in every
client, so the CLI and TUI told servers they support MCP Apps although they
cannot render one. The UI registry entry now requires an App renderer: its
default applies only when the new `rendersApps` option is set (or an
app-elicitation renderer is supplied). The web client sets it; the CLI and
TUI no longer claim the extension. An explicit advertisedExtensions override
still wins, and the CLI exposes it as `--advertise-apps` for probing servers
that gate App tools on the advertisement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Sep 24, 2026
@cliffhall
cliffhall requested a balanced review from Copilot September 24, 2026 03:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Web still advertises App support when its sandbox renderer is unavailable.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Restricts MCP Apps capability advertisement to rendering clients and adds an explicit CLI opt-in.

Changes:

  • Gates the UI extension behind rendersApps.
  • Enables it for web and via CLI --advertise-apps.
  • Adds core and CLI coverage.
File Description
core/​mcp/​types.ts Adds the renderer capability option.
core/​mcp/​inspectorClient.ts Applies renderer-aware advertisement.
core/​mcp/​extensions.ts Gates the UI extension default.
clients/​web/​src/​test/​integration/​mcp/​extensions-mimetype.test.ts Updates MIME advertisement coverage.
clients/​web/​src/​test/​core/​mcp/​inspectorClient-app-elicitation.test.ts Tests client capability combinations.
clients/​web/​src/​test/​core/​mcp/​extensions.test.ts Tests extension gating and overrides.
clients/​web/​src/​hooks/​useConnectionLifecycle.ts Enables Apps advertisement for web.
clients/​cli/​src/​handlers/​method-types.ts Adds the CLI argument type.
clients/​cli/​src/​cli.ts Implements --advertise-apps.
clients/​cli/​README.md Documents explicit opt-in behavior.
clients/​cli/​__tests__/​app-info.test.ts Tests CLI advertisement end to end.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread clients/web/src/hooks/useConnectionLifecycle.ts Outdated
…#2471 review)

Web set rendersApps unconditionally, so with no sandbox URL — where the
Apps screen reports that MCP Apps cannot run — it still advertised the UI
extension. Gate it on the confirmed sandbox URL, as appElicitation already
is. A Server Settings override can still force the extension on.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1 — 1 finding, fixed in d60a7d1.

  • Advertise App rendering only when a sandbox URL is available — accepted. It is a gap in what this PR added: web now claims Apps rendering only with a confirmed sandbox URL, matching the adjacent appElicitation gate. Covered by new assertions in useConnectionLifecycle.test.tsx (mutation-checked).

Suppressed comments: none. local:gate green on the fix.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The settings UI can misreport effective advertisement, and the new CLI flag is silently ignored on no-connection commands.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Reject --advertise-apps on commands without MCP connections

clients/​cli/​src/​cli.ts:770

--advertise-apps is accepted on the no-connection paths (--list-stored-auth, --print-handoff, servers/list, and servers/show), where parseArgs returns before callMethod, so the flag is silently ignored despite a successful command. This parser consistently rejects inert flags before those returns (cli.ts:943-974; see the corresponding short-circuit tests). Add an early validation that rejects --advertise-apps for commands that do not initialize an MCP connection.

Comment thread core/mcp/extensions.ts
…apps (#2471 review)

The Server Settings form resolved the UI extension's checkbox from the raw
registry default, so with no sandbox it showed "advertised" although the
client no longer declares it — and the modal's reconverge-to-default logic
made a true override impossible to save. Both now use the shared
isAdvertisedByDefault(ext, rendersApps), fed from App's sandbox URL.

The CLI rejected other connection-only flags ahead of its short-circuit
paths but accepted --advertise-apps there and ignored it; it is now
rejected on --list-stored-auth, --print-handoff and servers/list|show.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2: 2 findings, both accepted and fixed in 2ebadf4. local:gate is green.

  • Server Settings checkbox ignores renderer-aware advertised default (inline): accepted. My round-1 change caused it. The toggle and the modal's override handling now share isAdvertisedByDefault with the builder, and App passes in whether the sandbox is available. Correction to my round-1 reply: I said a Server Settings override could still force the extension on when no sandbox is available. Before this fix it could not, because re-checking the box was treated as a return to the default and the override was dropped. It can now.
  • Reject --advertise-apps on commands without MCP connections (listed under "Previously missed", which has no thread): accepted. The flag is now rejected before the short-circuit returns (--list-stored-auth, --print-handoff, servers/list, servers/show), the same way --strict and --verify already are. This is covered by an it.each over all four paths; disabling the check fails all 4.

Screenshots: the only visible web change is the MCP Apps UI checkbox starting unchecked when the backend supplies no sandbox URL. The normal configuration looks the same as before, so I've left screenshots out.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟢 Approval recommended

The implementation satisfies the linked issue with focused cross-client coverage; remaining feedback only concerns stale PR metadata.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)

Comment thread clients/web/src/hooks/useConnectionLifecycle.ts
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 3: approval recommended. The one new finding (the PR description was out of date) is fixed: the description now covers the sandbox-gated web behavior and all added tests. The other open item, the Server Settings checkbox, was fixed in 2ebadf4 and answered in its thread. No suppressed comments.

Review loop closed: this round held no code findings, and only the PR description changed since it ran, so another round would re-review unchanged code.

@cliffhall
cliffhall merged commit 2806b4f into v2/main Sep 24, 2026
4 checks passed
@cliffhall
cliffhall deleted the v2/fix/2403-apps-extension-opt-in branch September 24, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CLI and TUI advertise io.modelcontextprotocol/ui although they cannot render MCP Apps

2 participants