Extensions: first-class advertised-extensions map (Phase 1 of #1633) - #1742
Conversation
Collapse the two ad-hoc `capabilities.extensions` spreads in the InspectorClient constructor (EMA, gated on enterpriseManaged; the modern Tasks extension, always-on) into a single builder that is the source of truth for what the client advertises. - core/mcp/extensions.ts: `ADVERTISABLE_EXTENSIONS` registry (id + label + default-on flag), the `EMA_EXTENSION_KEY` constant, and `buildClientExtensions()`. The registry is the shared list the Server Settings toggle UI (Phase 2, #1739) will drive; EMA stays a conditional built-in (auth-mode driven, not a free toggle). - InspectorClientOptions.advertisedExtensions: per-extension overrides (present key wins over the registry default) so a future UI can toggle advertised extensions as a debugging knob. - Behavior preserved: tasks defaults to advertised, so the map is non-empty and rides every modern request envelope; EMA advertised iff enterpriseManaged. The raw modern-task envelope re-stamp is left untouched (it fires only on the raw tasks channel, which requires the extension anyway). Tests: extensions.test.ts covers the registry and every builder branch (defaults, EMA on/off, override enable/disable, unknown key) at 100%. The tasks-era and EMA integration suites still assert the same wire advertisement. `advertisedExtensions` is plumbed but not yet wired to the form (that lands in Phase 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
@claude review |
|
Claude finished @cliffhall's task in 1m 23s —— View job Review — Extensions advertised-map (Phase 1 of #1633)
Nicely scoped refactor. The two ad-hoc Correctness — behavior preserved ✅
Minor — a comment goes stale under Phase 2 (non-blocking)
// extensions always carries the tasks extension (advertised at
// construction), so spreading it is never a no-op.This is accurate today (no way to disable Tasks — Test coverage — thorough, one optional guardThe new suite exercises every branch of the builder and the registry shape (100% as claimed). One invariant worth locking in explicitly, since it's central to the design ("EMA is not a free toggle"): it("does not let an override advertise EMA (auth-mode only)", () => {
const map = buildClientExtensions({
enterpriseManaged: false,
advertised: { [EMA_EXTENSION_KEY]: true },
});
expect(map).not.toHaveProperty(EMA_EXTENSION_KEY);
});The existing "ignores override keys that are not in the registry" test covers this generically (EMA isn't in the registry), but an EMA-specific assertion documents the intent and would catch a future regression where someone mistakenly adds EMA to Nits
Overall: LGTM. Clean single-responsibility module, faithful behavior preservation, and a genuine improvement to the drift risk between the client builder and the upcoming settings UI. The one comment-staleness item is a Phase-2 cleanup, and the extra test is optional. |
…ent) - extensions.test.ts: add an EMA-specific assertion that an override for EMA_EXTENSION_KEY cannot advertise it (auth-mode only) — documents intent and guards against EMA being added to ADVERTISABLE_EXTENSIONS by mistake. - inspectorClient.ts: rewrite the withModernTaskEnvelope tasks-stamp comment so it stays accurate once Phase 2 lets a user disable general tasks advertisement — the force-stamp is load-bearing (the raw tasks/* channel requires the extension), not a redundant re-add. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Thanks for the review! Addressed in 6153e48:
CI is green (web coverage gate 4396 tests, @claude review |
|
Claude finished @cliffhall's task in 43s —— View job Re-review — Extensions advertised-map (Phase 1 of #1633)
Both items from the prior review are cleanly addressed in 1. Envelope comment rewrite ✅
// Force-stamp the tasks extension regardless of what the client
// advertised at construction: the raw `tasks/*` channel requires it, and
// a user may disable general tasks advertisement via `advertisedExtensions`
// (#1738). So this stamp is load-bearing, not a redundant re-add.This is the right framing — it explains why the unconditional stamp is correct (the raw 2. EMA-override test ✅
3.
|
The step-up authorize tests intermittently timed out on the final "Step-up authorization succeeded" frame. Root cause is a test-infra timing budget, not product code: waitForFrame/waitUntil polled only 25× (25ms tick ≈ 625ms), and the step-up flow adds an extra async OAuth-runner hop plus React commits that can exceed that under CI load with v8 coverage. Hoist the poll count into a named POLL_TRIES = 100 (~2.5s ceiling). A poll exits the instant its predicate is true, so a higher ceiling never slows a passing assertion — it only widens the margin for the slow path. Verified 5× green (3 plain + 2 under --coverage), including the previously-flaky step-up cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Also fixed the flaky TUI step-up test in this PR (3770f9c), since it can trip CI here. Root cause was a test-infra timing budget, not product code: @claude review |
|
Claude finished @cliffhall's task in 1m 5s —— View job Review — TUI step-up de-flake (
|
|
Thanks — noted both:
No further changes. Both review rounds are green — ready to merge. |
The POLL_TRIES change pushed waitForFrame's signature past the print width; Prettier wraps it. (Root has no aggregate format script — format is per-client, so this was missed locally.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
…elcontextprotocol#1699) The server name and Disconnect button are removed by two independent Mantine Transition exits. The test waitFor'd the server-name removal, then did a synchronous queryByRole check for the button — on a slow runner the button's exit lags the name's, so it was still in the DOM at the check. Wrap the button "removed" assertion in its own waitFor. Part 2 of modelcontextprotocol#1699 (TUI OAuth step-up) was already fixed in PR modelcontextprotocol#1742 via POLL_TRIES=100. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
Closes #1738
Phase 1 of #1633 (Extensions capability UI + MCP Apps alignment). Collapses the two ad-hoc
capabilities.extensionsspreads in the InspectorClient constructor into a single builder that is the source of truth for what the client advertises — the foundation Phase 2 (#1739, settings toggle) and Phase 3 (#1740, Connection Info +io.modelcontextprotocol/ui) build on.What changed
core/mcp/extensions.ts(new)ADVERTISABLE_EXTENSIONSregistry —{ key, label, defaultAdvertised }. The shared list the Server Settings toggle UI (Phase 2) will drive, so the client builder and the UI never drift.EMA_EXTENSION_KEYconstant (SEP-2350 enterprise-managed authorization).buildClientExtensions({ enterpriseManaged, advertised })— assembles the map: registry entries resolve to advertised/not via the user override with the registry default as fallback; EMA is layered on as an auth-mode-driven built-in (not a free toggle).core/mcp/types.ts— newInspectorClientOptions.advertisedExtensions(per-extension overrides, keyed by extension id; a present key wins over the registry default). Plumbed through the client here; wired to the form in Phase 2.core/mcp/inspectorClient.ts— constructor now callsbuildClientExtensions()instead of the two hardcoded spreads.Behavior preserved
io.modelcontextprotocol/tasks, SEP-2663) defaults to advertised, so the map is non-empty and rides every modern request envelope — the per-request declaration a server needs before it may return aCreateTaskResult.enterpriseManaged, exactly as before.withModernTaskEnvelope) is intentionally left untouched: it fires only on the rawtasks/*channel, which requires the extension anyway.Tests
clients/web/src/test/core/mcp/extensions.test.ts: registry shape + every builder branch (defaults, EMA on/off, override enable/disable, unknown key). 100% lines/statements/functions/branches on the new module.npm run cigreen (validate → coverage ≥90% gate → smoke → Storybook).Notes
io.modelcontextprotocol/uiadvertisement and its likely cast land in Phase 3 (Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) #1740). The follow-up issue to remove all ext-apps v1 shims will be filed once Extensions: Connection Info both eras + io.modelcontextprotocol/ui advertisement (Phase 3 of #1633) #1740 is in review, so it can enumerate every shim site.🤖 Generated with Claude Code
https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5