feat(connection-info): wire Connection Info modal to the connection handshake#1377
feat(connection-info): wire Connection Info modal to the connection handshake#1377cliffhall wants to merge 14 commits into
Conversation
Closes #1364 - Add ServerInfoModal wrapping the existing ServerInfoContent, mirroring the ServerSettingsModal layout. - Gate the Server Info button on ServerCard to render only when the connection is "connected". - Expose `getClientCapabilities()` on InspectorClient (and protocol + FakeInspectorClient), and surface it through useInspectorClient so the modal can render the client side of the capability handshake without poking the SDK Client's private state. - App.tsx: open the modal from the gated Server Info button, derive the active server's transport and OAuth details (synchronously, from the guided-OAuth state snapshot + persisted settings), and reset the open flag on InspectorClient `disconnect` so the modal does not auto-reopen on a future reconnect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…entViewer - Drop the in-content "Server Information" Title from ServerInfoContent and rename the modal title to "Server Information" so the modal renders one heading instead of two stacked ones. - Swap the instructions Blockquote for ContentViewer (copyable, with the existing wrapping Code variant) so long instructions wrap and the user can copy the full payload. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The popup covers server identity AND client capabilities AND OAuth state — it's a snapshot of the live connection, not just the server. Rename to match what it actually shows. Also add a "Server Implementation" heading above the top section (name/version/protocol/transport) to balance the existing capability headings. - ServerInfoContent -> ConnectionInfoContent (directory, file, exports) - ServerInfoModal -> ConnectionInfoModal (directory, file, exports) - ServerCard button label "Server Info" -> "Connection Info"; prop onServerInfo -> onConnectionInfo - Modal title -> "Connection Info" - App.tsx / ServerListScreen / InspectorView wiring updated to match Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…place A long server prompt (e.g. an Everything-style instruction document) was pushing the OAuth section and the modal chrome off-screen — users had to scroll the whole modal to reach the close button. Wrap the ContentViewer in ScrollArea.Autosize with mah=280 so the instructions section scrolls internally and the rest of the modal stays put. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a `section` variant for Title that paints the heading in the existing `--inspector-surface-subtle` token (same surface token already used for modal call-out panels). Apply it to each section heading in ConnectionInfoContent (Server Implementation, Server Capabilities, Client Capabilities, Server Instructions, OAuth Details) via a SectionHeading subcomponent constant, so the sections read as distinct cards against the modal background instead of bleeding into one stream of text. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prettier flagged the import in CI. The line exceeded printWidth=80 and needed to wrap onto multiple lines. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a pre-commit instruction to run the auto-fixing `npm run format` ahead of `npm run validate`. `validate` runs `format:check` (non-fixing) which will fail CI on any unformatted file — running the auto-fixer first eliminates that class of failure entirely. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Point index.html's icon link at /src/theme/assets/MCP.svg so the same MCP logo asset already imported by ViewHeader is also the favicon. Delete the now-unused clients/web/public/favicon.svg. Vite resolves the source path during build and emits a hashed copy under dist/assets/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@claude review |
|
Claude finished @cliffhall's task in 2m 53s —— View job Claude PR Review
SummarySolid PR. The rename is mechanically clean and consistent across files, tests, stories, and props. The new A few small things worth a look before merging: Findings1.
|
Two related fixes so the user actually sees what went wrong when a
connect attempt fails (e.g. OAuth-backed server, bad URL, missing dev
auth token):
- App.tsx: add `connectInFlightRef` and skip clearing `activeServerId`
in the InspectorClient `disconnect` listener while a connect attempt
is in flight. The transport closes during a failing handshake and
dispatches `disconnect` before the catch block sets `errorMessage`;
without this guard InspectorView would map the card to plain
"disconnected" and the InlineError would never render.
- InlineError: opt-in `autoDismissMs` prop that runs a Mantine `slide-up`
Transition exit animation after the message has been visible for the
given duration. Visibility is derived from a "dismissed message"
state (rather than a mounted boolean + setMounted-in-effect), so the
timer re-arms whenever `error.message` changes without tripping the
react-hooks/set-state-in-effect rule. ServerCard passes
autoDismissMs={5000}.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iven removal Move the 5s auto-dismiss timer and visibility state out of InlineError and into ServerCard so the slide-up exit animation runs in both cases: - Timer-driven: after 5s the alert animates out (existing behavior). - Parent-driven: when `connection.error` is cleared mid-flight (e.g. a successful reconnect), the alert ALSO slides up instead of vanishing. To keep the message painted during the parent-driven exit, ServerCard tracks a `lastError` state that mirrors `connection.error` only on its truthy edges. The conditional setState during render is the supported React pattern for "remember the last seen value of a prop"; gated on message inequality so it can't loop. The `useRef` version of this pattern failed `react-hooks/refs` (no ref writes/reads during render). InlineError is now a pure presentational component: `mounted` prop controls a Mantine `slide-up` Transition. The internal `autoDismissMs` prop is gone — its single caller has moved to the controlled `mounted` pattern. Closes the "error vanishes the moment the parent removes it" half of #1364 (the timer half landed in 6d147e0). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ineError Move handshake-failure messaging out of the ServerCard body and onto a Mantine toast (`notifications.show`). The inline alert had a 5-second visibility contract entangled with InspectorView's status pipeline, and the active card was flipping through error → disconnected → error during a failing handshake; surfacing the error as a toast sidesteps that whole timing problem and keeps the card focused on the live status indicator. - App.tsx: toast on the connect-catch path; drop `errorMessage` state + `connectInFlightRef` (the latter was only there to keep the inline alert visible past the transport's close event). - InspectorView: drop the `errorMessage` prop and the `connectionStatus === "error" && errorMessage` mapping branch. - ServerCard: drop the InlineError rendering + the displayedError / errorVisible visibility state machine. - Delete the InlineError component — no other consumers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the Notifications portal from top-right to bottom-right so a connection-failure toast doesn't overlap the ViewHeader / tabs row. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1. Hoist `EMPTY_CLIENT_CAPABILITIES = Object.freeze({})` to module scope
in `useInspectorClient` so the fallback returns a stable reference
(avoids invalidating downstream `useMemo`/`useEffect` deps when no
client is attached).
4. Drop the `?? "stdio"` silent fallback on `connectionInfoTransport`
in the JSX — gate the modal render on `activeServer` too. The
"stdio" default still lives at the derivation site (config.type is
schema-optional and `ServerType` is required by the modal prop),
but the gate makes the invariant explicit.
6. Replace the raw `document.querySelector("button.mantine-CloseButton-root")`
in `ConnectionInfoModal.test.tsx` with `screen.getByRole("button",
{ name: /close/i })`. Add `aria-label="Close"` to the
CloseButton so the assertion (and screen readers) have a name to
match on.
7. Switch the OAuth scope splitter from `/\s+/` to the literal `" "`
per OAuth 2.1 §3.3.
Also clarify in comments:
2. Note that `connectionInfoOAuth` uses snapshot-at-open semantics on
purpose — token refreshes mid-session won't re-render the modal.
5. Note that we prefer the AS-advertised `authorization_endpoint`
over `authorizationUrl` because the latter is per-flight noise
(state, code_challenge) rather than a stable identifier.
Finding 3 (modal exit animation short-circuited by the `{initializeResult
&& …}` guard) left as intentional — we want the snap-shut on disconnect
so a stale info dialog can't keep rendering with no live client behind it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks for the review. Addressed in 05634b6a: 1. 2. 3. Modal close animation — Intentional. The 4. 5. Auth URL preference (metadata endpoint vs. constructed URL) — Kept the metadata endpoint first. Added a comment explaining: the constructed 6. Close-button selector — ✅ Replaced 7. Scope splitter ( Re: nits — confirmed |
|
@claude review |
|
Claude finished @cliffhall's task in 1m 20s —— View job Re-review of follow-up changes (commit 05634b6a)
VerdictLGTM. The follow-up commit cleanly addresses every actionable finding from the previous review, and the items left as "intentional" are well-justified with clear comments in the code itself (not just in the PR comment), so a future reader won't be tempted to "fix" them. Per-finding check1. 2. 3. Modal exit animation short-circuited — ✅ Acknowledged as intentional. The "snap-shut on disconnect rather than render a stale info dialog" reasoning is defensible; the commit message captures the rationale. No change requested. 4. 5. Auth URL preference (endpoint vs. constructed URL) — ✅ Documented. The comment at 6. Close-button selector — ✅ Resolved. 7. Scope splitter — ✅ Resolved. One small thing (non-blocking)On the memo at ApprovalAll previously raised findings either fixed or explicitly accepted with documentation. Ready to merge from a review perspective. |
The reviewer noted that "snapshot-at-open" was imprecise — the memo re-runs on any of its deps (connectionStatus, inspectorClient, activeServer), which includes settings edits that re-reference the active server. Reframe as "snapshot-at-last-derivation" and call out the natural triggers (server switch, reconnect, settings edit) so a future reader doesn't conclude the memo is open-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Picked up the non-blocking nit too — 260e0393 rewords the OAuth memo from "snapshot-at-open" to "snapshot-at-last-derivation" and enumerates the natural triggers (server switch, reconnect, settings edit) so a future reader doesn't conclude the memo is open-only. Thanks for the catch. |
Summary
ConnectionInfoModalwrapping the existing presentationalConnectionInfoContent, mirroringServerSettingsModal's layout (Modal+ MantineTitle+CloseButton). Title: Connection Info.ServerCardso it only renders whenconnection.status === "connected"; Settings continues to render in all states.getClientCapabilities()onInspectorClient(snapshotted from the initialize-time capability build), thread it throughInspectorClientProtocol,FakeInspectorClient, anduseInspectorClient, then render it in the modal alongside the liveinitializeResult, transport, and OAuth details.App.tsx, derive the active server's transport fromServerEntry.config.typeand OAuth details synchronously frominspectorClient.getOAuthState()(auth URL, access token) and persistedsettings.oauthScopes. Drop the modal-open flag on the InspectorClientdisconnectevent so a future reconnect doesn't auto-reopen the modal.ContentViewer(copyable + wrappingCodevariant), wrapped inScrollArea.Autosize mah={280}so a long instructions payload scrolls in place instead of pushing OAuth + modal chrome off-screen.Rename: Server Info → Connection Info
The popup covers server identity AND client capabilities AND OAuth state — it's a snapshot of the live connection, not just the server. To match what it actually shows:
ServerInfoContent→ConnectionInfoContent(directory, file, exports)ServerInfoModal→ConnectionInfoModal(directory, file, exports)onServerInfo→onConnectionInfoApp.tsx,ServerListScreen,InspectorViewConnection errors → toast (not InlineError on the ServerCard)
While testing OAuth-backed connections, the inline error on the ServerCard either flashed for a moment then vanished (the active card flips through
error → disconnected → errorduring a failing handshake) or required a 5s visibility contract that fought InspectorView's status pipeline. Replaced it with a Mantinenotifications.show({ title: \Failed to connect to "${name}"`, message, color: "red" })` call in App.tsx's connect-catch path:App.tsx: toast on the connect-catch path; droperrorMessagestate + the in-flight ref that was only there to keep the alert visible past the transport's close event.InspectorView: drop theerrorMessageprop and theconnectionStatus === "error" && errorMessagemapping branch.ServerCard: drop the InlineError rendering + visibility state machine; the card is back to a pure status indicator.InlineErrordeleted (no other consumers).main.tsx: anchor<Notifications>atbottom-rightso toasts don't overlap the ViewHeader/tabs row.Follow-up issue (#1378) tracks the related "missing auth token → silent 401" dev-UX problem: the only way the browser learns
MCP_INSPECTOR_API_TOKENtoday is via the URL the launcher prints, so a reload at the bare URL still 401s.Closes #1364
Screenshot
Test plan
npm run validate(format, lint, build, unit + integration with coverage gate)npm run test:storybook🤖 Generated with Claude Code