| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
04b5e70a53 |
fix(a11y): make the form error color meet WCAG AA in both schemes
Mantine's `--mantine-color-error` defaults to `red-6` in light and `red-8` in dark. Both fail the 4.5:1 AA threshold that applies at the 12px size it renders input error text: 4.48:1 on white, and 1.93:1 on our charcoal body. Override to `red-7` (5.72:1) and `red-4` (6.44:1) — the latter matching the one-to-two-step lift App.css already applies to the semantic status/log reds for dark. Surfaced by the JsonObjectInput stories, which are the first in the repo to render an input error state and so the first to put this in front of the Storybook a11y gate. It is app-wide, not specific to that component — every form error in the Inspector was affected. It has to go through `cssVariablesResolver`: MantineProvider injects these variables into a `<style>` block appended after the stylesheet imports, so a `:root` rule in App.css loses to it on source order at equal specificity. That resolver was duplicated in main.tsx and .storybook/preview.tsx and missing from renderWithMantine, so the three could drift on any token they set. Extract one `theme/cssVariables.ts` and use it at all three sites. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177mPHAdECD18nLLCTwR5rh Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
de7795ca54 |
fix(auth): address Copilot review round 2 on the secret-store PR
Seven findings, all taken. - **The descriptor went stale after the upgrading write.** Round 1 made it read the file instead of the policy, but it was still computed once and cached beside the selection — so the first `set` under a newly-set passphrase encrypted the file while `/api/config` kept serving "still unencrypted" for the rest of the session, describing bytes this very process had just changed. The *selection* stays cached (the banner, the payload, and the store doing the writing must agree on which store it is); the file descriptor is re-derived per call, which costs one read of a small file per config fetch. - **The mount check asked the wrong question.** Comparing a directory's `st_dev` against its parent's detects a mount *at* that path, but a volume mounted at `/home/node` leaves `/home/node/.mcp-inspector` an ordinary subdirectory sharing its parent's device — so a durable container setup was judged unmounted and demoted to session-only `memory`. It now reads `/proc/self/mountinfo` and asks whether the directory is *under* any mount other than `/`, which also catches the same-device bind mount no `st_dev` test can see. Where mountinfo is unreadable (macOS, Windows, a `/proc`-less container) it falls back to the device comparison, now against `/` rather than the parent. - **A decrypted payload was cast, not checked.** GCM proves the bytes are authentic, not that they parse to the right shape. An array passes `typeof === "object"`, takes the named assignment, and then `JSON.stringify` drops every named property of an array — so `set` would resolve having written a file without the secret it was handed. Silent loss on the one operation whose entire contract is that it does not lose things. Both branches now validate a non-array object of string values, and a shape refusal is deliberately not rethrown as a key mismatch, which would send the user to fix a working passphrase. - **The read-modify-write cycle was only serialized in-process.** A durable file is shared state and a second Inspector on the same box (a CLI run beside a web session) could read the same map and overwrite the first one's save, both reporting success. Mutations now take a `mkdir`-based lock. A lock left by a killed process is stolen after 10s — one `docker kill` must not brick secret writes permanently, which would be a worse failure than the race. If the lock cannot be created at all, the write proceeds unlocked rather than denying a real user a real secret to prevent a race with a process that may not exist. Reads do not lock: `writeStoreFile` is atomic, so a reader sees the old file or the new one, never a torn one. - **The permission repair was fire-and-forget while the docs promised 0600.** A file owned by another user, or on a read-only mount, stayed exposed while the footer described it as protected. It now verifies the resulting mode and warns, naming the actual mode, when it could not be tightened. - **`Group`'s theme carried a domain-named `secretStorageFooter` variant**, which AGENTS.md reserves for app-wide primitive styling. Renamed to the structural `stickyModalFooter`; the secret-storage meaning stays in the element that owns it. - The PR description still claimed the salt made a short passphrase safe, contradicting the corrected comment and README. Updated. Tests: both mount layouts that motivated the change (mount-at and mount-above) plus the sibling-prefix, space-escaped, first-run, and no-mountinfo cases; the array and non-string refusals on both the plaintext and the authentic-ciphertext paths (the latter through a hand-built fixture, since the store's own writer cannot produce that state); lock exclusion, stale-lock stealing, the un-lockable fallback, and two independent store instances racing; the permission report in all three states; and the descriptor no longer going stale within one process. Full `npm run ci` green — 5,688 tests under the coverage gate. Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
bc408f5869 |
feat(auth): file-backed and in-memory SecretStore for hosts with no keychain
Closes #1950 Before this, a box with no reachable OS keychain — the published container (no D-Bus session, #1848), Android/Termux (no platform binary, #1905), a minimal Linux install without libsecret — could not persist an OAuth client secret or a stdio `env:` value at all. `KeyringSecretStore` degraded into a store that silently holds nothing: `get` returned null, `delete` no-opped, and `set` hard-failed with a 503. That is the correct failure behavior, but it left the documented, supported way to run the Inspector with no secret persistence whatsoever. Adds the two missing implementations and, more importantly, the policy that picks one and the surfaces that say which was picked. - `FileSecretStore` — one `0600` JSON document at `~/.mcp-inspector/secrets.json`, encrypted with AES-256-GCM whenever `MCP_INSPECTOR_SECRET_KEY` is set (scrypt, per-file random salt). The whole map is encrypted as a unit rather than value-by-value so the account names — `${serverId}:${field}` — are hidden too; a per-value scheme would leave a readable index of which servers you hold a client secret for. A file that can no longer be decrypted reads as empty (matching the interface's read tolerance) but *refuses to be written*, because replacing a file of still-valid secrets to satisfy an additive request destroys data. - `secret-store-selection.ts` — the policy. `MCP_INSPECTOR_SECRET_STORE=keyring|file|memory` wins outright; otherwise probe the keychain and, if unreachable, fall back loudly. The fallback is `memory` in a container whose secrets directory is not on a mount, and `file` otherwise — so mounting the volume the README already recommends for the catalog flips the same run to durable storage with no configuration, and an unmounted container gets the honest answer rather than a file `--rm` will discard. Cached per process so the banner, `/api/config`, and the store doing the writing cannot disagree. - Automatic, but never silent. The downgrade prints at startup, rides `GET /api/config`, and is named in a permanent 32px footer at the bottom of both dialogs that accept a secret. A startup banner is seen once by whoever started the process; a toast is seen once; a dismissible banner is the thing a user dismisses before doing the work it describes. The plaintext and memory cases render their caveat inline in a warning tone — a warning you must hover to discover is not one. - `SecretStoreUnavailableError` is now the base every store's `set` throws, so the routes translate one condition into a 503 instead of matching `KeychainUnavailableError` by name. Docs: the container section of the root README gains a table of which store you get and how to make secrets survive, plus the `MCP_INSPECTOR_SECRET_KEY` upgrade/mismatch rules. Tests: the existing interface contract block is extracted to `secretStoreContract.ts` and run against all three implementations, so none can drift on the availability contract. `FileSecretStore` is tested against a real temp filesystem — mode, cross-instance round-trip, that the ciphertext does not contain the plaintext, the read-modify-write race, and every refusal path. Full `npm run ci` passes, per-file gate included. Signed-off-by: cliffhall <cliff@futurescale.com> |
||
|
|
4defa32ac6 |
chore: close a batch of quality-gate & tidy gaps (#1785 #1787 #1789 #1790 #1767) (#1792)
* chore: close a batch of quality-gate & tidy gaps (#1785 #1787 #1789 #1790 #1767) Bundles five small, related tidy/gate items into one PR: - #1785: delete the dead src/types/navigation.ts InspectorTab (0 importers; duplicated/drifted from utils/inspectorTabs InspectorTabId). src/types/ now holds only ambient .d.ts stubs. - #1787: add src/hooks/** to the web coverage `include` (it was silently outside the ≥90 gate) and add tests for the two untested hooks, useServerJsonImport and useImportClientConfig, bringing every hook to ≥90 on lines/statements/functions/branches. - #1789: widen clients/{cli,tui,launcher} format/format:check to their top-level configs; add root format:shared / format:check:shared covering test-servers/src/**, vitest.shared.mts, and the repo-root eslint.config.js. - #1790: pin prettier to an exact version (3.8.4, dropping the ^3.8.1 caret) in all five package.json files so the format gate's verdict can't shift with an in-range patch bump; lockfiles synced. - #1767: its root-file format+lint gap is closed here (format:shared + a new lint:shared via a second files block in the root eslint.config.js; web/server/ was already gated in #1788). Its larger cli/tui __tests__ typecheck piece (76 type errors) is split out to #1791. Prettier-normalized the newly-covered files (pure formatting). No runtime behavior change. `npm run ci` green. Closes #1785 Closes #1787 Closes #1789 Closes #1790 Closes #1767 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * chore: address PR #1792 review findings - #1789 gap: widen clients/web format to include .storybook and clients/ launcher format to include scripts/; reformat the 3 .storybook files (were non-conforming under the pinned prettier 3.8.4). Audited all 885 tracked source files — now fully prettier-clean. - Export VALIDATE_DEBOUNCE_MS / COLLAPSE_DELAY_MS / HIGHLIGHT_DURATION_MS from useServerJsonImport and use them in the test instead of hardcoded 300/1000/250 (so a tuned constant can't silently desync the test). - Assert the symmetric renameErrors["alpha"] alongside ["beta"] so a one-directional collision-check regression fails. - Comment the blank-rename test as exercising a UI-unreachable defensive branch (canImport gates submit). - Un-stale the two specification/v2_ux_interfaces_plan.md references to the now-removed navigation.ts InspectorTab. npm run ci green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs: sync AGENTS.md format-surface description with the widened globs (#1792 review r2) 1ce3b64 added .storybook to clients/web's format glob and scripts/ to clients/launcher's, but AGENTS.md:222 still described the pre-fix scope. Bring it in line and note the non-recursive top-level glob is why nested dirs are named explicitly. Docs only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * chore: handle PR #1792 round-3 follow-ups in-place (format-coverage guard, uniform globs, theme gate) Rather than defer these to tickets, fold them into this PR: - Obs 2: widen every client + root format glob to a uniform *.{ts,tsx,mts,cts,js,jsx,mjs,cjs} so a future .mts/.cjs/.tsx top-level file can't silently slip the gate. - Obs 1: add scripts/verify-format-coverage.mjs (wired into `validate`) — it parses the format:check globs out of every package.json, enumerates all tracked source files, and fails listing any not covered by a glob. The durable, standing version of #1789's one-shot audit; negative-tested. - Obs 3: bring src/theme/** under the coverage gate (21/23 files were already ≥90 transitively; add Paper.test.tsx for the one laggard, Paper.ts). src/App.tsx (~4.5k lines, ~42% branches) is converted from a silent omission into an explicit, documented exclusion (comment on the coverage `include` + AGENTS.md) — gating it is a dedicated effort, not a whitelist tweak. npm run ci green (the new guard reports 885/885 tracked source files gated). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * chore: address PR #1792 round-4 findings (guard hardening, docs, exclusion list) - Harden verify-format-coverage: only harvest format:check globs from scripts transitively reachable from `validate` (via npm run refs), so an unwired/renamed format:check no longer counts as coverage. Negative-tested — renaming clients/web format:check to an unreferenced name now fails the guard (627 files flagged) instead of passing. - Name src/main.tsx and src/index.ts (browser + bin bootstraps) alongside src/App.tsx in the coverage-include comment and AGENTS.md, so the documented exclusion list is complete rather than claiming App.tsx is the only one. - Doc sync: document verify:format-coverage (AGENTS.md + README script table + scripts/ layout line), fix the `validate` ordering (verify:format- coverage now runs before validate:core) in AGENTS.md and README, and update the quoted globs to the uniform *.{ts,tsx,mts,cts,js,jsx,mjs,cjs} / test-servers *.{ts,tsx,mts,cts}. npm run ci green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * chore: address PR #1792 round-5 findings (airtight guard + doc/config sync) - verify-format-coverage: assert the root `validate` chain invokes each client's `validate` (cd <dir> && npm run validate), closing the last reachability edge — a client dropped from the root chain now fails the guard even though its own globs still exist. Negative-tested. - eslint.config.js: widen the shared `files` block to test-servers/src/**/*.{ts,tsx,mts,cts} to match the widened lint:shared glob, so a future .mts/.cts there is lint-gated, not just prettier-gated. - AGENTS.md: scope the "uniform extension set" claim to client globs (core stays {ts,tsx}, shared {ts,tsx,mts,cts}), with verify:format-coverage as the backstop — removing the self-contradiction with the line below. npm run ci green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
092165476e |
web: pin Server/Client Settings modal header while body scrolls (#1753)
* web: pin Server/Client Settings modal header while body scrolls (#1698) Expanding enough accordion sections grew the Server Settings modal past the viewport, so the whole modal scrolled and carried the header (title + close) out of view. The header was a custom Group inside the modal body, and the modal used no `scrollAreaComponent`, so overflow was not confined to the body. Switch both the Server Settings and Client Settings modals from the default `<Modal>` (which always wraps children in `Modal.Body`) to the compound `Modal.Root`/`Modal.Content`/`Modal.Header`/`Modal.Body` API, moving the toggle/title/close row into `Modal.Header` (sticky by design) and setting `scrollAreaComponent={ScrollArea.Autosize}` so only the body scrolls. `Modal.Root` keeps the close-on-escape / close-on-click-outside / focus-trap / scroll-lock / portal defaults; `transitionProps` restores the fade-down animation the `<Modal>` wrapper otherwise supplies. Client Settings does not yet have enough content to scroll but is kept consistent per the issue. Closes #1698 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs(pr-screenshots): Server Settings modal sticky-header proof (#1698) Two frames from the ServerSettingsModal story (all sections expanded, short viewport): header pinned at the top of the scroll and again scrolled down, showing it stays put while only the body scrolls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * web: move Modal.Root fade-down transition into the theme (#1698 review) Addresses the review note on PR #1753: the `transitionProps={{ transition: "fade-down", duration: 200 }}` literal was duplicated in both settings modals to restore the animation `<Modal>` defaults to but `Modal.Root` doesn't inherit. Move it to a `ThemeModalRoot` (`Modal.Root.extend`) registered under the `ModalRoot` theme key, mirroring the existing `ThemeScrollAreaAutosize` precedent. Both call sites drop the literal; any future compound `Modal.Root` now animates consistently with every plain `<Modal>` from one source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
8d33e76f97 |
web: server-screen, footer, monitoring-sidebar, and responsive-header polish (#1683)
Some checks failed
Run install, format, lint, build, and test on every push / build (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish (push) Has been cancelled
Run install, format, lint, build, and test on every push / publish-github-container-registry (push) Has been cancelled
Closes #1682 Server-screen, footer, monitoring-sidebar, and responsive-header polish: connected-card highlight/scroll + Failed status, footer row + MCP-docs tooltip, Servers box, card-surface refresh (gray-0 entry cards + white code), monitoring-sidebar layout, a 1280px responsive floor honored by the header (always-icon Disconnect, always-SegmentedControl tabs, 1500px status text, always-available sidebar), a horizontally-scrollable long resource URI in the compact Protocol entry, and copy buttons beside resource URIs / network URLs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
3d75ef5f8e |
web: Accessibility — accessible palette + WCAG AA contrast, labels, names, roles (#1679)
* web: accessible light/dark color palette + dark-mode legibility fixes (#1678) Ports the design system's accessible palette. Colors are defined once as Mantine color tuples in theme.ts (shade 0 lightest → 9 darkest); every `--inspector-*` token in App.css references `--mantine-color-*`, so improving the tuples cascades through the whole UI. - theme.ts: full accessible tuple set (inspector/gray/dark/green/red/yellow/blue/ teal/violet), WCAG-AA at the primary shades. Tuples are the one place literal hex belongs — the token source. Primary stays inspector-7 (light) / -8 (dark). - theme/ActionIcon.ts: default `color: "gray"` so chrome icons (settings, theme toggle, sidebar, pin, sort, replay) render neutral grey instead of inheriting the blue primary. Icons that set their own color/variant still override. - App.css: fix light surfaces (`surface-body` gray-0, `surface-card` white — the body token pointed at near-black dark-9); add direction/method badge tokens; lift the semantic status/log/task/history colors 1–2 steps in the dark scheme (the shared `*-6` shade sat too dark on charcoal). Light `:root` keeps `*-6`. - Badges: MessageDirectionBadge + new MethodBadge element driven by `--inspector-badge-*` tokens (deep fill + light text in dark, tinted in light) via Mantine `bg`/`c` style props with `autoContrast={false}`; swaps `<Badge color="dark">{method}</Badge>` in ProtocolEntry/NetworkEntry (their Badge import stays — both still use it for status/category chips). Stories + test added for MethodBadge (≥90 coverage gate). Domain badge semantics live in the element components, not theme files. No raw hex outside the tuples; no inline styles. Supersedes the reverted #1677. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw * web: WCAG AA accessibility pass — contrast, labels, names, roles (#1678) Drives the axe-core ruleset (the engine behind Storybook's a11y addon) to zero violations across all stories in light and dark mode. Contrast (805 → 0 hits): - Darken Mantine `*-light-color` + `dimmed` in light mode (and lift dark `dimmed`) via `!important` App.css overrides — Mantine injects these at runtime at matching specificity, so specificity alone loses the tie. - `filledBadgeColor` pins amber (orange/yellow) filled badges to shade 5 so `autoContrast` (JS-computed from the theme shade, not CSS vars) picks black. - `accessibleTextColor` routes colored log/filter text through the scheme-aware `-light-color` token so it clears AA on light and dark surfaces. - `status` Badge variant: amber statuses use black-on-amber (orange text can't reach AA on light at any Mantine shade). - Resource URIs use `-light-color` (blue-4 was 4.38:1 in dark). Names / roles: - `aria-dialog-name`: custom-header modals use `Modal.Title` (wires the dialog's `aria-labelledby`) instead of a bare `Title` + a root `aria-label` (which also tripped `aria-prohibited-attr`). - `button-name`: a `Modal` theme default labels every built-in close button. - `label`: `aria-label` on the previously-unlabelled Textarea/Select inputs. - `scrollable-region-focusable`: keyboard-focusable content Code/ScrollArea viewports; long OAuth values wrap instead of scrolling. - `heading-order`: correct heading levels (keeping visual size via `size`) so outlines don't skip a level under the h2 modal/section titles. Scrollbars: `type="scroll"` ScrollArea theme default — scrollbars auto-hide and show only while scrolling (a deliberate product choice over the always-visible a11y default). `region` is disabled in the Storybook a11y config: isolated component/screen stories render outside the app's `AppShell` landmarks, so it's a page-level concern the components can't own. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: let the resource template form fill the main-area width (#1678) Drop the maw=40% cap on the template-form pane so the URI preview and variable inputs use the full width beside the sidebar, matching the resource preview pane. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: left-align + reverse the input-panel action rows (#1678) Move the footer action buttons to the left (justify flex-start) and put the primary action first on the Tool / Prompt / Resource-preview / Resource-template panels — Subscribe, Refresh, View Source on the preview panel; Execute before Cancel on the tool panel. Shortens the pointer travel from the sidebar controls / the form fields above to the actions, which matters most when the monitoring sidebar is hidden and the form is wide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: match the dark-mode body background to the header tab bar (#1678) Set the dark `--inspector-surface-body` to `dark-8` (the Mantine SegmentedControl fill used by the header tab bar) instead of `dark-9`, mirroring light mode where the body and the tab bar share `gray-1`. Cards (`gray-9`) still sit a step above the body, and the contrast audit stays at zero AA violations on the slightly lighter background. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: enforce the a11y gate + fix modal-overlay heading-order (review #1679) Address the code review: - Flip the Storybook a11y addon from test: 'todo' to 'error' so axe violations fail the play-function tests in CI — the zero-violation state is now gated, not point-in-time. - Enforcing it surfaced a real heading-order skip: the Tools screen's h4 'Tools'/'Results' sit under an h2 Modal.Title when a sampling/ elicitation request modal opens over them. Lift both to h3 (size h4 preserves the look) so h2→h3 is valid in any DOM order. - Comment the intentional color+c pairing on the Remove/Disconnect danger buttons. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: brighter warning alerts + pin the elicitation form's actions (#1678) - Add a 'warning' Alert variant: a bright light-yellow fill (yellow-1) with crisp near-black text and a bright yellow-5 left accent, instead of the muddy brown a tinted yellow alert produced once yellow-light-color was darkened to yellow-9 for contrast. Dark mode keeps the light-on-dark-tint treatment. Applied to all five yellow warning/notice alerts. - Elicitation form: scroll only the form fields (capped ScrollArea), keeping the quoted message, warning, and Cancel/Decline/Submit buttons in view. The modal has no other close affordance, so the actions must never scroll out of reach. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: robustly pin the elicitation form's actions (#1678) The previous approach capped only the fields' ScrollArea with a fixed rem reserve for the pinned chrome, which underestimated it on some viewports — the whole modal then overflowed and the buttons clipped half-off-screen. Instead cap the whole panel below the modal height and lay it out as a flex column: the message and the warning + buttons are pinned (flex: 0 0 auto) and only the fields (flex: 1) shrink and scroll. Verified the buttons stay within the panel and the modal fits the viewport down to very short heights. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: fix elicitation form scrolling — fields scroll, actions pinned (#1678) The prior fix capped the panel with a bare max-height, but a flex:1 ScrollArea's viewport won't bound against a max-height (not fixed-height) parent, so a tall form's fields overflowed unscrollably (clipped) rather than scrolling. Adopt ToolDetailPanel's pattern instead: the modal gives its body a bounded flex column, the panel fills it, and the fields use a flex: "0 1 auto" ScrollArea — which sizes to content when the form is short (modal stays compact, nothing scrolls) and shrinks + scrolls once the panel is capped at the modal height, keeping the message, warning, and Cancel/Decline/Submit pinned in view. Add a ManyFields story (with a bounded-height decorator mirroring the modal) to cover the scroll case. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: fix elicitation scrolling in the real modal (cap fields, not flex) The flex-column approach (panel flex:1 in a flex-column modal body, fields flex:"0 1 auto") worked in a fixed-height Storybook decorator but NOT in the real modal: the modal body is bounded by max-height, not an explicit height, so the fields' ScrollArea viewport still couldn't bound — a tall form's fields overflowed and pushed the warning + Cancel/Decline/Submit off-screen. Drop the flex plumbing (and the modal-body styles) and cap the fields directly with ScrollArea.Autosize mah=calc(90dvh - 22rem): it sizes to content when the form is short (modal compact, nothing scrolls) and scrolls once the fields hit the cap. The 22rem reserve = the measured non-field chrome (modal header/padding + message + warning + buttons + gaps), so the whole modal stays within 90dvh and the actions are always in view. Verified in the real PendingClientRequestModal (new ElicitationFormTall story) for both short and tall forms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * web: pin the sampling modal's actions too (#1678) Apply the elicitation-form treatment to SamplingRequestPanel: wrap the request/response content in a ScrollArea.Autosize capped at calc(90dvh - 10rem) so it scrolls while the Reject / Send Response buttons stay pinned in view (the modal has no other close affordance). The 10rem reserve is the pinned chrome (modal header/padding + the actions + gaps) — smaller than the elicitation form's 22rem since there's no warning banner. Add a SamplingTall story (8 messages + preferences) to cover the scroll case in the real PendingClientRequestModal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
dbda70e83b |
Monitoring sidebar toggle, Prompts-style Tools results, and icon-button UI polish (#1661) (#1662)
* feat(web): single header MonitoringToggle + Prompts-style Tools results (#1661) Consolidate the monitoring column's open/close affordances into one header toggle, and align the Tools/Prompts result panes and the sidebar log layout. - Add a single `MonitoringToggle` in the header, to the right of the theme icon. It opens/closes the monitoring column and only shows when a server is connected OR a connect attempt failed (wide viewport + a monitor tab available) — the same availability rule the column uses. - Remove the scattered pin/close icons: delete `PinColumnButton`; drop `onPin` from the Logs/Protocol/Network/Console screens and their stream panels; drop `onOpenMonitor` from the server list; remove the close button from `MonitoringControls`/`MonitoringScreen`. - Tools screen: remove the "Results will appear here" placeholder pane. Results now replace the input form in a single content pane (the Prompts pattern), with a top-left X (ToolResultPanel) that dismisses them back to the form; form values are preserved for a re-run. - Prompts screen: the argument form fills the content pane width (drop the 40% cap), matching the Tools input. - LogEntry: add a `compact` two-line layout (time/level/logger on line one, message wrapping below) used by the embedded monitoring-column Logs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 * polish(web): tooltips on icon buttons + rename "monitoring column" → "monitoring sidebar" (#1661) - Add Mantine `Tooltip`s to the icon-only controls: Client settings, Theme (dynamic light/dark label), the monitoring-sidebar toggle, the list-level and per-entry Expand/Collapse toggles, the Pin/Unpin toggle, and the Replay button. Labels mirror each control's existing aria-label. - Rename the feature term "monitoring column" → "monitoring sidebar" across the user-facing labels (Open/Close/Resize), comments, and tests, so the UI calls it a sidebar consistently. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 * review: consistent sidebar wording + reuse MonitoringToggleProps (#1661) Address the two optional notes from the PR #1662 review: - Make the MonitoringToggle JSDoc voice consistent ("sidebar" throughout, no lingering "column"). - Drop ViewHeader's duplicate `MonitorToggleProps` and reuse the element's exported `MonitoringToggleProps` as the single source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 * feat(web): slide-in/out animation for the monitoring-sidebar toggle (#1661) The header MonitoringToggle now animates in/out via a Mantine Transition whose in/out styles drive width + margin-left (plus opacity/translateX): as its slot grows from 0 → the 36px icon, the flex-end header row reflows and pushes the theme + client-settings buttons one slot to the left; the exit reverses and they slide back over. A negative margin-left while collapsed cancels the row gap so the theme icon stays flush-right when the toggle is absent. Only appearance/ disappearance (connect / disconnect / narrow viewport) animates — opening or closing the sidebar leaves the toggle in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sMW1YZXkM7fDkdPtHpE95 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5d024ab54f |
feat(web): add copyright notice + footer/toolbar polish (#1639)
- Add a grey CopyrightBadge ("Copyright © Model Context Protocol a Series of LF
Projects, LLC.") to the footer. Version (bottom-left) and copyright
(bottom-right) share the bottom `xl` margin band: vertically centered in it
and inset by `xl` to line up with the content's left/right margins.
- Move the list expand/collapse toggle to the right of the "Add Servers" button
in ServerListControls.
- Make the monitoring column's close-sidebar control match the open-sidebar
PinColumnButton theme (Button size sm / variant subtle) instead of a gray
ActionIcon, so the open/close affordances read as a pair.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
|
||
|
|
650e5bd020 |
feat(web): show the Inspector version in the lower-right corner (#1639)
Display the single-source version (root package.json) in the web UI's lower-right corner, in grey — matching the CLI and TUI. The browser can't read the filesystem, so the version flows backend → browser via GET /api/config: - Add `version?: string` to InitialConfigPayload; the web backend (webServerConfigToInitialPayload) reads it once via the shared readInspectorVersion() and tags it onto the payload. - Add a `useInspectorVersion` hook mirroring useSandboxUrl/useServerListWritable. - Add a presentational `VersionBadge` element rendered fixed lower-right, grey, non-interactive; styling lives in a `versionBadge` Text theme variant (no inline styles, --inspector-* tokens). - Wire it into App.tsx. Verified live: /api/config serves `version: "2.0.0"` and the badge renders `v2.0.0` fixed at the lower-right in grey. Tests: useInspectorVersion (full branch coverage), VersionBadge (unit + Storybook), web-server-config payload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
ace4250d31 |
refactor(web): rename "History" tab/feature to "Protocol" (#1623)
Rename the web client's History tab and its supporting code to Protocol so the feature reads as the MCP "Protocol" view. - Tab renders as "Protocol"; INSPECTOR_TAB_IDS, InspectorView tab constants/MonitorTab, TAB_UI_REGISTRY key, and navigation.ts tab id all updated to Protocol. - Rename components + folders: HistoryScreen -> ProtocolScreen, HistoryListPanel -> ProtocolListPanel, HistoryEntry -> ProtocolEntry, HistoryControls -> ProtocolControls, historyUtils -> protocolUtils (plus their tests and stories, and exported types). - Rename App/InspectorView plumbing: historyUi, pinnedHistoryIds, on*History* handlers/props, replayHistoryRequest, isReplayableHistoryMethod, sort/compact scopes, etc. -> Protocol names. - Export filenames use the "protocol" slug (buildExportFilename + ExportKind). Preserved (per issue #1623 decisions): - The in-panel section discriminator keeps its "pinned" | "history" values, and the sub-sections stay labeled Pinned / History. - window.history and core message-log/replay concepts are untouched. npm run ci passes (format, lint, build, unit tests, >=90 per-file coverage gate, smoke, Storybook). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
c6999dff96 |
feat(web): red border on a server card whose connection attempt failed (#1621)
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 |
||
|
|
219298b24d |
feat(web): add Console (stderr) tab; show it on stdio connect failure (#1621)
A failed stdio launch had no useful diagnostic surface: the message log (History) is cleared on the error transition and there are no HTTP requests (Network), so the auto-opened monitoring column was empty. The process's stderr — which explains the failure (e.g. "No module named mcp_server_time") — was captured in StderrLogState but never displayed. - New ConsoleScreen renders the captured stderr stream (search / sort / clear / export; embeddable in the monitoring column), wired through App via the existing useStderrLog hook + StderrLogState. - Console is a pinnable monitor tab like Logs/History/Network, gated to stdio servers (HTTP has no child process). It lives in the header menu when the column isn't pinned, and moves into the column when it is. - The failure column now picks its tabs by which diagnostic actually captured content (stderr → Console, fetch requests → Network), not by the active server's transport: a connect failure fires the client `disconnect` event, which clears `activeServer`, so transport can't be read back there. The memo re-runs as stderr/fetch entries stream in, so the diagnostic tab appears as soon as the failing process prints. Verified live against a failing `python -m mcp_server_time` stdio server: the column auto-opens to Console showing the real ModuleNotFoundError. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw |
||
|
|
3550b64032 |
feat(web): compact two-line HistoryEntry/NetworkEntry for the narrow column (#1616)
In the pinned monitoring column (embedded), the single-line entry headers wrap badly. Add compact two-line variants gated on `embedded`: - HistoryEntry: line 1 = time + client/server badge + elapsed + status; line 2 = method (and target, truncated) on the left with the controls on the right, where Replay becomes an icon (new ReplayButton element) next to Pin/Expand. - NetworkEntry: line 1 = time + HTTP method + category + elapsed + status; line 2 = the URL in a horizontal ScrollArea (new Text `nowrap` variant) with the expand toggle on the right. The full-size (non-embedded) layouts are unchanged. `embedded` is threaded from the History/Network panels into the entries. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
74c6491b52 |
refactor(web): resolve Mantine/CSS rule violations in mid-session auth UI
- App.tsx: replace the inline `style={{}}` on the re-auth banner wrapper with
Mantine style props (pos/top/bg) plus a `.reauth-banner-bar` class for the
z-index/box-shadow that have no Mantine shorthand.
- App.tsx: extract the ~100-line inline `onAuthorize` and the inline `onCancel`
step-up handlers into named `handleStepUpAuthorize`/`handleStepUpCancel`.
- ReAuthBanner: move the flat `styles={{ root }}` (background/border) into a
`reauth` Alert theme variant; replace the raw `<span>` with `<Text>`.
- StepUpAuthModal: extract the inline `onClick` wrapper into `handleAuthorize`.
No behavior change; validate + integration green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DZyeWManYTvcXxjcrhxSQc
|
||
|
|
13dafe457b |
Guided-auth removal, OAuth smoke testing, and TUI/CLI support (#1531)
* refactor(web): update OAuth E2E tests to use new authentication method - Replaced `runGuidedAuth` with `authenticate` in the InspectorClient OAuth end-to-end tests to support quick authentication with CIMD pre-registration. - Updated comments to reflect the change in authentication method and its support for HTTP test metadata URLs. - Ensured that the tests continue to validate the expected authorization URL after the change. Additionally, imported `ensureCimdClientRegistration` in the OAuthManager to streamline client registration during the authentication process. * Guided-auth removal, OAuth smoke testing, and TUI/CLI support * Added CLI OAuth runner flags tests * Added clarifying comment on port 0 binding * README notes on TUI oauth callback port, prettier fixes for CLI tests * CIMD validation uses inline like EMA IdP * Fixed TUI test failures from merge * test: bring 5 OAuth files over the 90 per-file coverage gate The guided-auth-removal work added/touched several files that fell below the repo's >=90 per-file coverage gate (npm run coverage). CI doesn't run the gate (it's local-only), so validate + test:integration passed while these gaps slipped through. Bring them all back over the line: - core/auth/node/runner-oauth-callback.ts: test bad-URL/non-http-scheme throws, no-port->80 default, and IPv6 hostname bracketing. Annotate the provably-dead guards (empty host, out-of-range port, String(err) fallback, pathname "/" fallback) -- all rejected by new URL() upstream -- with justified v8 ignore comments instead of lowering the gate. - core/auth/cimd.ts: test the AS-doesn't-support-CIMD early return. - core/react/useClientSettingsDraft.ts: test the functional-updater path, the stale-onChange-after-close prev===null guard, and the timer-fires-after-close value===null guard. - ClientSettingsForm.tsx: test the CIMD checkbox toggle and CIMD URL clear. - clients/tui/src/utils/oauthDisplay.ts: test static/dcr registration kinds, the expired IdP session, and the configured-scope/empty-scope branches of formatScopes. npm run coverage now passes (exit 0) across web, cli, tui, launcher. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: cliffhall <cliff@futurescale.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0b0bd40e5d |
fix(web): remove mantine-border-animate, use green highlight border (#1536)
* 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> |
||
|
|
d04a7fb9c8 |
fix(web): keep server cards a fixed height (#1528) (#1530)
* fix(web): keep server cards a fixed height (#1528) Two causes made server cards ragged-height: 1. The command/URL box wrapped long content onto multiple lines, growing the card. Add a `nowrap` Code variant (single line, ellipsis on overflow) and a `wrap` prop on ContentViewer (default true); ServerCard passes `wrap={false}`. The full value stays available via the copy button. 2. At ~1000-1500px the grid packed cards narrow enough that a connected card's action row (Clone/Edit/Remove + Connection Info/Settings) wrapped and stacked. Switch the SimpleGrid to container queries with thresholds that keep every card >= ~500px wide at each column count, so it drops to fewer/wider columns instead of wrapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): address review — correct threshold comment, single-line note, title tooltip - Correct the column-threshold comment in ServerListScreen: the values (1040/1560) use N gaps, not N−1; restate the formula accurately so the numbers aren't "fixed" to match a wrong formula later. - Note in the ContentViewer `wrap` JSDoc that `false` is for single-line values only (nowrap collapses embedded newlines). - Add a native `title` tooltip to the non-wrapping Code box so a clipped command/URL is readable on hover, not only via copy. Covered by tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bb32d1db5f |
fix(web): section-header hover outline + clear buttons out of tab order (#1487) (#1488)
* fix(web): section-header hover outline + clear buttons out of tab order (#1487) Two button-behavior adjustments: 1. History section headers (Pinned / History) now use the filter-toggle style — a thin outline on hover instead of a background fill — by pointing the `sectionHeader` Group variant at the shared `.filter-toggle` class. The open section keeps its filled background (passed per-instance via `bg`). 2. Every input clear button added in #1448 is removed from the keyboard tab order via `tabIndex={-1}` (CloseButton clear buttons app-wide, plus the Autocomplete clear button through `theme/Autocomplete.ts` clearButtonProps), so tabbing through a form moves to the next field, not onto the clear button. The buttons stay mouse-clickable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): apply filter-toggle hover outline to Resources accordion headers (#1487) The Resources screen `disclosure` accordion controls now share the same outline-on-hover treatment as the filter toggle buttons and History section headers: a thin border on hover instead of a background fill, plus a filled background when the section is open (keyed off `aria-expanded`). The `.filter- toggle` class is added to the disclosure control slot; the default-variant accordion (Server Settings) keeps its background-fill hover via a `:not(.filter- toggle)` guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): extract ClearButton element, replace 34 inline clear buttons Addresses the code-review suggestion on PR #1488: the same five-line `CloseButton` clear-affordance block (aria-label="Clear" + tabIndex={-1} + onClick) was hand-copied across 17 files. Extract a single `elements/ClearButton` (Mantine `CloseButton.withProps` with the aria-label and tabIndex baked in) and replace all 34 call sites, so the clear-button contract lives in one place. A global CloseButton theme default would be wrong here — it would also catch tabbable modal-close X buttons and the Select clear buttons — so a dedicated element is the right altitude. Net -151 lines. Adds element unit tests + a Storybook play test covering the aria-label / tabIndex / clickable contract for all sites at once. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
ef921ba052 |
fix(web): hide resource subscription UI when server lacks subscribe capability (#1479)
* fix(web): hide resource subscription UI when server lacks subscribe capability (#1478) Thread a `subscriptionsSupported` flag derived from `capabilities?.resources?.subscribe === true` from App through InspectorView → ResourcesScreen to the leaf components. When false: - ResourcePreviewPanel hides the Subscribe/Unsubscribe button (Refresh stays). - ResourceControls omits the Subscriptions accordion section entirely, dropping it from `allSections` so the expand/collapse-all toggle and open-section bookkeeping stay correct even if a stale "subscriptions" entry persists from a prior subscription-capable session. The flag defaults to true at the leaf components so existing callers, tests, and stories keep rendering the subscription controls unless a server is explicitly marked unsupported. Adds unit tests and Storybook stories covering the hidden and shown states across all three components. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): give the Servers screen scroll the same gutter/idle-hidden treatment (#1474) The server grid's ScrollArea.Autosize used the default type="hover" with no offsetScrollbars, so the scrollbar popped in on hover and overlaid the right edge of the server cards (occluding their action icons / status badges) — the same problem #1474 fixed for the History/Network/Logging list panels. Apply the same `type="scroll"` + `offsetScrollbars` treatment, and reuse the shared `expectScrollbarGutterIdleHidden` story assertion to regression-protect the gutter + idle-hidden behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): stop the InspectorView shell from scrolling as a whole AppShell.Main carried Mantine's default `padding="md"` on top of the fixed-header offset, but every screen already sizes itself to `calc(100vh - header)` and supplies its own `xl` padding (with no allowance for Main's padding). That extra inset pushed screen content past the viewport, so the whole InspectorView gained a page-level scrollbar instead of confining overflow to each screen's inner ScrollArea. Set the shell `padding` to 0 (Main now contributes only the header offset, matching what the screens were built for) and clamp the Main slot to `height: 100dvh` with `overflow: hidden` in the theme, so the view can never scroll regardless of content — only the inner ScrollArea regions do. Adds an InspectorView story play assertion guarding the invariant (Main clips + is viewport-clamped) and a ManyServers fixture that confirms it holds under enough content to overflow, with the inner server-grid ScrollArea absorbing the overflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): address review — align viewport units + drop stale subscriptions intent Responding to the @claude review on #1479: - Align all screen height calcs from `100vh` to `100dvh` so they match the Main slot's `100dvh` clamp. On a mobile viewport where `dvh < vh` a `100vh` child would have exceeded Main's `100dvh` content box and been clipped by `overflow: hidden`; the units are now in lockstep. - Restrict `intendedButEmptySections` in ResourceControls to `allSections` so a stale "subscriptions" entry persisted from a prior subscription-capable session is dropped from persisted open-sections instead of being perpetually re-appended once the section is no longer rendered. Adds a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d525fc9f54 |
fix(web): ResourceControls single-scroll, full-height panel, right/down chevrons (#1462) (#1464)
* fix(web): ResourceControls single-scroll + right/down disclosure chevrons (#1462) Two fixes to the Resources controls panel: 1. Sections scrolled prematurely. `panelMaxHeight` divided the available height equally across open sections (`… / n`), so a section with many items scrolled while sparse siblings left their equal share unused. Replace the per-section ScrollAreas with a single viewport-bounded ScrollArea.Autosize around the whole accordion (matching ToolControls / PromptControls). Sections size to content; nothing scrolls until the open sections' combined height exceeds the cap. 2. Accordion chevrons pointed down/up. Add a `disclosure` Accordion theme variant + `.disclosure-chevron[data-rotate]` rule that rotates a right-pointing RiArrowRightSLine 90° (not Mantine's default 180°), so closed sections point right and open sections point down — matching the ResourceLink chevron convention. Also keep empty (disabled) sections out of the accordion's open set so they render collapsed with a right chevron instead of an open-but-empty panel. Verified all states in a real browser via Playwright. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(web): cover empty-section-collapsed behavior in ResourceControls (#1462) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): make Resources sidebar fill panel height before scrolling (#1462) The first pass capped the accordion scroll region at a fixed sub-viewport height (`100vh - … - 160px`), so the sidebar scrolled before reaching the height of the detail panel to its right. Make the sidebar card full-height instead: add a `sidebar` Card variant (height:100%, flex column, overflow hidden), give it `flex: 1`, and let ResourceControls' root Stack (`flex: 1; min-height: 0`) and a plain flex-filling ScrollArea claim the space below the title/search. The card now matches the detail panel's height exactly and the single inner scroll region scrolls only once the open sections overflow the full panel. Adds a ResourcesScreen ManyResources story asserting the sidebar/detail height match and that overflow scrolls, plus a unit test for the empty-section-collapsed behavior. Verified heights + scroll in a real browser via Playwright. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): pin Resources section headers, scroll each section in place (#1462) The previous pass scrolled the whole accordion as one block (headers scrolled away). Restore pinned headers with per-section scrolling, without the original equal-division premature-scroll bug. The `disclosure` Accordion now fills its full-height card as a flex column (`disclosure-sections`): controls are pinned, and each open section's panel scrolls within its own share of the remaining height. Per-item `flex` weights (shrink ∝ item count, grow 0) hand space from long sections to short ones, so nothing scrolls until the combined content overflows the panel — a long URIs list scrolls in place while Templates stays fully visible below it. Drops the single outer ScrollArea (and useScrollMemory). Verified in a real browser: 6 URIs → no scroll, headers pinned, empty space at the bottom; 40 URIs → URIs panel scrolls in place, Templates intact, all three headers pinned, sidebar still matches the detail height. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): Resources sidebar grows with content, capped at screen (#1462) The `sidebar` Card variant forced `height: 100%`, so the panel was always full-height with empty space below short content. Switch to `max-height: 100%` (and drop `flex: 1`) so the card hugs its content and only caps at the screen height — matching the Tools panel. When content overflows the cap, the flex accordion still takes over per-section scrolling with pinned headers. Verified in a real browser: 6 URIs → card hugs (638px, shorter than the detail panel); 40 URIs → card caps at the screen (949px == detail), headers pinned, URIs scrolls in place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): preserve empty-section intent + kill accordion collapse jump (#1464 review) Address review feedback on PR #1464: - Intent loss (review #1): the accordion's `value` is the filtered `visibleOpenSections`, so Mantine's toggle dropped open-but-empty sections from the persisted intent. Merge them back in the onChange handler so an open empty section stays open and reappears with items. Adds a unit test. - Collapse animation jump (review #3): Mantine's Collapse briefly set the panel to its full natural height (fighting the flex layout) on open/close, flashing siblings. Set `transitionDuration={0}` so flex controls the height; restore a smooth chevron rotation via CSS. Verified no jump in a real browser (panel 501→0, no spike). Since closed sections now render no content, drop the redundant (and incorrect) "Templates" toggle clicks from 4 ResourcesScreen tests (the section is open by default) and add a section-toggle test to keep the openSections handler covered. - Nits: scope the stories' chevron query to the accordion root; tighten the Collapsed assertion to the identity transform. Native-vs-styled scrollbar (review #4) split out as #1466. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(web): clarify chevron-timing and intent-merge invariants (#1464 review) Address the two optional doc nits from the re-review: - Note that the chevron's 200ms restates Mantine's default accordion duration (keep in sync if accordion timing is themed). - Note that the unconditional empty-section merge is safe because empty-section controls are disabled, so `next` never contains one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
d90cc4c34b |
fix(web): filter toggle hover as border, not background (#1460) (#1461)
* fix(web): filter toggle hover as border, not background (#1460) The Logging/History/Network filter toggles used the same --mantine-primary-color-light background for both their hover and their active (toggled-on) state, so clicking a button to turn it off while the cursor was still over it looked like a no-op — the hover background masked the removal of the active background. - Add a shared FilterToggleButton element used by LogControls, NetworkControls, and MessageDirectionFilter, replacing the duplicated UnstyledButton + inline `bg={active ? … : undefined}` in each. - Add a `filterToggle` UnstyledButton theme variant + `.filter-toggle` rules: hover is now a thin border (reserved via a transparent 1px border so it doesn't shift layout); the active state is the filled background, keyed off `aria-pressed="true"`. - This leaves the `.list-item` selectable rows (Prompts/Apps/Tools/ Resources lists, section headers) untouched — they keep their hover-fill since they aren't toggles. - Add `aria-pressed` to the Logging level toggles (previously missing). Tests + stories cover the active/hover distinction via aria-pressed for FilterToggleButton and all three controls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): move filter-toggle border to CSS so :hover actually shows (#1460) The previous attempt set the reserved `1px solid transparent` border via the theme's `styles` callback, which Mantine emits as an *inline* style. Inline styles outrank stylesheet `:hover` rules, so `.filter-toggle:hover { border-color }` never applied — hovering showed no border at all (verified in a real browser via Playwright). Move the border (and border-radius) out of the inline theme styles into the `.filter-toggle` class in App.css, so `:hover` can override the transparent border-color. The active fill (`[aria-pressed="true"]`) was already correct. Verified with real Playwright page.hover: inactive+hover now shows a 1px --inspector-brand-primary border, active shows the fill, active+hover shows both, all distinct. Story play functions assert the active fill and the reserved transparent border (the hover border can't be asserted in the synthetic-event test runner, so it's documented as Playwright-verified). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a10ced988c |
feat(web): show Apps, Prompts, Resources, and Tasks tabs only when the server exposes the relevant items (#1450) (#1453)
* feat(web): show Apps tab only when server exposes MCP App tools (#1450) Gate the "Apps" tab on appTools.length > 0 so it is hidden when the connected server exposes no MCP App tools. appTools is now computed before availableTabs, which depends on it — the tab appears/disappears live as the tool list changes (list-changed refresh, server switch). The existing activeTab fallback already snaps a user sitting on Apps back to Servers when the tab disappears. When app tools exist but the sandbox is unavailable the tab stays visible so the AppsScreen's "unavailable" message remains reachable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): content-gate the Prompts and Resources tabs too (#1450) Extend the Apps tab gating to Prompts and Resources: each tab is hidden when its list is empty and appears reactively when the list gains an entry (list-changed refresh, server switch). Resources is gated on resources OR templates, since a server may expose only templates. The existing activeTab fallback snaps a user on a now-empty tab back to Servers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): content-gate the Tasks tab too (#1450) Extend the content gating to the Tasks tab: hidden when no tasks exist, appears reactively once a task-augmented tool call creates one and disappears when all are cleared. Consistent with the Apps/Prompts/ Resources gates — the existing activeTab fallback snaps a user on a now- empty tab back to Servers. The "run as task" affordance still lives on the Tools screen (gated by server task support), so discovery is unaffected; advertised capabilities remain visible in Connection Info. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): fade content-gated tabs in to draw attention on appearance (#1450) Add a brief 325ms fade-in to navigation tab labels via a new `tabLabel` Text theme variant + `inspector-fade-in` keyframe. SegmentedControl keys segments by value, so when a content-gated tab (Apps/Prompts/Resources/ Tasks) appears after a list change its label mounts fresh and fades in to draw the eye, while existing tabs stay put. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): animate tab-bar width when a tab is added or removed (#1450) Wrap the tab SegmentedControl in a clip element (new `tabBar` Group variant) whose width is set to the control's measured border-box size and transitions over 325ms ease-in. The control is sized `max-content` and the clip `overflow: hidden`, so when a content-gated tab appears or disappears the bar grows/shrinks smoothly. Width is measured with useElementSize (ResizeObserver); it falls back to auto width where the observer is unavailable, so the bar still renders at its natural size. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): lengthen tab-label fade-in to 450ms (#1450) Bump the `tabLabel` fade from 325ms to 450ms so a newly-appearing tab name fades in more gently. Also formats the previously-unformatted ViewHeader width-clip test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): drop tab-label fade-in, keep only the 325ms width animation (#1450) Remove the per-tab fade-in (tabLabel Text variant + inspector-fade-in keyframe + label wrapping) so the tab bar's 325ms ease-in width change is the sole animation when a content-gated tab appears or disappears. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): collapse the tab bar to 0 width when it leaves the DOM on disconnect (#1450) Unify the connected/disconnected ViewHeader into a single tree so the tab controls can sit in a keep-alive Mantine Transition. On disconnect the Transition holds the bar mounted for 325ms while the clip's CSS width transition collapses it measured->0, then unmounts. The connected tab list/selection is snapshotted (adjust-state-during-render, no refs) so the bar keeps rendering its tabs as it shrinks. The disconnected header keeps its logo-left / title-center / theme-right layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): fade the disconnected title in after the tab bar collapses (#1450) On disconnect, hold the "MCP Inspector" title until the tab bar's shrink-to-0 exit finishes (tracked via the keep-alive Transition's onExited), then fade it in over 300ms. Removes the prior overlap where the title appeared beside the still-collapsing bar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): set both disconnect animations to 300ms (#1450) Drop the tab-bar collapse from 325ms to 300ms so it matches the title fade (also 300ms), trimming the overall disconnect sequence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): staggered fade+slide-down crossfade between title and tab bar (#1450) Replace the tab-bar width/size animation with a staggered crossfade. The disconnected "MCP Inspector" title and the connected tab bar share one CSS grid cell, so on connect/disconnect one fades + slides down out while the other fades + slides down in — the incoming one delayed by half the 300ms duration so they're staggered and one replaces the other in place. Both motions travel downward (enter descends from above, exit descends below) via inspector-fade-slide-in/out keyframes, selected per element with a data-anim attribute; Mantine Transition provides keep-alive so the exit plays before unmount. Removes the clip/useElementSize/tabBar-variant size-animation machinery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): pulse a red glow on a tab that newly appears mid-session (#1450) When a content-gated tab is added to the bar while connected, pulse a red text glow around its label in-then-out over 325ms. New tabs are detected by comparing the shown set to the previous render's (adjust-state-during- render), so only mid-session additions glow — not the initial set on connect, nor anything on disconnect. The glow is a CSS keyframe (inspector-tab-glow) applied via a `tabGlow` Text variant + `data-glow` attribute; the red comes from a new --inspector-glow-red token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): start the new-tab glow at full intensity, then fade (#1450) Change the tab glow from an in-then-out pulse to full-on-appear then fade over 325ms (ease-out), so a newly added tab flashes brightest the instant it appears. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): keep new-tab label text red through the glow, then revert to theme (#1450) Animate `color` red across the whole glow keyframe so a newly-appeared tab's text stays red for the 325ms; with no forwards fill it snaps back to the label's theme color when the animation ends. The glow text-shadow still fades over the duration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): revert new-tab text color animation, keep glow only (#1450) Drop the `color: red` keyframe stops so the label text keeps its theme color; only the red glow (text-shadow) plays, full intensity on appear then fading over 325ms. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make the new-tab glow ease-in over 325ms (#1450) Change the tab glow timing function from ease-out to ease-in (duration already 325ms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): animate server name and right-side controls on connect/disconnect (#1450) Wrap the server name (left) and the status indicator + Disconnect control (right) in keep-alive Transitions so they fade + slide-down in on connect and out on disconnect, using the same values as the tab bar. Split the animation CSS (.header-anim[data-anim]) from the center-stack grid placement so it's reusable, and broaden the snapshot to retain serverName/ status/latency for rendering during exit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): address review — glow grace window, Select comment, key unification (#1450) - Arm the new-tab glow only after a GLOW_GRACE_MS window post-connect, so asynchronously-resolved lists (prompts/resources/tasks) aren't mistaken for mid-session additions and don't glow on initial load. - Document why the narrow-viewport Select doesn't glow. - Route snapshotKey and the glow key through a shared tabsKey()/TAB_SEP helper (also removes a stray control char from the join separator). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(web): note the slow-server tail of the glow grace window (#1450) Acknowledge in a comment that GLOW_GRACE_MS keys on time-since-connect, not on lists having settled, so a server slower than the window glows on its tail. Per review follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): honor prefers-reduced-motion for the new header animations (#1450) Add a reduced-motion media query that disables the header fade/slide (.header-anim) and the new-tab glow (.tab-glow), leaving elements at their resting state. Per review accessibility suggestion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0890c7509a |
feat(web): add Mantine clear buttons to text inputs app-wide (#1333) (#1448)
* feat(web): add Mantine clear buttons to text inputs app-wide (#1333) Add a clear (×) affordance to every user-editable text input across the web client so a populated field can be reset in one click. - Autocomplete: enable `clearable` by default via the theme (`theme/Autocomplete.ts`) — covers the prompt-argument and resource-template variable autocomplete branches. - TextInput / Textarea: Mantine v8 does not expose a public `clearable` prop on these primitives (only the combobox-based components do), so add the clear button manually via `rightSection` + `CloseButton` (aria-label="Clear") shown only when the field has a value, wired to reset the field's controlled value to "". Applied to sidebar search inputs (prompts, tools, apps, tasks, history, resources, network, logs), prompt-argument and resource-template variable inputs, the sampling response/model fields, server config & settings forms, roots table, schema form, experimental features panel, and the server.json import panel. Read-only/display Textareas (OutputValidationModal, UrlElicitationErrorModal) are intentionally left untouched. Tests cover the clear behavior on representative inputs from each category (sidebar search, prompt argument, resource-template variable, sampling response, plus the config/settings forms). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): disable ServerConfigModal clear buttons while submitting Address review #2 on #1448: the clear CloseButton sat in `rightSection` with `rightSectionPointerEvents="auto"`, so it stayed clickable even while the field was `disabled={submitting}`, letting a user wipe a field mid-submit. Forward `disabled={submitting}` to each clear button so it locks together with its input. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(web): remove redundant Auto-respond from SamplingRequestPanel The sampling modal pre-loads its draft with the default stub result, and "Send Response" sends that draft — so on an untouched form "Auto-respond" did exactly the same thing as "Send Response", just with a fresh copy of the same defaults. Drop the redundant button and its `onAutoRespond` prop; "Send Response" now covers both the unedited (stub) and edited cases. The InlineSamplingRequest variant keeps its Auto-respond button — there it is the quick path versus "Edit & Send" (which opens this editable modal), not a duplicate of an adjacent send action. Updated PendingClientRequestModal wiring, stories, and tests accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
121f875a0e |
feat(web): History — Pin & Replay, plus two history-status fixes (#1438) (#1441)
* fix(web): don't show a permanent PENDING badge on history notifications (#1439) `extractStatus` derived the history status badge purely from whether a `response` was attached, so a fire-and-forget notification (no id, no response ever) — and any unmatched standalone response — was labelled "Pending" forever. The pending → OK/Error lifecycle only applies to request entries (messageLogState attaches the response by JSON-RPC id), so scope the status to `direction === "request"` and render no request-style badge for notifications/responses. The method badge already labels them. Part of the History-screen work on #1438. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(core): track outgoing responses so server→client requests resolve in history (#1440) MessageTrackingTransport.send only tracked outgoing requests, so the client's response to a server→client request (roots/list, sampling, elicitation) was never recorded. The inbound request entry never got its response folded in, leaving it stuck "Pending" with no response body. Make send symmetric with onmessage: track outgoing responses (by id + result/error) as well as outgoing requests. The response now correlates to its request entry, which resolves to OK/Error with the body. Outgoing notifications remain untracked (separate gap). Part of the History-screen work on #1438. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): wire up History Pin and Replay (#1438) Both HistoryEntry actions were rendered but stubbed to todoNoop in App. - Pin: App now owns a session-scoped Set<string> of pinned entry ids, toggled by onTogglePinHistory and passed down as pinnedHistoryIds. HistoryListPanel already sorts pinned entries to the top. The set resets with the rest of the per-screen state on disconnect/server-switch. - Replay: onReplayHistory re-issues the entry's original request by method (tools/call → callTool, prompts/get → getPrompt, resources/read → readResource). The call flows through InspectorClient → tracked transport → message log, so the fresh request+response surface as a new History entry (history-local) without touching the Tools/Prompts/ Resources panels. Unsupported methods / a removed tool surface a toast. Tests: pin toggle propagation, replay dispatch for each supported method, unsupported-method and missing-tool toasts. Closes #1438. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make history Replay support list/discovery requests Replaying a tools/list entry reported "Replay isn't supported". Extend replayHistoryRequest beyond the call-style requests to the read/discovery methods, re-issuing each via its InspectorClient method (preserving the pagination cursor): - tools/list → listTools, prompts/list → listPrompts, resources/list → listResources, resources/templates/list → listResourceTemplates, ping → ping. Tests: tools/list replay (cursor preserved) and an unsupported-method toast (logging/setLevel). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): replay tasks/list; reorder + conditionally show History buttons - Add tasks/list to the replayable set (re-issued via listRequestorTasks), completing the client-issued */list methods. - Reverse the HistoryEntry actions to Pin then Replay, and hide Replay for entries whose method can't be replayed. - Introduce a single source of truth — REPLAYABLE_HISTORY_METHODS / isReplayableHistoryMethod in historyUtils — used by HistoryEntry to show/hide the button and by App's replayHistoryRequest to gate dispatch, so the two can't drift. Tests: tasks/list replay, Replay hidden for a non-replayable method, and Pin-before-Replay ordering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): add client↔server direction badge to History and Network (#1437) A dual-state badge at the top-left of each entry shows which way it traveled: "client → server" (green) for inspector-originated, "client ← server" (yellow) for server-originated. - New shared element MessageDirectionBadge (Elements). - History: MessageEntry gains an `origin` ("client" | "server") set at tracking time — MessageTrackingTransport tags `send` as client and `onmessage` as server, threaded through the track callbacks. HistoryEntry maps origin → badge. (direction stays the request/response/notification message-type used for response correlation; origin is the flow.) - Network: fetches are always inspector-originated, so NetworkEntry renders the badge as outgoing. Tests + stories for the badge; HistoryEntry direction tests; transport test asserts the origin argument. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): use purple (not yellow) for the client ← server direction badge Yellow + green read as caution/ok status; purple vs green reads as direction. Incoming (server → client) is now violet; outgoing stays green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): make History Pinned/History sections collapsible toggles The "Pinned Requests (N)" and "History (N)" section headers are now clickable toggle buttons that expand/collapse their entries — styled like the LogControls level toggles (UnstyledButton listItem variant with the active background) and wrapping the entries in a Collapse. Both default open and collapse independently. Tests assert the per-section aria-expanded toggling and independence. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): replace Expand/Collapse text button with an icon toggle History, Network, and Task cards now use a shared ExpandToggle ActionIcon instead of the "Expand"/"Collapse" text button: LuArrowDownToLine when collapsed (expand), LuArrowUpFromLine when expanded (collapse). The aria-label stays "Expand"/"Collapse" so the control reads the same. - New shared element ExpandToggle (+ test + stories). - HistoryEntry: actions split into a left group (Pin/Replay) with the toggle pushed right via space-between. - NetworkEntry / TaskCard: drop their now-unused SubtleButton const. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): pin icon toggle on HistoryEntry, moved to the right Replace the Pin/Unpin text button with a shared PinToggle ActionIcon (TiPinOutline when unpinned, TiPin when pinned; aria-label stays Pin/Unpin). Move it to the right action group, just left of the expand/collapse toggle — Replay stays on the left. - New shared element PinToggle (+ test + stories). - HistoryEntry action row: Replay left; Pin + expand toggle right. - Drop the now-unused formatPinLabel helper. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): per-section Clear/Export on the Pinned and History headers Each section header now shows Clear and Export links on the right, but only when both sections are present (a single section is already covered by the panel-level Clear/Export). They act on just that section's entries: - Clear: removes that section's entries by pin membership (clearing the Pinned section also drops the now-stale pinned id set). - Export: downloads just that section's entries (inspector-history-pinned/-unpinned-...json). Threaded onClearSection/onExportSection App → InspectorView → HistoryScreen → HistoryListPanel; App computes each section from messages + pinned ids. Extracted a CollapsibleSection subcomponent and a SectionActions links group. Extended ExportKind with history-pinned/history-unpinned. Tests cover both-sections visibility gating and per-section dispatch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): Filter by Message Direction on the History controls Add a "Filter by Message Direction" section to HistoryControls, modeled on LogControls' "Filter by Level": a Select/Deselect All control plus two listItem toggles — "client → server" and "client ← server" — that show or hide entries by origin. - HistoryUiState gains visibleDirections (Record<MessageOrigin, boolean>), defaulting to both visible; persists with the other per-screen state. - HistoryScreen wires the toggles (single + all) through onUiChange and passes the filter to HistoryListPanel, where matchesFilters drops entries whose direction is off (entries without a recorded origin are never filtered out). Tests cover the controls (render, single toggle, select/deselect all), the HistoryScreen onUiChange wiring, and the list-level direction filtering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): section Clear/Export as subtle buttons, not link anchors Match the Select/Deselect All control in HistoryControls: the per-section Clear/Export on the History/Pinned headers now use a subtle Button (variant subtle, size xs) instead of an Anchor link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): color the direction filter labels to match the badge The "client → server" / "client ← server" toggles in HistoryControls now color their labels green / violet, matching the MessageDirectionBadge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): track outgoing client notifications in history too Review follow-up (#1441): MessageTrackingTransport.send now also tracks the client's own notifications (initialized, progress, roots/list_changed) as origin "client", making send fully symmetric with onmessage. Previously only id-bearing outgoing messages (requests, responses) were tracked, so client notifications never reached History while incoming server notifications did. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(web): Filter by Message Direction on the Network screen Add the same "Filter by Message Direction" section to NetworkControls as on HistoryControls. Extracted the shared section into a MessageDirectionFilter group component (used by both HistoryControls and NetworkControls) so they stay identical — a Select/Deselect All control plus green/violet client → server / client ← server toggles. - NetworkUiState gains visibleDirections (default both visible), persisted with the rest of the per-screen state; NetworkScreen wires the toggles and NetworkStreamPanel filters on them. - Network fetches are always inspector-originated, so all entries are client → server: the client toggle gates them all and the server toggle matches nothing (documented in NetworkStreamPanel). The section mirrors History for parity. Tests cover the shared filter, both controls' direction sections, the Network list filtering, and the NetworkScreen wiring; ambiguous "Deselect All" selectors (now two sections) disambiguated by order. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * revert(web): drop message direction from the Network tab Network traffic is always client → server — even SSE is a client-initiated stream tagged outgoing — so a direction badge/filter there is meaningless. Remove the MessageDirectionBadge from NetworkEntry and the Filter by Message Direction section (and its NetworkUiState.visibleDirections wiring) from the Network screen. History keeps its direction badge + filter; the shared MessageDirectionBadge and MessageDirectionFilter components stay (still used by History). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): a lone History section isn't a collapsible accordion When only one of Pinned / History is present, its header is now a plain title and its entries always show — instead of a `listItem` accordion toggle that could hide them based on a stale collapsed state from when both sections were on screen. The accordion (and per-section Clear/Export) only applies when both sections are present. Example fixed: collapse History while pins exist, then unpin everything — History becomes the sole section and shows its entries under a plain header rather than staying collapsed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): put section Clear/Export on the header pleat itself The per-section Clear/Export sat to the right of the collapsible header bar, on the panel background. Make the whole header row the "pleat": a new Group `sectionHeader` theme variant (rounded + hover, active bg passed per instance) wraps a flex-1 toggle button (the title) on the left and the Clear/Export buttons on the right — so the actions live on the pleat. The toggle is a separate button since the action buttons can't nest inside it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style(web): entry expand toggle uses the same icons as ListToggle Swap ExpandToggle (History / Network / Task cards) from the LuArrow icons to RiExpandVerticalLine / RiCollapseVerticalLine — the same expand/collapse icons the list-level ListToggle uses. Same ActionIcon size, so it matches the other small buttons on the cards; aria-labels unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(web): panel Clear keeps pinned entries (history-only) Review follow-up (#1441): the panel Clear button is gated on the unpinned count (implying history-only), but onClearHistory wiped everything (pinned included) and left pinnedHistoryIds stale. Make Clear history-only — clearMessages((m) => !pinnedHistoryIds.has(m.id)) — so pinning protects an entry, matching the gating and the per-section model; the pin set stays valid since its entries survive. Also drop the stale "and Network" mention from the MessageDirectionFilter docstring (Network direction filter was reverted). Test: panel Clear's predicate keeps pinned ids and removes unpinned. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e3b0b6a908 |
feat(connection-info): wire Connection Info modal to the connection handshake (#1377)
* 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> |
||
|
|
4641339cc3 |
feat(network): add Network screen surfacing backend fetch log (#1355) (#1363)
* feat(network): add Network screen surfacing backend fetch log (#1355) Routes `FetchRequestLogState` to a new Network tab so users can inspect HTTP-layer request/response headers, status, duration, and category (auth vs transport) of every fetch the backend makes for an active session. Adds `NetworkControls` + `NetworkStreamPanel` + `NetworkEntry` groups, a `monoBreak` Text theme variant for long header values, and wires onClearNetwork / onExportNetwork (JSON download) from `App.tsx`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(network): include request / response bodies in search filter Headers were already covered but bodies were not, so searches for strings that live inside the JSON-RPC payload (e.g. "jsonrpc", "initialize", a tool name) silently returned no matches. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(network): always render Response Body section, note streaming Streaming responses (text/event-stream, application/x-ndjson, POST to /mcp) are intentionally not body-captured by the fetch tracker, so the section was silently omitted. Now whenever a response was received we render the section, with either the body, a "(empty)" note, or a "Streaming response — body not captured" note keyed off content-type. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(network): capture response bodies via async body-update events Two related fixes: 1. The fetch tracker was marking every streamable-HTTP POST /mcp call as "not captured" because the response content-type is text/event-stream. In practice those streams are bounded — the server sends the JSON-RPC reply (sometimes preceded by progress events) and closes — so we can clone the body and read it. Only GET + SSE / ndjson is the unbounded long-lived channel that has to stay uncaptured. 2. Reading the body inline blocked the transport from processing progress notifications until the entire stream finished, breaking the `resetTimeoutOnProgress` integration test. The body is now read in the background and dispatched via a new `fetchRequestBodyUpdate` event that the FetchRequestLogState patches into the matching entry by id. Plumbing wires through node/transport, the remote SSE channel (RemoteSession, RemoteClientTransport), and the remote event types. Also hides the Network tab when the active server is stdio (no HTTP traffic to surface) and bumps the inline body preview limit to 100 KB so typical tools/list responses render rather than tripping the "too large" notice. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(network): show orange SSE badge for long-lived stream entries Adds a small "SSE" badge to the right side of the NetworkEntry header (left of the HTTP status badge) when the response is a long-lived streaming channel (GET + text/event-stream or application/x-ndjson) — the only case where the body genuinely isn't captured. Makes the "not captured" placeholder less surprising at a glance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(network): address PR review findings - Stop capturing OAuth response bodies (drop updateResponseBody from the effectiveAuthFetch tracker). Restores pre-PR behavior for auth-category entries so live access_token / refresh_token values can't leak into the Network tab body preview during a screen-share. Headers + status are still tracked. - Extract `isLongLivedStreamResponse(method, contentType)` to core/mcp/fetchTracking so the rule lives in one place; consumed by the tracker and the NetworkEntry UI placeholder. - Document the intent of NetworkEntry's `useEffect` (the list-level toggle is authoritative; per-entry overrides are intentionally discarded on each list toggle). - Rename MAX_INLINE_BODY_BYTES → MAX_INLINE_BODY_CHARS (`.length` is UTF-16 code units, not bytes) and update the user-facing notice. - Switch NetworkStreamPanel's search from joined-string `.includes` to per-field `.some(.includes)` so a term can't span field boundaries. Regression test added. - Add aria-label to ListToggle ("Expand all" / "Collapse all") — both an a11y improvement and a stable selector that lets the panel test stop fishing the button out by text-content negation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(network): use ListToggle aria-label in panel + modal tests Cleanup spotted in PR review follow-up: - HistoryListPanel + ServerSettingsModal previously fished the ListToggle out by empty text content because the toggle had no accessible name. Now that ListToggle exposes "Expand all" / "Collapse all", both tests can use getByRole + name and the stale comments go away. - ListToggle's own tests now assert the aria-label contract so a future refactor can't silently drop it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(network): expose category toggle state via aria-pressed The category UnstyledButtons in NetworkControls previously communicated their toggled state only via background color. Adds aria-pressed={active} so assistive tech can discover the on/off state without visuals, with a test that asserts the attribute tracks the visibleCategories prop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8d6dea0af2 |
Port resource preview UX improvements to PromptsScreen (#1328) (#1331)
* Port resource preview UX improvements to PromptsScreen (#1328) Brings the prompt-fetching flow up to parity with the resource preview work from #1326: - Cap the argument-form pane at maw=40% so a bare input + button doesn't stretch across wide displays. - Auto-fetch no-argument prompts the moment they're picked from the sidebar (handleSelectPrompt routes them straight to onGetPrompt({})), and hide the form once the user clicks Get Prompt — the result panel takes the same fixed-height column the resource preview uses. - Apply the PreviewCard variant=preview pattern: card sizes to content but caps at viewport, PromptMessagesDisplay pins its header (flex 0 0 auto) and lets the inner ScrollArea (flex 0 1 auto, mih 0) absorb overflow. - Tag getPromptState with the prompt name in App.tsx so the screen can ignore a stale result whose name no longer matches the selection. - MessageBubble now routes each content block through ContentViewer (markdown for text via mimeType="text/markdown", image/audio/resource via existing ContentViewer branches). Non-renderable block types (tool_use, tool_result) are filtered out; the role-label header keeps the bubble visible regardless. - PromptArgumentsForm gains onCompleteArgument + completionsSupported; when both are set, each input becomes Mantine Autocomplete with the same per-arg AbortController + per-arg debounce timer pattern as ResourceTemplatePanel. PromptsScreen + InspectorView + App.tsx wire the callback to inspectorClient.getCompletions with a ref/prompt envelope. Closes #1328. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add close (X) buttons to ResourcePreviewPanel and PromptMessagesDisplay A top-left CloseButton dismisses the preview panel. The host screen decides what to fall back to: - ResourcesScreen remembers the originating template URI when the user reads from a template form (originatingTemplateUri); closing the preview restores the template form. Plain-resource reads (sidebar selection) just empty the selection and return to the empty state. Picking a different template / resource from the sidebar clears the back-trail. - PromptsScreen flips submittedFor back to undefined when the closed prompt has arguments — the argument form re-renders with the user's values preserved so they can edit and re-submit. No-arg prompts have nothing to fall back to, so the selection is dropped and the empty state appears. The X button is hidden when the host omits onClose, so callers that don't want a dismiss control keep their existing rendering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add close button to pending and error preview states The OK-state preview already had its X button (inside the panel component itself). Pending and error states were inline cards in renderReadState / renderPreview with no way to dismiss them, so a user who submitted bad input to a resource template or prompt was stuck staring at the error until they picked a different sidebar item. Adds a top-left CloseButton row to both states on both screens, wired to the same handleClosePreview handler — so the template form or argument form re-appears on close just like it does from the OK state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fire prompt completions on focus and send every sibling in context Two fixes to the PromptArgumentsForm autocomplete: - Focusing an argument input now fires completion/complete immediately (handleFocus) — the dropdown is populated as soon as the user clicks in, not only after they start typing. Any in-flight debounce timer for the same arg is cancelled so a stale keystroke request can't overwrite the fresh focus response. - The completion context now includes every declared prompt argument (with "" for ones the user hasn't typed into yet), minus the one being completed. Previously the context only carried args the user had touched, so servers that disambiguate based on co-arguments couldn't see the full picture on the first keystroke. Tests cover both: a focus-only path that fires before any keystroke, and a typing path that asserts the empty sibling is sent through. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fire resource-template completions on focus Mirrors the prompt-side change: focusing a template variable input now fires completion/complete immediately so the dropdown populates the moment the user clicks in, rather than waiting for the first keystroke + 300ms debounce. Any pending debounce timer for the same variable is cancelled first so a stale keystroke response can't overwrite the focus response. The sibling-context coverage was already correct here — `variables` is seeded with empty strings for every declared template variable at mount and on template switch, so the context payload always carries the full variable set minus the one being completed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Style Autocomplete dropdowns to match the rest of the app Two fixes so the completion dropdowns on prompt-argument and resource-template inputs look like every other dropdown in the app: - App.css: the dark-mode dropdown / option-hover rules used to target .mantine-Select-* only, so the Autocomplete dropdowns shipped with Mantine's default surface color and a different hover background. Re-scoped to .mantine-Combobox-* so every Combobox-built input (Select, Autocomplete, MultiSelect, …) inherits the same styling. - theme/Autocomplete.ts: new theme module setting `radius: "md"` so Autocomplete matches the default Select / TextInput corner radius. Wired through theme/index.ts and theme/theme.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Fix dropdown styling — Select and Autocomplete need separate selectors The previous commit re-scoped the dark-mode dropdown rules from `.mantine-Select-*` to `.mantine-Combobox-*` on the assumption that both Select and Autocomplete share a Combobox class on the dropdown root. They don't — Mantine's Combobox factory takes `__staticSelector` from the parent component, so Select emits `mantine-Select-dropdown` and Autocomplete emits `mantine-Autocomplete-dropdown` with no shared `Combobox` class on that element. The `.mantine-Combobox-*` selector matched nothing, so the TaskControls filter (and every other Select) lost its gray surface and reverted to Mantine's default brown-ish dark background. List both selectors so Select dropdowns keep their styling and Autocomplete dropdowns pick it up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address review feedback: canSubmit, fire-time context, re-click guard - PromptArgumentsForm: add a canSubmit guard that disables Get Prompt until every required argument has a value, matching the resource template form's symmetry. Optional args may stay blank. - Both forms: capture sibling context inside the debounce callback, not at schedule time. Hold the latest argumentValues / variables in a ref + sync via useEffect, and call buildContext() at fire time. Without this, typing in arg A then arg B within the 300ms window would ship A's request with B's pre-keystroke value. - PromptsScreen.handleSelectPrompt: early-return when the user re-clicks the already-selected prompt — sidebar is for navigation, ✕ is for dismiss. Previously a re-click wiped form values and re-fired the auto-fetch for no-arg prompts. - MessageBubble: expand the comment on effectiveMimeForBlock to flag the unconditional markdown promotion as a known asymmetry with the resource side (which only promotes when the server signals it), pending a per-block mimeType in the spec. - New tests: canSubmit disabled / enabled transitions, fire-time context capture across siblings, abort-path verification (a stale in-flight response must not overwrite the fresh one), and the no-op re-click guard on PromptsScreen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address follow-up review nits: ghost suggestions, dead previewActive branch - Clear `completions[name]` at the top of handleChange in both PromptArgumentsForm and ResourceTemplatePanel so the dropdown doesn't show stale options from the previous prefix during the 300ms debounce + network window. - Simplify previewActive in PromptsScreen to drop the unreachable `!getPromptState?.promptName` fallback. App.tsx tags every prompt state transition with `promptName`, so the fallback was dead code that obscured the actual runtime invariant. - Add a focused test for each form that asserts stale dropdown options vanish the instant a new keystroke arrives. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6678313373 |
Expose live resource subscriptions via a hook (#1325) (#1326)
* Expose live resource subscriptions via a hook (#1325) Add ResourceSubscriptionsState that mirrors InspectorClient's subscribed URIs as InspectorResourceSubscription[], resolving each URI against the managed resources list (so the subscription tile shows server-supplied name/title) and stamping lastUpdated on notifications/resources/updated. Pair it with useResourceSubscriptions and wire App.tsx so the Resources screen reflects subscribe/unsubscribe actions in real time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Address review notes on resourceSubscriptionsState - Soften the onResourceUpdated comment: the client's dispatch is already guarded by subscribedResources.has(uri), so the re-check is true defense-in-depth rather than guarding a known hazard. - Use this.getSubscriptions() in the statusChange handler so every emit goes through the defensive-copy path. - Document the deliberate fallback to a synthetic Resource when a previously-listed resource is removed from the managed list while the user is still subscribed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Reflect subscribed state in preview + compact subscribed tile labels - Wire `isSubscribed` on the ReadResourceState passed to ResourcesScreen by deriving it from the live subscriptions list in App.tsx. The ResourcePreviewPanel's SubscribeButton already flips its label to "Unsubscribe" when subscribed; without this derivation isSubscribed was always false and the button looked stuck on "Subscribe". - In ResourceSubscribedItem, display only the last URI path segment (truncated with ellipsis if it still overflows) and surface the full URI via a hover tooltip. Keeps the Subscriptions pleat readable when resources have long path-style URIs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Auto-read resource on sidebar click Clicking a resource in the URIs accordion now triggers onReadResource in addition to setting the selection, so the preview panel jumps straight to the pending → ok render path. Removes the unreachable "Click to read this resource" placeholder, since the loader / preview / error states always cover the rendered output for a selected resource. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Render markdown resources via react-markdown + fix preview overflow - Add react-markdown / remark-gfm dependencies. - ContentViewer accepts an optional mimeType prop; when it matches text/markdown (or text/x-markdown), text blocks render via react-markdown wrapped in a `.markdown-content` container. Non-markdown text now uses the existing Code "wrapping" variant so long lines stay inside the panel. - ResourcePreviewPanel infers `text/markdown` from a `.md` / `.markdown` URI suffix when the server didn't supply a mimeType, and threads the effective mime through to ContentViewer per content item. - ResourcesScreen flex children get miw=0 so a long unwrappable line in the resource body can no longer push the preview past the viewport's right edge. - App.css gains a `.markdown-content` ruleset constraining nested pre, table, code, and img elements to the container's width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Contain resource preview: sticky header/footer, scrollable body Restructure ResourcePreviewPanel into a fixed-height column with the resource title + URI pinned to the top and the timestamp / annotations / subscribe-refresh actions pinned to the bottom. The content viewer area in the middle now owns its own ScrollArea, so a long markdown body scrolls within the panel instead of pushing the subscribe button below the viewport. In ResourcesScreen, the selectedResource branch (and the template branch's right pane) now hosts the panel inside a PreviewPane Flex column with the screen's max-height, and renderReadState wraps in a FillDetailCard sized to fill that column. The legacy outer ScrollArea.Autosize is removed for these panes since scrolling is internal now. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Auto-hide template after read; size preview to content with cap - After the user clicks Read Resource on the template form, handleReadResource now clears selectedTemplateUri so the screen swaps from the template form to the resource preview. Previously both panels stayed mounted side-by-side. - The preview no longer hard-fills the viewport. The Card uses a new "preview" theme variant (overflow: hidden) and is content-sized, capped at SCROLL_MAX_HEIGHT. ResourcePreviewPanel's flex column marks the header / meta / footer rows as `flex: 0 0 auto` and the ContentScroll as `flex: 0 1 auto` with `mih: 0`, so: - short content → card hugs it, footer sits right under the body - long content → card caps at viewport, ContentScroll shrinks and scrolls internally, footer stays pinned at the cap - ScrollArea / Group imports in ResourcesScreen pruned to match the simpler single-pane layout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Cap template form panel at 40% width A bare text input + Read Resource button stretched across the whole main content area looks awkward on wide displays. Apply maw=40% to the PreviewPane in the template branch so the form keeps a reasonable form-field width regardless of viewport. The preview branch is unaffected — resource bodies still get the full main area for content. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Live completion/complete suggestions on resource template inputs Wires the server's `completions` capability through to the resource template form so each variable's input becomes an Autocomplete that fires `completion/complete` (debounced, 300ms) on every keystroke and surfaces the returned values as a dropdown — mirroring v1's behavior. - core/mcp/inspectorClientProtocol.ts: surface getCompletions on the protocol so non-runtime callers (state managers, hooks, tests) can depend on it. FakeInspectorClient gains a vi.fn-backed stub. - ResourceTemplatePanel: accepts onCompleteArgument + completionsSupported. When both are present it renders Mantine Autocomplete instead of TextInput, debounces keystrokes via per-arg timers, aborts in-flight requests on the next keystroke, and disables client-side filtering (the server already filtered for the typed prefix). - ResourcesScreen / InspectorView: thread the props through; the screen-level callback re-injects the active template's URI as the `ref: "ref/resource"` so the panel-level callback stays ref-free. - App.tsx: wires onCompleteArgument to inspectorClient.getCompletions and derives completionsSupported from `capabilities?.completions`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
658ef86944 |
Phase 1: Style refinements and component tweaks from Storybook review
Theme & styling:
- Add custom "inspector" color palette with dark navy primary
- Change primaryShade to { light: 7, dark: 8 }
- Add Input theme override to apply --inspector-input-background globally
- Add --inspector-input-background token (white in light, dark-9 in dark)
- Override --mantine-color-body in dark mode via cssVariablesResolver
in both main.tsx and Storybook preview.tsx
- Add dark-mode CSS overrides for Select dropdown and option hover
- Add dark-mode CSS override for Accordion control hover
- Rename CSS variable --inspector-status-failed → --inspector-status-error
Component changes:
- CopyButton: increase icon font size to 24px, use --inspector-text-primary
for high-contrast color (black/white per theme)
- TaskControls: remove onClearHistory (moved to TaskListPanel), fix Select
clear button not disappearing by passing null instead of undefined
- TaskListPanel: add onClearCompleted callback, render "Clear" button
next to Completed section header
- HistoryListPanel: move "Clear" button from toolbar to History section
header, rename from "Clear All"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
||
|
|
4f37321e2c |
Move ServerCard dimmed style to Card theme variant
Replaces inline style and opacity prop on ServerCard with a new 'disabled' variant on the Card theme, per AGENTS.md rules against inline styles on components. Also picks up prettier formatting on ResourcesScreen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
a0e763a315 |
Redesign Resources screen: template panel, subscriptions, hover states, and callback architecture
- Rename ResourceTemplateInput to ResourceTemplatePanel with panel layout (title header, URI with live variable preview, labeled inputs, Read Resource button) - ResourceTemplatePanel manages its own variable state via useState; single onReadResource(uri) callback returns fully resolved URI - Extract ResourceSubscribedItem group component with name, timestamp, and subtle Unsubscribe button - Add hover state to listItem variant via theme classNames + App.css pseudo-selector - Add Refresh button to ResourcePreviewPanel - Refactor ResourcesScreen callbacks: onSelectUri, onSelectTemplate, onReadResource, onSubscribeResource, onUnsubscribeResource replace per-item onClick/onUnsubscribe props; screen wires callbacks to children - Define data-only types (ResourceItem, TemplateListItem, SubscriptionItem, SelectedResource, SelectedTemplate) separating data from callbacks - Extract named .withProps() subcomponents (FooterRow, AnnotationGroup, ActionGroup, Spacer) for AGENTS.md compliance - Update all stories (ResourceTemplatePanel, ResourcesScreen, ConnectedView) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
961dc283fc |
Add new components, typography, badge contrast, and screen layout refactors
New element components: - ListToggle: expand/collapse button using react-icons (RiExpandVerticalLine/RiCollapseVerticalLine) - LogLevelBadge: extracted from LogEntry, handles all 8 log levels with filled variant and dark/light text contrast New group components: - ViewHeader: shared header bar for ConnectedView and UnconnectedView with discriminated union on `connected` prop; MCP logo links to modelcontextprotocol.io - ServerListControls: combines ListToggle and AddServerMenu, hides toggle when no servers - LogStreamPanel: extracted from LoggingScreen with scroll containment, filtering by visible levels and search text - HistoryControls: vertical sidebar with search, method filter, export, and clear - HistoryListPanel: extracted from HistoryScreen with ListToggle compact mode, filtering by method and search text Badge contrast improvements: - AnnotationBadge: filled variant for both dark/light modes; black text in dark, white text in light - LogLevelBadge: same contrast approach; bold weight for alert/emergency levels - Badge theme default weight reduced from 700 to 600 Typography: - Added Fredoka (300-700) as body and heading font - Added Roboto Mono as monospace font - Google Fonts loaded in index.html and Storybook previewHead - Heading weight set to 600 Screen layout refactors: - LoggingScreen: owns filterText and visibleLevels state; passes to LogControls and LogStreamPanel; removed controls prop in favor of flat props - HistoryScreen: owns searchText and methodFilter state; passes to HistoryControls and HistoryListPanel for filtering - Both screens use new ScreenLayout (Flex variant="screen") and Sidebar subcomponents - Added Flex theme with "screen" variant (overflow: hidden) - Added Paper theme "panel" variant (flex column, overflow hidden) Component enhancements: - ServerCard: added compact prop showing only header row - HistoryEntry: renamed isExpanded to isListExpanded; internal useState syncs via useEffect; individual entries toggle independently - LogControls: Select All / Deselect All toggle for level checkboxes; nowrap on level selector group - ConnectionToggle: LoadingOverlay with rounded corners for connecting state - ConnectedView: ThemeToggle ActionIcon matched to DisconnectButton height (36px) and border color (outline variant) - Storybook preview: fullscreen layout wrapper with 100vh overflow hidden New stories: - UnconnectedView: Connecting and ManyServers (12 servers for scroll testing) - ServerCard: CompactConnected and CompactDisconnected - All new components have corresponding stories Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
27186c2ff7 |
refactor molecule components to use .withProps() subcomponents and extract inline code
- Add UnstyledButton listItem theme variant to replace inline style for border-radius - Extract subcomponent constants with .withProps() across all molecules - Replace raw <span /> with Mantine <Flex /> in ResourcePreviewPanel - Extract inline template literals and expressions to named functions Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
f674ce0c22 |
refine UI layout, theming, and component consistency across screens
- Add MCP logo (light/dark variants) to ConnectedLayout header - Replace Tabs with SegmentedControl in header, split into 1/3 sections - Wrap all screens in Container for consistent margins - Use Card instead of Paper for panel backgrounds across all screens - Add SubscribeButton atom, CopyButton for resource URI - Move annotations from ResourceListItem to ResourcePreviewPanel - Improve AnnotationBadge colors for dark mode visibility - Add Tools, Resources, and Prompts headers to list panels - Add ConnectedLayout stories for Tools, Resources, and Prompts tabs - Remove unused onCopy prop and copy button from PromptMessagesDisplay Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
08c42a2e62 |
fix dark/light mode surface colors and Remove button color
- Fix ContentViewer background invisible against card in dark mode - Fix Remove button not red in dark mode by using color="red.6" - Add distinct card background for light mode (gray-0) vs app background - Use CSS custom properties for card background across color schemes Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
3c25bdfbd5 |
refine InlineError, ServerCard, HistoryEntry, and LoggingScreen
- InlineError: show more/less toggle on same line as message, collapsible details, doc link as button - ServerCard: use ContentViewer with CopyButton for command, remove onCopyCommand prop, combine action rows, add responsive Card variant - HistoryEntry: use ContentViewer with CopyButton for JSON blocks, swap duration and status badge positions - LoggingScreen: stretch log stream panel to full height, center empty message vertically Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
1da76c53ef |
storybook component fixes and improvements
- Add @storybook/addon-actions for action logging in stories - Add onTestCapability callback to ExperimentalFeaturesPanel - Use CopyButton overlay in ResourcePreviewPanel and ResultPanel - Replace includeContext checkbox with badge in SamplingRequestPanel - Add LongServerName story for ServerCard - Fix long tool name overflow with truncation and contained Paper variant - Add optional title prop to ToolListItem and ToolDetailPanel - Move tool annotations from list to detail panel form - Vertically center static text in ResourceTemplateInput - Fix InlineError blank space when no details Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
4a4818e933 |
fix InlineError blank space and ElicitationUrlPanel URL overflow
Only render InlineError details Stack when there is content to show. Add wrapping Code theme variant for long URLs that would push buttons offscreen. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
||
|
|
4b28a69856 | formatting | ||
|
|
39ef184b53 |
feat: Add Mantine theme, 12 atom components, and 25 molecule components with Storybook stories
- Mantine theme with component extensions (Badge, Card, Paper, Button, etc.) - CSS custom properties for design tokens (--inspector-* vars) - Dark/light mode support with auto detection - 12 atoms: StatusIndicator, AnnotationBadge, CapabilityItem, LogEntry, ProgressDisplay, ContentViewer, ListChangedIndicator, CopyButton, ConnectionToggle, TransportBadge, InlineError, MessageBubble - 25 molecules: ServerCard, AddServerMenu, SchemaForm, ToolListItem, ToolDetailPanel, ResultPanel, ResourceListItem, ResourcePreviewPanel, ResourceTemplateInput, PromptArgumentsForm, PromptMessagesDisplay, LogControls, TaskCard, HistoryEntry, ServerInfoContent, ServerSettingsForm, RootsTable, SamplingRequestPanel, ElicitationFormPanel, ElicitationUrlPanel, InlineSamplingRequest, InlineElicitationRequest, PendingClientRequests, ImportServerJsonPanel, ExperimentalFeaturesPanel - Exclude storybook-static from version control - Exclude .stories.tsx from app tsconfig (Storybook-only modules) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> |
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04b5e70a53 | ||
|
|
de7795ca54 | ||
|
|
bc408f5869 | ||
|
|
4defa32ac6 |
||
|
|
092165476e |
||
|
|
8d33e76f97 |
||
|
|
3d75ef5f8e |
||
|
|
dbda70e83b |
||
|
|
5d024ab54f | ||
|
|
650e5bd020 | ||
|
|
ace4250d31 | ||
|
|
c6999dff96 | ||
|
|
219298b24d | ||
|
|
3550b64032 | ||
|
|
74c6491b52 | ||
|
|
13dafe457b |
||
|
|
0b0bd40e5d |
||
|
|
d04a7fb9c8 |
||
|
|
bb32d1db5f |
||
|
|
ef921ba052 |
||
|
|
d525fc9f54 |
||
|
|
d90cc4c34b |
||
|
|
a10ced988c |
||
|
|
0890c7509a |
||
|
|
121f875a0e |
||
|
|
e3b0b6a908 |
||
|
|
4641339cc3 |
||
|
|
8d6dea0af2 |
||
|
|
6678313373 |
||
|
|
658ef86944 | ||
|
|
4f37321e2c | ||
|
|
a0e763a315 | ||
|
|
961dc283fc | ||
|
|
27186c2ff7 | ||
|
|
f674ce0c22 | ||
|
|
08c42a2e62 | ||
|
|
3c25bdfbd5 | ||
|
|
1da76c53ef | ||
|
|
4a4818e933 | ||
|
|
4b28a69856 | ||
|
|
39ef184b53 |