Opening the monitoring column on a failed connection reflows the server
grid to fewer/narrower columns, which can push the failed card below the
fold. Scroll it into view so the red-bordered card is visible without
hunting for it.
- ServerCard scrolls itself into view on the disconnected→errored
transition (mirrors the freshly-added highlight scroll), deferred
~320ms so the column open + grid reflow settle first — otherwise
scrollIntoView targets the card's pre-reflow position. Guarded by a ref
so it fires once per failure and won't fight a user who scrolls away.
Verified live: the failed `time` card scrolls fully into view after the
column opens. Full `npm run ci` passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
Flags the server whose last connection attempt failed with a red card
border, cleared when another server is connected or a new connection is
attempted — a persistent, at-a-glance companion to the failure toast and
the auto-opened monitoring column.
- App tracks `failedServerId`: set in onToggleConnection's non-auth
handshake catch, cleared at the start of any new connect attempt. It is
deliberately kept out of resetSessionScopedUiState, since the failure's
own `disconnect` event would otherwise clear it the same tick (that same
event clears `activeServer`, which is why the flag is tracked separately).
- New `errored` Card theme variant (red border via a new
`--inspector-error-border` token), mirroring the `highlighted` green
border. Precedence: disabled (dimmed) > errored > highlighted.
- Plumbed `erroredServerId` through InspectorView → ServerListScreen →
ServerCard (`errored` prop).
Full `npm run ci` passes (validate + coverage + smoke + storybook).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
* fix(web): remove mantine-border-animate, use green highlight border (#1535)
The @gfazioli/mantine-border-animate dependency emitted a persistent
React "forwardRef render functions accept exactly two parameters"
warning on every page load. Remove the dependency entirely and replace
the freshly-added card's animated border with a green border.
- Drop @gfazioli/mantine-border-animate from clients/web (package + lock)
and remove its styles.css import from main.tsx.
- Add a `highlighted` variant to the Card theme that draws a 2px green
border, driven by a new --inspector-highlight-border token in App.css.
- ServerCard now selects the variant (disabled > highlighted) instead of
wrapping in BorderAnimate; the BorderAnimate flex-wrapper `w="100%"`
workaround is no longer needed.
- Update ServerCard/ServerListScreen tests to assert the highlighted
variant instead of the removed BorderAnimate DOM.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web): drop redundant Card props on ServerCard (#1535)
ThemeCard.defaultProps already sets withBorder and padding="lg", so the
explicit props on ServerCard's <Card> were redundant. Addresses review
feedback on PR #1536.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(web): note highlighted Card variant relies on inherited withBorder (#1535)
Addresses a maintainability flag from the PR #1536 re-review: the
highlighted variant only sets borderColor/borderWidth and depends on the
withBorder:true default, so make that coupling explicit in a comment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web,core): import client configs and registry server.json (#1348)
Wire up the two server-import affordances that were previously todoNoop:
- Import config: source picker (Claude Desktop / Cursor / Cline / VS Code)
read on the backend via a new GET /api/import-source, plus a file-upload
fallback. Per-server review with import/skip on new servers and
overwrite/skip/rename on id conflicts, then a per-server outcome summary.
- Import server.json: parse the MCP registry single-server format
(npm/pypi/oci packages + remotes, env vars, package/runtime args, URL
template vars) into a runnable config, with a file picker and debounced
live validation; Add Server is disabled until validation passes.
Core gains an isomorphic import strategy layer (core/mcp/import) with a
strategy registry, client-config + server.json parsers, a strategy-agnostic
merge, and a dependency-injected source resolver. useServers exposes
importSource(). Modal headers are bold and the redundant server.json section
header is folded into the modal title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): collapse the server.json textarea into a File Contents disclosure
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): auto-collapse File Contents disclosure after content loads
Once content is pasted or a file is loaded, collapse the File Contents
disclosure after ~1s with Mantine's built-in accordion animation, signaling
the input was accepted and surfacing the validation/env-var sections. The
disclosure becomes controlled by the modal; clearing the textarea re-opens it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): flash File Contents hover highlight before auto-collapse
Before the File Contents disclosure auto-collapses, paint it with its hover
background for 250ms, then run the collapse over a 325ms animation, so the
collapse reads as intentional.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): rename "Import server.json" to "Import from registry config"
Rename the Add Servers menu entry and the modal header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): rename "Import config" to "Import client config"
Rename the Add Servers menu entry and the modal header.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web,core): move import orchestration into core helpers + web hooks
Make the import logic reusable by CLI/TUI and keep the modals display-only:
- core/mcp/import: add parseClientConfig (mcpServers→servers fallback),
resolveServerId, selectServerJsonOption, and buildServerConfigForSelection,
so the full import (parse → select → build → merge) is callable from any
environment.
- core/storage: extract the pure validateStoreId into a Node-free store-id
module (store-io re-exports it) so the isomorphic import layer can validate
ids without pulling Node deps into the browser bundle.
- clients/web/hooks: useServerJsonImport and useImportClientConfig own all the
wizard/parse/debounce/apply state; ServerImportConfigModal and
ServerImportJsonModal are now thin display shells over the dumb panel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): rename "Import client config" to "Import from client config"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): remove the Cancel button from the registry import modal
The modal header X (and Escape) still dismiss it; drop the redundant Cancel
button (and the panel onCancel prop).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): hide validation results + name override until content is present
In the registry import modal, the Validation Results section and the Server
Name override field stay hidden until something has been pasted/loaded into the
textarea.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): make Validation Results a collapsible disclosure
Wrap the registry import modal's validation results in a Mantine accordion
disclosure (default open), matching the File Contents section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): Server Name disclosure with read-only derived name + override
Make Server Name a collapsible disclosure containing a read-only "From
configuration" field (the id derived from the server.json name) and the
editable Override field.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): scroll to and highlight a freshly-added server
After a manual add/clone or an import (client config / registry), the new
server's card scrolls into view and draws an animated border (Mantine Border
Animate); clicking anywhere on the card dismisses it.
- App tracks highlightedServerId; addServerHighlighted sets it (edits and
conflict-overwrites don't). Threaded through InspectorView -> ServerListScreen
-> ServerCard.
- ServerCard scrolls into view on highlight and wraps in <BorderAnimate>;
onClick clears the highlight.
- Pin @gfazioli/mantine-border-animate@2.0.3 (Mantine 8 compatible; 2.0.4+
requires Mantine 9).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): highlighted card fills its cell and its controls work on first click
- The BorderAnimate wrapper is display:flex, so the card was shrinking to
content width while the border spanned the cell. Stretch the card with
w=100% (and the wrapper).
- Always render BorderAnimate and toggle show/animate instead of conditionally
wrapping: swapping the element type on clear remounted the card, swallowing
the click that triggered it (e.g. the connect toggle fired clear but not
connect, needing a second click). Verified: one click now connects + clears.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web,core): address review — OCI env, untrack fixture, rename validation
- Untrack the stray inspector-v1-multi.server.json (accidentally committed) and
gitignore root *.server.json fixtures.
- core/mcp/import/serverJson: forward declared env vars into OCI containers via
'-e KEY' docker args (they previously only set the docker CLI process env);
add a test. Clarify buildServerConfig (destructure env out instead of
spread-then-delete) and document value-vs-default env handling.
- useImportClientConfig: validate 'rename' targets inline (invalid charset /
collision with existing, additions, or other renames) and gate the Import
button on canImport; surface the error on the field.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): highlight all servers from a batch import, not just the last
Track highlighted servers as a set of ids instead of one. addServerHighlighted
accumulates each id (a multi-server client-config import highlights them all);
opening an add/import modal starts a fresh batch. Clicking a card clears only
that card's highlight; only the first highlighted card scrolls into view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): catalog vs read-only session launch model + apply --header (#1481, #1483)
Per specification/v2_catalog_launch_config.md, `mcp-inspector --web` now selects
the backend's server-list source and a session-wide writability, instead of
seeding an inert `initialMcpConfig` the UI never read:
- `--catalog <path>` (and `MCP_CATALOG_PATH`) → active WRITABLE catalog
(seed/CRUD/watch), like the default catalog.
- `--config <path>` → READ-ONLY session file: served in the UI but never
written, seeded, or plaintext-secret-migrated — safe to point at a foreign
config (e.g. claude_desktop_config.json).
- ad-hoc `--server-url`/command (+ `--header`) → one server held IN MEMORY
(no file written); `--header` is lifted onto the entry so the seeded
connection actually uses it (closes the #1483 warn-only no-op).
- nothing → default catalog (writable), unchanged.
Backend (`createRemoteApp`): new `writable` + `initialServers` options. GET
/api/servers branches three ways (in-memory / read-only file with no
seed+no migrate / writable catalog); the four mutating routes return 403 when
read-only; `/api/config` carries `writable`; the file watcher is skipped for
in-memory sessions. `--catalog`/`--config` are mutually exclusive and neither
combines with an ad-hoc target or `--header`; `--header` requires an ad-hoc
HTTP/SSE server (clear errors otherwise — no silent no-op).
UI: `useServerListWritable` reads `writable` from /api/config; the Servers
screen hides add/edit/clone/remove/reorder/settings and shows a read-only
banner when false. All new props default to writable=true (back-compatible).
Docs: spec gaps G2/G3/G4 updated; launcher README documents the flags.
Verified end-to-end against the built launcher: --config (writable:false,
file byte-stable, mutations 403), ad-hoc --header (in-memory, header lifted),
and --catalog (writable:true, POST persists).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web): address review — Mantine <Code> in read-only banner, clarify watcher comment
- Replace raw <code> with Mantine <Code> in the read-only-session Alert
(repo convention: prefer Mantine primitives over raw HTML).
- Add a comment near `watchable` clarifying that a read-only `--config` file
is still watched on purpose (read-only blocks UI writes, not external
observation; `writable` does not gate watching).
Deferred (per reviewer): consolidating useSandboxUrl + useServerListWritable
into one useConfig hook to avoid the duplicate /api/config fetch — optional
follow-up. The redundant ad-hoc header type-guard is kept as defensive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): reorderable ServerCards — drag-and-drop + keyboard (#1369)
Let users control the order of servers on the Servers screen instead of
hand-editing mcp.json. Each ServerCard gets a grip handle (in the header,
left of the name) that drives an @dnd-kit sortable; pointer and keyboard
sensors both work, with live-region announcements for screen readers.
- Backend: PUT /api/servers/order rewrites mcp.json in the supplied order
inside the write lock, reusing the atomic-write + watcher-notify path.
Registered before /:id so "order" isn't captured as a server id. Rejects
with 409 when the id set no longer matches disk and 400 on duplicate /
malformed bodies, so a reorder racing an external edit can't drop or
duplicate an entry.
- Hook: useServers.reorderServers(orderedIds) — optimistic local reorder,
PUT, refresh-to-disk-truth on failure.
- UI: SortableServerCard wrapper owns the dnd-kit concerns; ServerListScreen
wraps the grid in DndContext + SortableContext (rect strategy) with
pointer + keyboard sensors and announcements. Reorder math and a11y copy
extracted to reorderIds.ts / serverReorder.ts for direct unit testing.
Renders plain cards (no grip) when no onReorder is wired.
- Wired onServerReorder through InspectorView -> App.tsx.
Tests: pure-helper units, hook (persist / optimistic / 409 revert), 9
backend integration tests (incl. id-set-mismatch rejection and the
route-registration guard), ServerCard/ServerListScreen units, and a
real-browser Storybook keyboard-reorder play (the dnd-kit keyboard sensor
needs layout rects that happy-dom doesn't provide).
Adds @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(web): notify on reorder failure; skip reorder glue when not reorderable
Address PR #1447 review:
- App.tsx: catch reorderServers rejection (409 from a racing external edit,
or a network error) and show a red toast, matching the error-handling
pattern used by every other mutation — previously the rejection was
discarded with `void` and the drag silently bounced back.
- ServerListScreen: only build handleDragEnd / announcements when
`reorderable`, so the four-closure announcements object isn't allocated
every render when no onReorder is wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(web): show negotiated MCP protocol version on ServerCard (#1324)
Add optional protocolVersion to ConnectionState and render it as a
dimmed "MCP <version>" label at the end of the transport-badge/
description row on ServerCard, shown only when connected. The value
is plumbed through useInspectorClient as part of #1324.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(core,web): surface negotiated protocolVersion end-to-end (#1324)
Plumb the MCP protocol version negotiated during initialize from the
InspectorClient through to the ServerCard so the active connected card
displays "MCP <version>".
The SDK Client doesn't expose protocolVersion; it stamps it onto the
transport via setProtocolVersion. MessageTrackingTransport now captures
it (and forwards to the base transport when present), so it works for
stdio too — whose base transport has no setProtocolVersion.
- core: MessageTrackingTransport captures + exposes protocolVersion;
InspectorClient stores it, exposes getProtocolVersion(), dispatches a
protocolVersionChange event, and clears it on disconnect.
- core: useInspectorClient subscribes and returns protocolVersion.
- web: App reads it from the hook (drops the hard-coded "2025-06-18")
and InspectorView splices it onto the active server's connection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web): decouple initializeResult from protocolVersion (#1324 review)
Gate the initializeResult memo on serverInfo only, not protocolVersion.
initializeResult also drives the connected ViewHeader and Connection Info
modal, so requiring the version risked hiding the whole connected surface
in the edge case where it's absent. protocolVersion now flows through as
the optional field it already is everywhere downstream ('' reads as
unknown; the ServerCard label and modal value both tolerate it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(web): drop redundant connected-check in InspectorView splice (#1324 review)
initializeResult is derived from connectionStatus in App's memo, so a
truthy initializeResult.protocolVersion already implies a live
connection — the extra connectionStatus === 'connected' guard in the
splice was redundant. ServerCard keeps its own status guard (dumb-
component robustness). No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(server-info): wire Server Info modal to the connection handshake
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>
* fix(server-info): dedupe modal header and render instructions in ContentViewer
- 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>
* refactor(server-info): rename ServerInfo -> ConnectionInfo
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>
* fix(connection-info): cap instructions block height so it scrolls in 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>
* feat(connection-info): add subtle background to section headings
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>
* fix(stories): wrap long import line in ConnectionInfoModal.stories.tsx
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>
* docs(agents): require `npm run format` before committing
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>
* chore(favicon): use theme/assets/MCP.svg as the favicon source of truth
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>
* fix(server-card): surface failed-handshake errors with a 5s auto-dismiss
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>
* refactor(server-card): own InlineError visibility + animate parent-driven 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 6d147e0f).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(server-card): surface connection errors as a toast, drop InlineError
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>
* chore(notifications): anchor toasts to the bottom right
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>
* review: address PR feedback on the Connection Info modal
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>
* docs(connection-info): tighten the OAuth memo comment per re-review
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>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(coverage): bootstrap vitest unit infra and add tests at 90%+ per file (#1245)
Establishes the unit-test runtime AGENTS.md mandates: a vitest "unit"
project alongside the existing storybook project, RTL + jest-dom +
happy-dom, a Mantine-aware render helper, and a 90%-line per-file
coverage gate enforced by CI. Adds 66 test files covering all 62
components under elements/groups/screens/views, plus core/mcp and
clients/web/src/utils. Total: 577 tests, 98.7% line coverage globally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(coverage): address PR #1269 review feedback
- AGENTS.md: correct coverage gate description (lines ≥ 90, statements ≥ 85,
functions ≥ 80, branches ≥ 50) with rationale for the relaxed branch
threshold
- InspectorView.test.tsx: document what the Math.random() mock values
(0.1 / 0.99) control vs. STUB_SUCCESS_RATE in InspectorView.tsx
- vite.config.ts / tsconfig.test.json: drop unused `globals: true` and the
`vitest/globals` type now that every test file imports describe/it/expect/vi
explicitly; manual `afterEach(cleanup)` in setup.ts remains required because
testing-library auto-cleanup relies on globalThis.afterEach
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>