Skip to content

feat(web): list and terminate active backend sessions - #2901

Merged
cliffhall merged 8 commits into
v2/mainfrom
v2/feat/2831-web-session-list
Nov 5, 2026
Merged

cliffhall merged 8 commits into
v2/mainfrom
v2/feat/2831-web-session-list

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #2831

What

A web-backend operator view of every live MCP session, so a session whose tab went away (and the stdio child behind it) can be found and ended without restarting the Inspector.

Backend (core/mcp/remote/node)

  • GET /api/mcp/sessions → { sessions: RemoteSessionSummary[] }, oldest first: sessionId, serverName, transport, createdAt, consumerAttached, lastConsumerSeenAt (now while attached, null if never), queuedEvents, transportDead.
  • DELETE /api/mcp/sessions/:sessionId: 404 for an unknown session. Otherwise it forgets the session, cancels its detach grace, pushes a transport_error naming why to a still-attached tab, rejects pending request waits, and closes the transport, which ends a stdio child. The order matches the Web: closing or reloading a connected tab leaks the backend session and its spawned stdio server process #2824 detach-grace reap.
  • Both routes sit behind the existing app-wide x-mcp-remote-auth middleware (a test asserts 401 without the token).
  • RemoteSession records a descriptor (server name, transport), createdAt, and the last attach/detach time, and exposes describe().

Client plumbing

  • RemoteConnectRequest.serverName is an optional display label. The web client passes server.name through createWebEnvironment → createRemoteTransport → the connect body. The backend keeps only a non-blank string, trimmed and capped at 256 chars, and never acts on it.

