* 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>
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
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
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>
* 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>
* 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>