fix(web): render the connected header when a modern server omits serverInfo (#1772)#1773
Conversation
…erInfo (#1772) A modern-era `server/discover` makes `serverInfo` OPTIONAL (SHOULD, not MUST — it's stamped in `_meta["io.modelcontextprotocol/serverInfo"]`). A conforming modern server may omit it, so the client stays `connected` with `serverInfo === undefined`. `initializeResult` was gated on `serverInfo`, returning `undefined` in that case, and the connected header (with its entire tab bar) is gated on `initializeResult` downstream (`InspectorView` `connectionStatus === "connected" && initializeResult`) — so such a server connected but showed NO top menu at all. Fix: gate `initializeResult` on `connectionStatus` alone and fall back to the active server's catalog name when `serverInfo` is absent, so the header + tabs render. Everything downstream already tolerates an empty version (and now an inferred name); legacy `initialize` always carries serverInfo, so the fallback only fires for a modern server that skipped the optional field. Adds App tests: initializeResult is built when connected without serverInfo, is absent while disconnected, and uses the reported serverInfo name when present. 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 4m 10s —— View job Code review — PR #1773
Verdict: the diagnosis and the fix are right, and it's the minimal change — 1. The synthesized
|
Review of PR #1773: 1. Fidelity — the synthesized catalog name was rendered under Connection Info → "Server Implementation → Name" indistinguishably from a server-reported value, hiding exactly what a user opens that modal to check (did the modern server stamp serverInfo?). Thread a `serverInfoReported` flag App → ConnectionInfoModal → ConnectionInfoContent; when false, Name/Version render "— (not reported by server)" instead of the inferred name. Required on the Modal (app path stays type-safe), optional-default-true on the presentational Content (leaf fixtures with a real serverInfo need no change). 2. `version ?? "—"` → `version || "—"` so a reported-but-empty version reads as unknown rather than a blank row (the empty-string path this PR makes reachable). 3. Update the now-stale App.test mock comment (initializeResult is built when connected as of this PR). 4. Add an App test that actually exercises the fallback — connect server "A" and assert the catalog name ("PlotRocket") surfaces, not just the degenerate empty case. 5. Hoist `activeServer` above `initializeResult` and reuse it, removing the duplicate `servers.find` and shrinking the dep list to `[activeServer]` so initializeResult stops re-identifying on every catalog change. 6. Soften the "only ever fires for a modern server" comment — there's also the batched instant on any connect between the status and serverInfo dispatches. Adds ConnectionInfoContent tests for the "not reported" and empty-version paths. Verified: web validate (3478 tests), the >=90 coverage gate, and Storybook (456) all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
All six items addressed in 9865afc. Thanks — #1 in particular was a real fidelity gap for a debugging tool.
Verified in the worktree: web |
|
@claude review |
|
Claude finished @cliffhall's task in 2m 38s —— View job Code review — PR #1773 (round 2)
Verdict: all six round-1 items are genuinely addressed, and the
Branch coverage of the new code looks complete without leaning on incidental hits: Three small things left, none blocking. 1.
|
Round-2 review of PR #1773: 1. `displayName` had the same blank-row bug just fixed for version — a reported `serverInfo: { name: "" }` (legacy `initialize` mandates the field, not a non-empty value) rendered a blank Name row. `serverInfo.name || "—"` makes the two rows symmetric. Adds a test for the empty reported name. 2. Adds a `ServerInfoNotReported` ConnectionInfoContent story (modern era, `serverInfoReported: false`) with a play function asserting Name/Version render "— (not reported by server)" and the catalog name is not shown — documents the modern-omitted state and doubles as a browser guard. 3. Pins the App → ConnectionInfoModal `serverInfoReported` wiring: adds an open-connection-info control to the mocked InspectorView and two App tests that open the real modal — one asserts "not reported" for the default (serverInfo undefined) mock, the other asserts the reported name for a stamped serverInfo. Without this, hard-coding the flag to `true` would keep the suite green and silently reintroduce the fidelity bug. Verified: web validate (3481 tests), the >=90 coverage gate, and Storybook (457) all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
All three round-2 items addressed in e29efff.
Verified in the worktree: web validate (3481 tests), the ≥90 coverage gate, and Storybook (457) all green. |
|
@claude review |
|
Claude finished @cliffhall's task in 4m 11s —— View job Code review — PR #1773 (round 3)
Verdict: all three round-2 items are done and done correctly. The fix is now complete and self-consistent: the header/tab bar survives a
Item 3 is the one that mattered and it's pinned the right way — the tests go through the real 1. Nit — the
|
…rResult (#1772) Round-3 review of PR #1773 (all non-blocking consistency/fidelity): 1. The Protocol row was the only value cell without a falsy fallback; App supplies `protocolVersion ?? ""`, so an empty one (a partial `fetchServerInfo()` throw) rendered a blank row. `{protocolVersion || "—"}` completes the sweep; adds a test for an empty protocol version. 2. Export `NOT_REPORTED` from ConnectionInfoContent (same pattern as the existing `CLEAR_OAUTH_STATE_AND_DISCONNECT_LABEL` export) and import it in the test, story, and App.test instead of re-typing the copy in four places. 3. The `ServerInfoNotReported` story set `protocolEra: "modern"` but omitted `discoverResult`, so the Discovery section didn't render — a real modern server that skipped the `_meta` stamp still has a discover result. Add one (no serverInfo), so the story accurately depicts the case and shows Discovery doesn't leak a name either. Verified: web validate (3482 tests), the >=90 coverage gate, and Storybook (457) all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
All three round-3 items addressed in a06a1e4 (all non-blocking, but they finish the consistency sweep):
Verified: web validate (3482 tests), the ≥90 coverage gate, and Storybook (457) all green. |
|
@claude review |
|
Claude finished @cliffhall's task in 3m 50s —— View job Code review — PR #1773 (round 4)
Verdict: all three round-3 items are done correctly, and I found nothing new that needs changing. The change is coherent end to end:
I re-derived the em-dash counts rather than trusting them: Three optional notes, none of which I'd hold the PR for. 1. Note — the "synthesized value + a flag saying don't trust it" shape is a parallel channel
The cleanest alternative — keep 2. Nit —
|
…on (#1772) Round-4 review of PR #1773 (LGTM; two cosmetic nits, item 1 was a recorded tradeoff needing no change): 2. Rename the cross-module export `NOT_REPORTED` → `SERVER_INFO_NOT_REPORTED_LABEL` so it reads with context at the import site, matching the self-describing `CLEAR_OAUTH_STATE_AND_DISCONNECT_LABEL` next to it. Updated all four sites. 3. Comment the `PlotRocket` negative assertion: it's an exact-match query, so it deliberately doesn't match the mocked InspectorView's prefixed `name:PlotRocket` harness span — only a bare "PlotRocket" (the modal's Name cell). Verified: web validate (3482 tests), the >=90 coverage gate, and Storybook (457) all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
|
Round-4 was LGTM — applied the two cosmetic nits in 7e01c25 (item 1 was a recorded tradeoff you explicitly said needs no change; agreed — the two-consumer invariant is pointed at
Verified: web validate (3482 tests), the ≥90 coverage gate, and Storybook (457) all green. |
|
@claude review |
|
Claude finished @cliffhall's task in 2m 49s —— View job Code review — PR #1773 (round 5)
Verdict: LGTM. Both round-4 cosmetic nits are applied and I found no new defects. The change is coherent:
One thing I verified this round that hadn't been checked: no tab-bar flashUn-gating Optional note — the App-side fallback is object-level (
|
|
LGTM confirmed across rounds 4 and 5 — thanks for the thorough passes. On the optional note (object-level This PR is ready to merge. |
Closes #1772
Problem
Connecting to a modern-era MCP server whose
server/discoveromits the (now-optional)serverInfoproduces aconnectedsession with no header tab bar at all — no Servers / Tools / Resources / Tasks, just the "MCP Inspector" title. The server is effectively uninspectable through the UI even though the connection succeeded.Reproduced against
test-servers/configs/tasks-modern-http.json(port 3222): green "Connected", modernserver/discover+tools/listin the Protocol view, but no tabs. Confirmed via live DOM thatdata-status="connected"while the header renders the disconnected/title branch — i.e.initializeResultwas falsy despite being connected.Root cause
InspectorViewgates the connected header (and its whole tab bar) onconnectionStatus === "connected" && initializeResult.App'sinitializeResultreturnedundefinedwhenever!serverInfo.serverInfocomes from the SDK'sgetServerVersion(), filled fromserverInfoFromDiscover(), which readsdiscover._meta["io.modelcontextprotocol/serverInfo"]. The modern spec makes that optional (SHOULD, not MUST), so a conforming modern server may omit it →serverInfostaysundefined→initializeResultundefined→ the header collapses to the title branch.Legacy
initializealways carriesserverInfo, so only modern servers hit this.Fix
Gate
initializeResultonconnectionStatusalone (never onserverInfo— matching the existing deliberate choice not to gate onprotocolVersion), and fall back to the active server's catalog name whenserverInfois absent. Everything downstream already tolerates an empty version (and now an inferred name). The fallback only ever fires for a modern server that skipped the optional field; a reportedserverInfostill wins.Tests
Adds three
Apptests:initializeResultis built when connected withoutserverInfo, is absent while disconnected, and uses the reportedserverInfoname when present. Full web unit suite green.Note
Surfaced while manually testing #1771 (the AGENTS.md
.withProps()styling sweep), but it's a pre-existing connection/modern-era bug — unrelated to that PR (which changes none of the header-gate /initializeResult/serverInfo/InspectorView/ core-connection code).🤖 Generated with Claude Code