Web UI

  • A new Active sessions header button (next to Client settings) opens an ActiveSessionsModal with one row per session: server, transport, status (This tab / Attached / Detached / Closed), ages, queued events, and Terminate. It has a refresh button and shows list or terminate errors.
  • useActiveSessions fetches on open, refresh and after a terminate, never from an effect. It drops out-of-order responses.
  • lib/activeSessions.ts holds the two calls. utils/sessionAge.ts holds the age formatter (reusing core's formatDuration).

Screenshots (1280×900, prod build, real backend)

Sessions were seeded through the API: one from a tab that went away, one from another attached tab, one whose browser never opened its stream, and one crashed server. The page itself is connected via deep link ("This tab").

Header button:
header

Active Sessions modal:
modal

After terminating "everything (other tab)":
after terminate

Tests

  • active-sessions-route.test.ts (integration, real stdio child reporting its pid): auth required; empty list; descriptor and attach state; 404; terminating a detached session kills the child and drops it from the list; an attached tab receives the termination transport_error; name sanitization.
  • remote-session.test.ts: describe() and last-consumer bookkeeping under fake timers.
  • Unit: activeSessions, useActiveSessions, sessionAge, ActiveSessionsModal (plus stories), the ViewHeader button, and serverName reaching the connect body (remoteClientTransport, environmentFactory).

Gate

npm run local:gate passed every stage through smoke:web:firefox. The final local:storybook stage then flagged an axe empty-table-header on the actions column. The fix (a VisuallyHidden "Actions" label) is the last commit; that story file now passes, and lint, typecheck and unit tests were re-run.

🤖 Generated with Claude Code

@cliffhall cliffhall added the v2 Issues and PRs for v2 label Nov 5, 2026
@cliffhall cliffhall linked an issue Nov 5, 2026 that may be closed by this pull request
2 tasks done
@cliffhall
cliffhall requested a balanced review from Copilot November 5, 2026 06:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Terminating idle attached sessions leaves event streams open and retains connections and session references, while overlapping refreshes can erase termination errors.

2 open issues
What changed in this PR

Adds a web operator view for finding and terminating backend MCP sessions without restarting Inspector, complementing the detach-grace cleanup from #2824.

Changes:

  • Adds authenticated session-list and termination endpoints with session metadata.
  • Passes server display names to the backend and adds an Active Sessions modal with refresh and termination controls.
  • Adds unit, integration, and Storybook coverage for the new behavior.
File Description
core/​mcp/​remote/​types.ts Defines session summaries and optional server labels.
core/​mcp/​remote/​remoteClientTransport.ts Sends server labels when connecting.
core/​mcp/​remote/​node/​server.ts Adds listing, termination, and label sanitization.
core/​mcp/​remote/​node/​remote-session.ts Tracks session metadata and consumer timestamps.
core/​mcp/​remote/​index.ts Exports session-list types.
core/​mcp/​remote/​createRemoteTransport.ts Forwards server labels to transports.
clients/​web/​src/​utils/​sessionAge.ts Formats session ages.
clients/​web/​src/​utils/​sessionAge.test.ts Tests age formatting.
clients/​web/​src/​test/​integration/​mcp/​remote/​remote-session.test.ts Tests summaries and consumer bookkeeping.
clients/​web/​src/​test/​integration/​mcp/​remote/​active-sessions-route.test.ts Tests authentication, listing, and process termination.
clients/​web/​src/​test/​core/​mcp/​remote/​remoteClientTransport.test.ts Tests connect-body labels.
clients/​web/​src/​lib/​environmentFactory.ts Accepts and forwards server names.
clients/​web/​src/​lib/​environmentFactory.test.ts Tests server-name propagation.
clients/​web/​src/​lib/​activeSessions.ts Implements authenticated session API calls.
clients/​web/​src/​lib/​activeSessions.test.ts Tests requests and error handling.
clients/​web/​src/​hooks/​useConnectionLifecycle.ts Supplies the selected server’s name.
clients/​web/​src/​hooks/​useActiveSessions.ts Manages modal state and session requests.
clients/​web/​src/​hooks/​useActiveSessions.test.tsx Tests loading, termination, and stale responses.
clients/​web/​src/​components/​views/​InspectorView/​types.ts Adds the session-panel callback.
clients/​web/​src/​components/​views/​InspectorView/​InspectorView.tsx Passes the callback to both header states.
clients/​web/​src/​components/​views/​InspectorView/​InspectorView.test.tsx Updates test props.
clients/​web/​src/​components/​views/​InspectorView/​InspectorView.stories.tsx Updates story props.
clients/​web/​src/​components/​groups/​ViewHeader/​ViewHeader.tsx Adds the Active Sessions button.
clients/​web/​src/​components/​groups/​ViewHeader/​ViewHeader.test.tsx Tests the new button callback.
clients/​web/​src/​components/​groups/​ViewHeader/​ViewHeader.stories.tsx Wires the callback into header stories.
clients/​web/​src/​components/​groups/​ActiveSessionsModal/​ActiveSessionsModal.tsx Displays session details and controls.
clients/​web/​src/​components/​groups/​ActiveSessionsModal/​ActiveSessionsModal.test.tsx Tests modal states and interactions.
clients/​web/​src/​components/​groups/​ActiveSessionsModal/​ActiveSessionsModal.stories.tsx Demonstrates populated, empty, and error states.
clients/​web/​src/​App.tsx Connects the hook, modal, and current-session marker.

Review effort: Balanced

Comment thread core/mcp/remote/node/server.ts Resolved
cliffhall added a commit that referenced this pull request Nov 5, 2026
…visible

Address Copilot review on #2901: terminating an attached session now
flushes the termination event and closes its /api/mcp/events response
instead of holding it until the browser aborts, and a failed terminate
invalidates any in-flight list so it cannot clear the error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 1: 2 findings, both defects in this PR, both fixed in ef2aa51 (replies are in each thread).

  • Terminate leaves attached event streams open (High): the backend now flushes the termination event and closes that session's /api/mcp/events response itself.
  • In-flight refresh can clear termination errors (Medium): a failed terminate invalidates any outstanding list request.

npm run local:gate is green on the new head. Requesting round 2.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Client aborts leave termination listeners registered, retaining completed streams and cleanup closures across reattachments; listener cleanup and regression coverage are needed.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread core/mcp/remote/node/server.ts Resolved
cliffhall added a commit that referenced this pull request Nov 5, 2026
…visible

Address Copilot review on #2901: terminating an attached session now
flushes the termination event and closes its /api/mcp/events response
instead of holding it until the browser aborts, and a failed terminate
invalidates any in-flight list so it cannot clear the error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
cliffhall added a commit that referenced this pull request Nov 5, 2026
Address Copilot round 2 on #2901: a client abort now removes the stream's
listener from the session's terminate signal, so reattaching tabs do not
accumulate finished streams on a long-lived session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall
cliffhall force-pushed the v2/feat/2831-web-session-list branch from ef2aa51 to 54d544d Compare November 5, 2026 07:57
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 2: 1 finding (Remove termination listener when the event stream aborts, Medium), a leak introduced by the round-1 fix. Fixed in 54d544d with a regression test; the reply is in its thread. Rebased onto current v2/main; npm run local:gate is green. Requesting round 3.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical abort-handling bug can skip stream closure and leave released unresolved when the client disconnects during termination’s pending flush, keeping the handler blocked on the write.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread core/mcp/remote/node/server.ts Resolved
cliffhall added a commit that referenced this pull request Nov 5, 2026
Address Copilot round 3 on #2901: closing and releasing the stream is
now its own exactly-once step, so a client abort that lands while a
terminate is still flushing releases the handler instead of leaving it
waiting on a write to a departed peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 3: 1 finding (client abort during a terminate's pending flush left the handler unreleased), a defect in this PR's end path. Fixed in 68c7288 with a regression test; the reply is in its thread. npm run local:gate is green. Requesting round 4.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Human assessment is needed because chunk-boundary assumptions in the SSE test helper can cause false failures or checks against the wrong process.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

cliffhall and others added 8 commits November 5, 2026 03:35
Add GET /api/mcp/sessions (server name, transport, created, last consumer
seen, queued events) and DELETE /api/mcp/sessions/:sessionId, both behind
the existing x-mcp-remote-auth middleware. Terminating closes the
session's transport (ending a stdio child) and tells an attached tab why.
The web client labels each session with its server name on connect and
adds an Active Sessions modal, opened from the header.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
…visible

Address Copilot review on #2901: terminating an attached session now
flushes the termination event and closes its /api/mcp/events response
instead of holding it until the browser aborts, and a failed terminate
invalidates any in-flight list so it cannot clear the error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Address Copilot round 2 on #2901: a client abort now removes the stream's
listener from the session's terminate signal, so reattaching tabs do not
accumulate finished streams on a long-lived session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Address Copilot round 3 on #2901: closing and releasing the stream is
now its own exactly-once step, so a client abort that lands while a
terminate is still flushing releases the handler instead of leaving it
waiting on a write to a departed peer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Address Copilot round 4 on #2901: the pid match is anchored on the
JSON string's closing quote so a pid split across chunks is never read
short, and the terminate test waits for the termination message itself
rather than an event line that can precede its data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
@cliffhall

Copy link
Copy Markdown
Member Author

Copilot round 4: no inline comments, but the headline raised one point: chunk-boundary assumptions in the SSE test helper. That is a real defect in this PR's test, so it is fixed in 732f7c9. The pid match is now anchored on the JSON string's closing quote, so a pid split across chunks cannot be read short, and the terminate test waits for the termination message itself rather than the event: line that can arrive ahead of its data:. Rebased onto current v2/main; npm run local:gate is green. Requesting round 5.

@cliffhall
cliffhall force-pushed the v2/feat/2831-web-session-list branch from 68c7288 to 732f7c9 Compare November 5, 2026 08:48
@cliffhall
cliffhall requested a balanced review from Copilot November 5, 2026 08:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues are blocking the authenticated session-management changes.

No open issues 🎉

Review effort: Balanced

@cliffhall

Copy link
Copy Markdown
Member Author

Copilot review closed: round 5 was clean (no inline comments, no headline findings, nothing suppressed; "Approval recommended"). Every finding across rounds 1–4 was in scope and fixed, with a reply in its thread.

@cliffhall
cliffhall merged commit c1dcd43 into v2/main Nov 5, 2026
12 checks passed
@cliffhall
cliffhall deleted the v2/feat/2831-web-session-list branch November 5, 2026 09:03
cliffhall added a commit to modelcontextprotocol/modelcontextprotocol that referenced this pull request Nov 5, 2026
- web: an Active sessions section with three screenshots from
  modelcontextprotocol/inspector#2901. It covers the header button, what
  each column and status means, Terminate, the 60-second detach grace that
  lets a session outlive its tab, and the GET/DELETE /api/mcp/sessions
  routes behind the session token.
- load-testing: a Multiple generator processes section for the CLI's
  --load-workers (modelcontextprotocol/inspector#2914), which merged after
  the previous revision's source snapshot. It also adds the topic-table
  row, the summary's workers field, and why mcpdo has no equivalent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: cliffhall <cliff@futurescale.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web backend: list and terminate active sessions

2 participants