* Logging: per-request log level on modern servers (era fork) (#1629)
On modern (2026-07-28) servers `logging/setLevel` and the session-scoped
level are gone: the client opts into logs per request via the
`io.modelcontextprotocol/logLevel` `_meta` key, and the SDK does not
attach it automatically, so server logs are silently absent until stamped.
Core (`InspectorClient`):
- `setModernLogLevel(level | undefined)` / `getModernLogLevel()` store the
opt-in level; `mergeMeta` stamps it on every outgoing request, gated on
the negotiated modern era so legacy connections are untouched.
- Cleared on disconnect so it can't leak into the next connection.
Web UI:
- `LogControls` forks on `protocolEra`: legacy keeps the `logging/setLevel`
selector + Set button; modern shows a per-request opt-in Select (Off +
levels, applied immediately) with copy explaining logs arrive on the
originating request's stream.
- Threaded `protocolEra` / `modernLogLevel` / `onSetModernLogLevel` through
LoggingScreen and InspectorView from App.
Tests: unit coverage for the modern LogControls/LoggingScreen fork;
integration coverage proving the `logLevel` `_meta` is stamped on a real
modern server once opted in (and not on legacy).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* test-servers: logging era-fork showcase configs + proof screenshots (#1629)
Add two manual-testing configs and README docs for the logging era fork:
- logging-legacy-http.json: plain streamable-HTTP server with `logging: true`
and a `send_notification` tool. The Logs tab shows the legacy session-scoped
"Set Active Level" selector + Set button; calling the tool streams logs.
- logging-modern-http.json: same, with `transport.modern: true`. Connect with
Protocol Era = Modern and the Logs tab shows the per-request opt-in control;
the client stamps `_meta["io.modelcontextprotocol/logLevel"]` on every
request (visible in the Network tab request body).
pr-screenshots/ documents the verified behavior end-to-end against both real
servers: the legacy control with logs streaming, the modern per-request
control, and the modern tools/call request body carrying the stamped logLevel
_meta.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* test-servers: send_notification emits via per-request notifier so modern logs stream (#1629)
The `send_notification` tool sent its `notifications/message` through the
global `server.server.notification()`. On the modern (2026-07-28) stateless
leg that isn't tied to the in-flight request, so the SDK answered the tool
call with plain JSON and dropped the log — nothing reached the Inspector's
Logs panel.
Prefer the per-request `extra.sendNotification` (rebuilt from
`ctx.mcpReq.notify`): scoped to the current request, it makes the SDK upgrade
the response to SSE and stream the log back on the originating request's
stream. Falls back to the global notifier (legacy path / no per-request
context). Verified end-to-end: opting in via the Logs-tab "Log Level per
Request" control and calling send_notification now streams the entry on the
modern server; legacy behavior unchanged (server-notification integration
tests green).
README documents the modern logging showcase and the caveat that this test
server streams whenever a tool emits — the SDK beta strips the per-request
envelope from the handler context, so the tool can't gate on the client's
logLevel opt-in (the client-side `_meta` stamp #1629 owns is still fully
visible in the Network tab).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* test-servers: send_notification uses SDK request-scoped log() → spec-faithful modern gating (#1629)
The previous fix routed `send_notification` through `extra.sendNotification`
(`ctx.mcpReq.notify`), which streams on the modern leg but emits
unconditionally — the log showed up even when the client hadn't opted in via
the per-request `logLevel`.
The SDK already provides the correct primitive: `ctx.mcpReq.log(level, data,
logger)` (added in `McpServer.buildContext`). It applies the era-correct
threshold gating itself — on the modern (2026-07-28) leg it reads the
per-request `logLevel` opt-in from the request envelope and drops the message
when the client didn't opt in or the level is below the requested severity; on
legacy it honors the session level from `logging/setLevel` — and emits through
`notify`, so the modern response still upgrades to SSE and the log rides the
originating request's stream.
Surface it as `extra.log` (`toHandlerExtra` maps `ctx.mcpReq.log`) and have
`send_notification` prefer it, falling back to the global notifier for callers
without per-request context. Verified end to end: opting in via the Logs-tab
"Log Level per Request" control stamps `logLevel` in the request `_meta` and
streams the log; setting it back to Off omits the key and the server gates the
log (plain JSON response, no notification). Legacy server-notification
integration tests still green. (No SDK change needed — the earlier apparent
gap was our test server using the wrong low-level API.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* web+core: add "Log Level per Request" as a per-server setting, defaulting to on (#1629)
Adds the modern per-request log level as a persisted Server Settings option
(Options → "Log Level per Request", below Protocol Era), so a server can opt
into logs by default instead of the Logs-tab control starting at Off.
- types.ts: `ModernLogLevel` (`"off"` | the 8 levels), `DEFAULT_MODERN_LOG_LEVEL`
= "debug" (default-on so a modern connection surfaces server logs out of the
box), `MODERN_LOG_LEVELS`, and an `isModernLogLevel` guard. Field added to
`InspectorServerSettings` and `StoredMCPServer`.
- serverList.ts: round-trip the field (lift on read, omit-on-default on write,
drop unknown literals) mirroring `protocolEra`; add to the persisted field
allowlist.
- remote/node/server.ts: validateSettings accepts/validates it and the
normalize path drops a malformed literal.
- inspectorClient.ts: the constructor seeds `modernLogLevel` from the setting
(`"off"` → not opted in), so a modern connection stamps the level from the
first request without UI interaction. mergeMeta still gates on the modern era.
- ServerSettingsForm/Modal: a Select (Off + 8 levels) with a modern-era
description. App seeds the live Logs-tab control from the setting on connect
so the two stay in sync.
Tests: form Select (default/onChange/off/reflect), modal handler, serverList
round-trips, route validation (accept/reject/drop), and constructor seeding
(seeded level stamped from the first request; "off" opts out). The prior
modern-era "stamps once set" test is updated for the new default-on behavior.
Coverage gate green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Fix CI + review: format ServerSettingsForm, drop stray network export, broaden ignore
- Prettier-format ServerSettingsForm.tsx (the format:check failure that broke
CI — my pre-push `npm run format` from the repo root was a silent no-op since
there is no root format script; formatting is per-client).
- Remove `inspector-network-logging-modern-2026-07-19T15-11-27.013Z.json`, a
Network-tab export debugging artifact accidentally committed in ca2aa526
(flagged by review).
- Broaden the .gitignore debugging-artifacts section with
`/inspector-network-*.json` so future network exports are ignored (the
existing `/inspector-history-*.json` didn't match the network export naming).
Verified with the full `npm run ci` (validate → coverage → smoke → storybook),
green across all clients.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* test: add modern-era log delivery + gating e2e tests (#1629)
The modern-era suite covered the client-side `_meta` logLevel stamp but not
that a log actually flows back — the legacy suite already has that
(`should receive server-initiated notifications via SSE/streamable-http`), so
the modern side was the gap.
Add two live e2e tests against a modern (`createMcpHandler`) server serving the
`send_notification` tool with `logging: true`:
- opted-in (server setting = a level): calling the tool delivers a
`notifications/message` over the request's SSE stream, with the right
level/logger/data.
- not opted in (setting "off"): the tool still returns a result, but the server
gates the log — no `notifications/message` is received.
This exercises the full path end to end: the client seeding + `_meta` opt-in,
and the server's `send_notification` → `ctx.mcpReq.log` era-correct gating.
Verified with the full `npm run ci` (green across all clients).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* web: hide the modern "Log Level per Request" setting on legacy connections (#1629)
The per-request log level is meaningless on a legacy server (which uses
logging/setLevel), so hide it in Server Settings when the era is legacy:
- protocolEra pinned to "legacy" → hidden (no connection needed), or
- protocolEra "auto" that negotiated legacy at connect time → hidden.
It stays visible for a pinned "modern" server, and for an "auto" server that is
either not yet connected (era unknown) or resolved to modern.
Threads the negotiated era down: App passes `negotiatedEra` to
ServerSettingsModal only when the settings target is the live-connected server
(`connectionStatus === "connected" && settingsModalTargetId === activeServerId`);
the modal forwards it to ServerSettingsForm, which gates the control on
`configuredEra === "modern" || (configuredEra === "auto" && negotiatedEra !== "legacy")`.
Tests: visibility matrix on the form (legacy hidden; auto+legacy hidden;
auto+unconnected and auto+modern and modern shown) and a modal hide case; the
existing modern-log tests now base on a modern-era settings object. Verified in
the browser (legacy hides, switching to modern reveals it reactively) and with
the full `npm run ci` (green across all clients).
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>
* Thread cacheMode through managed list refresh via SDK cache-aware verbs (#1721)
Manual/auto refresh of tools/resources/prompts now forces a cache-bypassing
round trip on modern servers, delivering the raw-request cacheMode control
deferred from #1628.
The InspectorClient list/read path uses the SDK's single-page `client.request`
verbs (for pagination debugging), which never consult the SDK response cache —
so `cacheMode` was inert there. This adds a separate aggregate path that routes
through the SDK's cache-aware high-level verbs, where `cacheMode` is honored:
- InspectorClient: new `listAllTools/Prompts/Resources/ResourceTemplates(
{ cacheMode, metadata })` that call `client.listTools()` etc. (all-page
aggregate + SEP-2243 x-mcp-header exclusion + cache consult/write). A shared
`getCacheableRequestOptions` / `aggregateListParams` back all four.
- InspectorClientProtocol: the four `listAll*` methods added.
- ManagedListState: delegates page walking to the SDK verb (config `fetchPage`
loop → `fetchAll`); `refresh(metadata?, cacheMode?)` threads cacheMode. A
`list_changed` auto-refresh bypasses the cache (`cacheMode: "refresh"`).
- Managed hooks: user-initiated `refresh()` passes `cacheMode: "refresh"`, so
the existing per-tab Refresh buttons become genuinely cache-bypassing — no
App wiring change. Connect-time load stays default (`'use'`).
The single-page `listTools(cursor)` primitive is unchanged; the paged hooks
still surface per-page `nextCursor`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Single-page list pagination: sidebar toggle + server setting (#1721)
Delivers the deferred single-page fetch UI: Tools/Resources/Prompts can be
loaded one page at a time instead of auto-aggregating every page — a defensive
option for servers with very large lists.
- New `singlePageLists` server setting (persisted, server-wide), plumbed through
ServerConfig/InspectorServerSettings, serverList convert/default, the node +
remote settings paths, and the Server Settings form ("Fetch Lists One Page at
a Time").
- New `ListPaginationControls` element (a "Single page" switch + "Load next
page" button + "N pages loaded" status) rendered in each list sidebar; the
toggle edits the setting live (optimistic) and persists it.
- The paged state stores (PagedTools/Prompts/ResourcesState) now own loading:
they auto-load page 1 on connect in single-page mode and track nextCursor +
page count as observable state; the managed stores skip their all-page walk
in single-page mode (so nothing is over-fetched) while still lighting the
list-changed indicator. `usePaginatedList` selects the display source and
pagination controls per mode — pure, no data-loading in React effects.
- Refresh is mode-aware: reload page 1 in single-page mode, refetch the whole
aggregate otherwise. Sidebars widened 340→360 so the controls don't crowd
list entries.
- Test server: `test-servers/configs/pagination-http.json` (12 tools/resources/
prompts, page size 4 → 3 pages each) exercises the flow end to end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Add pr-screenshots proving single-page pagination (#1721)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Address @claude review: template load, double-click guard, auth recovery (#1721)
- Fix: resource templates never loaded on connect in single-page mode. The
connect-time all-page skip now applies only to lists with a paged counterpart
(new `deferInSinglePage` flag on ManagedListConfig; false for resource
templates), so templates still aggregate on connect regardless of the setting.
- Guard `PagedTools/Prompts/ResourcesState.loadPage` against a concurrent load
(fast double-click on "Load next page") so the same page can't be appended
twice; the in-flight load owns the cursor and the second call is a no-op.
- Thread ambient auth recovery through the single-page refresh, Load-next-page,
and mode-switch loads (they previously bypassed it) — usePaginatedList's
onRefresh/onLoadMore now return their promises so App can wrap them.
- Drop the misleading "aggregated list" wording from the paged-store docs.
Tests added for each; npm run coverage green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Review round 2: revert optimistic toggle when persisting the setting fails (#1721)
Non-blocking review follow-up. If updateServerSettings rejects, the optimistic
`singlePageListsOverride` is now cleared and the live client setting rolled back
(the effect only clears the override when the persisted value changes, which
doesn't happen on failure), so the UI and client reflect what's on disk rather
than the failed edit. Added an App test for the revert path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Fix single-page Refresh not clearing list-changed indicator + UI polish (#1721)
Round-3 review fix (blocking): in single-page mode a `list_changed` lights the
list-changed indicator, but the single-page Refresh reloads page 1 of the paged
store and bypassed `useManaged*.refresh()`, so the indicator was never cleared.
Expose `clearListChanged` from useManagedTools/Prompts/Resources and call it in
the single-page branch of onRefreshTools/Prompts/Resources. Tests added.
UI polish (user feedback on the sidebar control):
- Rename the switch label "Single page" → "Paginated".
- Put "Load next page" on the same row as the switch, space-between (switch
fully left, button fully right).
- Move the "N pages loaded" status to its own line below the row, centered.
- Hide the whole control once the list is known to be a single page
(single-page mode, page 1 loaded, no nextCursor) — nothing to paginate.
pr-screenshots refreshed to the new layout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Rename the singlePageLists setting to paginatedLists for clarity (#1721)
`singlePageLists` read backwards from the "Paginated" switch label (it sounds
like paginated=false). Rename the server setting to `paginatedLists` with the
same value meaning — true = paginated/one-page-at-a-time, false = aggregate-all
— and follow through on the related identifiers for consistency:
- Setting field `paginatedLists` across ServerConfig / InspectorServerSettings,
serverList convert/default, node + remote settings paths, and the form/modal
callbacks (onPaginatedListsChange).
- `usePaginatedList` / `ListPaginationControls` prop `singlePage` → `paginated`,
`onSinglePageChange` → `onPaginatedChange`.
- `ManagedListConfig.deferInSinglePage` → `deferWhenPaginated`.
- App state (paginatedListsOverride, onTogglePaginatedLists, …) and all
tests/fixtures + mode-referring comments.
"single-page" wording that denotes the SDK's one-page `client.request` verb
(inspectorClient) is intentionally left as-is. Purely a rename — no behavior
change; unit + integration + cli + tui + coverage all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Format App.test.tsx (prettier) after the paginatedLists rename (#1721)
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>
* Network: modern Mcp-* headers, new error codes, cancellation-as-abort (#1628)
Upgrade the Network tab for the modern Streamable HTTP transport
(SEP-2243 / SEP-2575):
- Recognise and highlight the mirrored headers (Mcp-Method, Mcp-Name,
Mcp-Param-*, MCP-Protocol-Version), decode =?base64?…?= sentinel values
inline, and cross-check each against the request body so a HeaderMismatch
is visible before the server rejects it.
- Show the new spec error codes distinctly: -32020 HeaderMismatch,
-32021 MissingRequiredClientCapability, -32022 UnsupportedProtocolVersion
(with the server-advertised `supported` versions), and modern -32601
MethodNotFound on HTTP 404 (distinguished from a legacy bare 404 by its
JSON-RPC body).
- Reflect cancellation as a connection abort ("Cancelled") rather than a
hard error, per SEP §7.5.
- Confirm and document browser-path header mirroring: the SDK builds the
Mcp-* headers client-side and the /api/fetch proxy forwards them verbatim
(new integration test).
New pure util `mcpNetworkHeaders.ts` holds the recognition/decode/classify
logic; new `McpErrorBadge` element renders the distinct error chips. The
optional raw-request controls (cacheMode bypass/refresh, single-page fetch)
are split to #1721 — they are cross-cutting core + feature-tab work beyond
the Network tab.
Also refreshes the AGENTS.md board Status option-ID table (the Building /
MCP Apps Extension columns were removed).
Closes#1628
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network smoke-test showcase + fix redaction clobbering JSON-RPC error codes
Adds a composable "modern-network" showcase server for exercising the #1628
Network-tab work end-to-end, and fixes a real bug the smoke test surfaced.
Bug fix (core): the fetch-tracking body redaction masked ANY field named
`code` (targeting the OAuth authorization code), including the numeric
JSON-RPC `error.code`. That destroyed the field the Network tab classifies
the modern spec errors on, so -32020/-32021/-32022/-32601 never rendered
against real traffic. Redaction now only masks STRING values of a sensitive
field (secrets are always strings; a numeric code is never one).
Test-server showcase (test-servers): `modern-network-http.json` serves a
`get_weather` tool with an `x-mcp-header: "City"` annotation plus four
`trigger_*` tools. A new `transport.modern.injectSpecErrors` flag installs a
middleware on the modern leg that answers the triggers with real HTTP status
+ JSON-RPC error bodies (400/-32020, 400/-32021, 400/-32022 w/ `supported`,
404/-32601), so the Inspector's error rendering can be driven live.
Mirroring finding (verified live, comment corrected in createRemoteFetch.ts):
the SDK builds the STANDARD mirrored headers (Mcp-Method / Mcp-Name /
MCP-Protocol-Version) unconditionally — they reach the server through the
web proxy — but gates `Mcp-Param-*` on a non-browser environment
(`detectProbeEnvironment() !== "browser"`), so the web client never emits
them. An x-mcp-header tool is therefore uncallable from the web client
against a strict modern server (it answers -32020); it works from the Node
CLI/TUI. The earlier "mirroring works through the proxy" note was corrected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network: move the spec-error badge off the crowded compact top row
In the narrow monitoring sidebar (embedded layout) the McpErrorBadge shared
the top row with the timestamp/method/category/duration/status chips, which
truncated the method and category ("PO…", "TRANSP…") and clipped the error
name. Render the badge separately from `metaBadges` and place it on the
second row, to the right of the URL, so the top row stays readable and the
error name shows in full. The full-width layout keeps it in the top-right
cluster (where there's room).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network: keep the spec error name's casing in McpErrorBadge (no uppercase)
The four spec/SDK error identifiers are PascalCase (`HeaderMismatch`,
`MissingRequiredClientCapability`, `UnsupportedProtocolVersion`,
`MethodNotFound`). Mantine's Badge uppercases label text by default, which
flattened them to a hard-to-read run-together string
("UNSUPPORTEDPROTOCOLVERSION"). Set `tt="none"` so the badge shows the
spec's own casing with word boundaries intact; short badges (POST, status)
keep the default uppercase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network: in the full-width layout, move the spec-error badge to the second row
The full-width entry kept the McpErrorBadge in the top-right cluster, where a
long spec name (e.g. "MissingRequiredClientCapability") crowded the top row
and truncated the method chips and URL. Move it to the start of the second
row (the expand-toggle row) so the top row stays readable; the toggle stays
at the end (`space-between` only when the badge is present). The compact
sidebar already places it on its second row beside the URL.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Protocol: show Replay as the right-side icon in the wide layout too
The compact (sidebar) ProtocolEntry already renders Replay as an icon next to
the pin/expand icons; the wide layout used a left-aligned "Replay" text
button instead. Make the wide layout match — Replay as the ReplayButton icon
at the start of the right-side action cluster (Replay, Pin, Expand) — so the
two layouts are consistent. Drops the now-unused SubtleButton constant.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Move the modern spec-error chip + alert from the Network tab to the Protocol tab
The Network tab is focused on the HTTP transaction; the distinct protocol-error
taxonomy (-32020/-32021/-32022/-32601) belongs with the protocol messages. So:
- ProtocolEntry now renders the McpErrorBadge chip on the top line (right after
the direction badge, both layouts) plus an explanatory alert in the expanded
detail, classified by JSON-RPC error code via `classifyProtocolSpecError`.
- NetworkEntry drops the chip + alert; it keeps the HTTP-level features (status
badge, Mcp-* header highlight/sentinel-decode, header/body mismatch marker,
cancellation-as-abort).
- The SDK delivers -32020/21/22 (HTTP 400) as JSON-RPC error responses, so they
reach the Protocol log directly. -32601 (HTTP 404) and other non-enveloped
errors are *thrown* — the request stays pending. `enrichProtocolEntries`
(App) correlates the Protocol log to the Network log by JSON-RPC id (parsed
from the transport fetch's requestBody) and folds the thrown error onto its
pending request, so it too appears in Protocol. This is a derived view; the
underlying log is not mutated.
Verified live against the modern-network showcase: both a -32020 error response
and a thrown -32601 render distinctly in the Protocol tab.
Follow-up: a "reveal in Network" link on the Protocol alert (cross-tab
scroll-to + expand of the correlated HTTP entry).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Protocol: show the spec-error chip only in the wide layout, after the method
Refine the placement of the spec-error chip added in the previous commit:
- Wide layout: move it from after the direction badge to right after the
method chip (reads as "tools/call -32020 HeaderMismatch").
- Compact sidebar row: drop the chip entirely — the two-line row stays
uncluttered and relies on its ERROR status badge. The explanatory alert
still appears in the expanded detail in both layouts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Protocol: "reveal in Network" link — jump from a spec error to its HTTP entry
Adds the cross-tab navigation that was the follow-up to relocating the
spec-error rendering. The expanded Protocol alert now shows a "View the HTTP
request in the Network tab" link when the entry has a correlated Network
request; clicking it:
- correlates the Protocol message to its transport fetch by JSON-RPC id
(`revealableMessageIds` gates the link; `correlateFetchEntry` finds the
target on click),
- clears whatever filter would hide the target (network filterText +
category, and the embedded monitorSearch),
- switches to the Network view in whichever mode is active — the pinned
column (`pinMonitor`) when the sidebar is open, or the full-screen header
tab (`onActiveTabChange`) when it's closed,
- scrolls the matching NetworkEntry into view and force-expands it.
The reveal is a one-shot: a new `revealId` threads NetworkScreen →
NetworkStreamPanel → NetworkEntry, where the targeted entry scrolls itself
into view (rAF, after `useScrollMemory`'s restore) + expands, then clears the
signal via `onRevealComplete`. Verified live in both the pinned-sidebar and
closed/full-screen layouts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Protocol: only show the "reveal in Network" link when it adds value
Gate the link on a new `httpRelevant` flag: show it only when the error is
either thrown by the SDK (its real HTTP response lives only in the Network
log — the Protocol entry is a synthetic fold) OR tied to the HTTP request
headers. A delivered, protocol-only error gets no link, since the raw HTTP
entry adds nothing over the alert.
- -32020 HeaderMismatch → link (the mirrored headers are the problem)
- -32601 MethodNotFound → link (thrown; the 404 response is only in Network)
- -32021 MissingRequiredClientCapability → no link (delivered, protocol-only)
- -32022 UnsupportedProtocolVersion → no link (supported list is in the alert)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network: cite SEP-2575 for cancellation-as-abort, not the internal doc's §7.5
The cancellation note referenced "(SEP §7.5)", but §7.5 is a section of the
internal v2_new_spec_impact.md doc, not a SEP. The behavior — closing the
Streamable HTTP request stream is the cancellation signal; notifications/cancelled
is stdio-only — is defined in SEP-2575 (stateless-mcp). Cite SEP-2575 in the
user-facing alert and the two related comments.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network: fix reveal one-shot canceling its own scroll; review doc/comment nits
Address @claude review of #1722:
- Reveal one-shot: move onRevealComplete() inside the rAF (after scrollIntoView)
so clearing revealId (which flips revealed->false and runs the effect cleanup)
can no longer race/cancel the pending scroll. Add a flip-back test.
- Comment the intentional double-mount of revealId (pinned monitor + full-screen
ScreenStage both match; clear is idempotent).
- Doc drift after the Network->Protocol relocation: McpErrorBadge + the
mcpNetworkHeaders module header now say the spec-error chip lives in Protocol.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Network: clarify redaction array-of-scalars edge + revealableIds superset
Address second @claude review of #1722 (both non-blocking polish notes):
- fetchTracking redaction: note that masking assumes a sensitive value is a
scalar or object; an array-of-scalars under a sensitive key would slip through
(no OAuth/token payload has that shape).
- revealableMessageIds: document it's a superset of the linked entries; the
spec-error/httpRelevant narrowing is applied downstream at render.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Protocol: gate modern -32601 framing to a genuine 404, not in-band 200
Address third @claude review of #1722. classifyProtocolSpecError recognised
-32601 from the code alone, so an ordinary in-band 'method not found' returned
on HTTP 200 was dressed up as the modern MethodNotFound taxonomy: the spec
badge, a 404-asserting alert, and a 'reveal in Network' link that jumped to a
plain 200 (claiming diagnostic value that wasn't there).
Gate the modern -32601 treatment on the correlated fetch actually being a 404
(mirroring classifyMcpSpecError). -32020/-32021/-32022 are SEP-reserved and
unaffected. The correlated HTTP status is threaded from InspectorView (new
correlatedFetchStatusById) through ProtocolScreen/ProtocolListPanel to
ProtocolEntry; when unknown, -32601 falls back to code-only recognition (the
thrown-404 path always carries a 404). Badge, alert copy, and link now resolve
together, so a plain in-band -32601 falls through to ordinary error rendering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Protocol: require a genuine 404 for modern -32601 (drop unknown-status fallback)
Address fourth @claude review of #1722. The prior fix only rejected -32601 when
the status was *known* and not 404; an unknown status fell back to code-only
recognition. But the thrown-404 path always carries a 404 (folded in from a 404
fetch), so the unknown-status branch is only ever reached by an ordinary in-band
-32601 with no correlated 404 — most notably a stdio connection (no HTTP at all)
— which then wrongly got the modern MethodNotFound badge and 404-asserting copy.
Drop the escape hatch: -32601 is the modern taxonomy only on httpStatus === 404.
Nothing intended is lost (the genuine case always has its 404). Update the tests
(code-only/undefined -32601 now expects null; add a stdio no-correlated-fetch
ProtocolEntry case) and mark the MethodNotFound story with correlatedHttpStatus
404.
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>
* MRTR: drive input_required manually to keep pending-request UX (#1704)
Follow-up to #1627/#1705. Instead of letting the SDK auto-fulfil the modern
(2026-07-28) MRTR retry loop, InspectorClient now drives it itself so the
Inspector keeps its pending-request UX, owns the loop, and shows era-accurate
semantics.
Core (core/mcp/inspectorClient.ts):
- Construct the SDK Client with `inputRequired: { autoFulfill: false }`
(unconditional; a no-op on legacy, which never returns input_required).
- New `requestWithInputRequired()` driver: issues the request with
`allowInputRequired` + `withInputRequired(schema)`, and while the result is
`input_required`, fulfils each embedded request through the SAME pending
sampling/elicitation plumbing, then retries the original call with
`inputResponses` + the echoed `requestState` on a fresh id, bounded by
MRTR_MAX_ROUNDS.
- Route tools/call, prompts/get, and resources/read through the driver.
- Embedded roots/list is auto-answered from configured roots (no pending UX);
decline/cancel is echoed to the server (not treated as an abort); a tool-call
abort mid-round settles the pending request and surfaces
ToolCallCancelledError.
- Extract shared `enqueuePendingSample`/`enqueuePendingElicitation` helpers used
by both the legacy inbound handlers and the driver; add a per-request `origin`
("server-request" | "input-required") to the pending wrappers.
Web UI:
- New `MrtrOriginNote` element shows an "input_required — your answer returns as
a retry" note on a modern MRTR round; legacy panels are unchanged.
- Thread `origin` App -> PendingClientRequestModal -> the sampling/elicitation
panels (and the inline panels).
Test servers: new MRTR presets (multi-round, roots, sampling, loop, edge-case
param shaping). Integration tests cover the manual round-trip (asserting the
pending UI pauses and the retry carries inputResponses + requestState on a new
id), multi-round, roots auto-answer, sampling, decline-echoed, maxRounds bound,
abort mid-round, and requestState-only/inputRequests-only param shaping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Review: clarify sampling-reject and between-rounds abort comments (#1704)
Address review nits on PR #1719 (comment-only, no behavior change):
- enqueuePendingSample docstring: sampling has no decline/cancel action; the
panel either sends a result (resolve) or Rejects (reject → fails the call).
The shared "declined/cancelled resolves" wording was accurate only for the
elicitation case.
- callToolWithRetries abort comment: note the between-rounds case — when the
abort lands while an MRTR round awaits an embedded pending request there is no
in-flight SDK request, so nothing is sent on the wire; the outcome
(ToolCallCancelledError, no failed-call record) is identical.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* test-servers: add mrtr-showcase config exercising every MRTR preset (#1704)
A single modern-era streamable-HTTP server (port 3111) bundling all six MRTR
presets — mrtr_confirm, mrtr_two_step, mrtr_sample, mrtr_roots, mrtr_edge,
mrtr_loop — for manual testing of the manual-driving pending-request UX across
single-round, multi-round, sampling, roots, param-shaping, and maxRounds paths.
Document it in the README alongside modern-mrtr-http.json.
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>
* History: MRTR conversation grouping + modern message vocabulary
Teach the Protocol/History view the modern (2026-07-28) message vocabulary,
correlating it off the frames the transport already captures — no change to how
MRTR is driven (manual driving is deferred to #1704).
- Correlation helpers in protocolUtils: extractResultType, extractRequestState,
extractSubscriptionId, isModernFrame, and groupProtocolEntries — clustering the
contiguous rounds of one logical operation (original call -> input_required ->
retried call with a new id -> final result) that share a requestState token.
- New MrtrConversation group renders that cluster as one expandable unit with a
status derived from the final round, reusing ProtocolEntry for each round.
- ProtocolEntry: resultType badge (input required / complete), a "modern" frame
badge for server/discover, subscriptions/listen, and the acknowledged
notification, and a copyable subscriptionId.
- ProtocolListPanel folds each section through the grouping pass and shows an era
badge in the header. Era is threaded from the negotiated protocolEra
(App -> InspectorView -> ProtocolScreen -> panel) and never inferred from
frames, respecting the transcript-classification caveat.
- Shared EraBadge element (+ eraUtils); ConnectionInfoContent reuses it.
- test-servers/configs/modern-mrtr-http.json serves an elicitation tool over the
modern leg for eyeballing a real MRTR round-trip.
- Pre-bundle @modelcontextprotocol/client in optimizeDeps.include so the
Storybook vitest project doesn't re-optimize mid-run once protocolUtils pulls
the client barrel into the browser graph.
Closes#1627
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* test-servers: real MRTR tool + live-verified History grouping
While driving the modern flow in the browser, the elicitation-based config
proved unable to produce MRTR: the modern (2026-07-28) SDK server rejects
`server.elicitInput` — server→client requests aren't allowed on that leg; a
tool must instead return `inputRequired(...)`. So the shipped
`modern-mrtr-http.json` (and its README claim) were wrong.
- Add a genuine MRTR tool preset (`mrtr_confirm` / `createMrtrTool`): its handler
returns `inputRequired({ inputRequests: { confirm: elicit(...) } })` on the
first call and the final result once the client echoes the answer back on the
retry.
- Teach the composable test-server to support it: the tool wrapper forwards an
`InputRequiredResult` verbatim (instead of coercing it to a CallToolResult),
and `toHandlerExtra` surfaces `inputResponses`/`requestState` so a handler can
branch between the original call and the retry.
- Point `modern-mrtr-http.json` at the new preset; correct the README to
describe the real mechanism.
- Add an integration test that drives the full round-trip through InspectorClient
(auto-fulfilling the embedded elicitation) and asserts the final result is only
reachable after the input_required round + retry — regression cover that would
have caught the original config bug.
Verified live in the browser: the History view renders the round-trip as one
grouped "MRTR conversation" (Round 1 input_required → Round 2 complete), keyed by
the real requestState, under the Modern era badge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Address review notes: dedupe status badge, unique MRTR token, import alias
Non-blocking review feedback on #1705:
- ProtocolEntry: suppress the redundant green "OK" status badge when a
`resultType` badge already conveys the outcome (a modern success shows only
`complete`/`input required`). Errors/pending have no `resultType`, so their
status badge still shows.
- createMrtrTool: mint a unique `requestState` per original call (closure
counter) so two same-action invocations don't fold into one conversation
during manual eyeballing. Real SDK tokens are already unique per operation.
- ProtocolEntry: align the `MessageEntry` import to the `@inspector/core` alias
used by its siblings (was a deep relative path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Review round 2: green `complete` badge, lighter embedded MRTR container
Non-blocking observations from the second review:
- ProtocolEntry: `resultType` "complete" now renders green (was gray) so a modern
success keeps the at-a-glance green success affordance a legacy "OK" had, now
that the redundant OK badge is suppressed. `input_required` stays yellow.
- MrtrConversation: in the narrow monitoring (embedded) column, drop the outer
container border and tighten padding — the per-round entries are already
bordered cards, so this avoids a busy border-in-border nesting. The full-width
view keeps the bordered surface that sets a conversation apart.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Review round 3: module-scope MRTR counter; revert embedded border change
- test-servers: hoist the MRTR `requestState` counter to module scope. The
modern (2026-07-28) leg is stateless — `createMcpHandler` rebuilds the server
(and every tool closure) per request — so the previous per-closure counter
reset to 0 on every request and every token ended in `:1`, defeating its
purpose. A module-level counter survives the per-request factory recreation
and increments monotonically across the process, so same-action invocations
get distinct tokens and don't fold into one conversation. (Review finding.)
- MrtrConversation: revert the embedded borderless/tighter-padding change — per
maintainer feedback from manual testing, dropping the border didn't improve
the narrow-column visual, so the group keeps its border in all layouts.
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>
* Add modern-era (2026-07-28) test-server preset (#1700)
The era-aware connection model (#1626, PR #1697) added per-server
`protocolEra` and the era-aware Connection Info UI, but no bundled test
server negotiated the modern era — the modern happy path had only
unit/Storybook fixtures, no live coverage.
Serve the modern (2026-07-28) protocol era from the HTTP test server via
the SDK's `createMcpHandler`. A `modern` flag on `ServerConfig` (and a
`transport.modern` field in the JSON config) mounts the handler with a
dual-era `legacy: "stateless"` fallback by default, or `legacy: "reject"`
for modern-only strict serving. The web-standard `fetch` handler reuses
the existing `toWebRequest`/`writeWebResponse` conversion, so no new
`@modelcontextprotocol/node` dependency is needed.
An SDK v2 client negotiating `protocolEra: "auto" | "modern"` now reaches
the modern leg end-to-end: `getProtocolEra() === "modern"`,
`getNegotiatedProtocolVersion() === "2026-07-28"`, and a populated
`getDiscoverResult()`. Verified live in the Inspector — Connection Info
shows Era Modern / Session Sessionless / the Discovery section.
- Modern serving path + `listen`/`startModernHttp` helpers in
test-server-http.ts; modern-leg teardown in stop().
- `modern` config field threaded through load-config/resolve-config with
a streamable-http-only validation guard; modern-http.json demo config.
- Integration test covering auto/modern negotiation, the dual-era legacy
fallback, a modern tools/call, and the strict `legacy: "reject"`
rejection.
Closes#1700
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5
* Review nit: gate transport.modern guard on truthiness
A falsy `transport.modern` (e.g. `false`) is a no-op that `resolveConfig`
normalizes to `undefined`, so it shouldn't trip the streamable-http-only
guard. Gate on truthiness instead of `!== undefined` so only *enabling*
modern on a non-HTTP transport is rejected.
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>
- Root README no longer links to docs/mcp-server-configuration.md (not yet
ported to v2); keep only the mcp-app-review.md pointer (review #3).
- Collapse the Local-fixture parenthetical onto one line so prettier no longer
renders '- resource preset …' as a mid-sentence bullet (review #5).
Findings #1/#2 (documents unmerged #1576/#1577/#1665 behavior) and #4 (the
web-README #deep-link-auto-connect anchor) resolve in the Wave 4 rollup, which
lands this doc together with those code PRs — the reviewer noted the same.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
Closes#1578
Adds docs/mcp-app-review.md — the CLI-first → one-shot-web recipe for automated
MCP App review: `--app-info` probe → deep-link navigate → rendered widget, plus
OAuth-gated servers (stored-auth handoff, now honoring refresh_token per #1665),
SSH port-forward (both ports), and HTTP proxy support.
Adapted from PR #1510's reference doc to only document what actually merged and
to match the current implementation:
- Correct driver contract: `data-testid="connection-status"` + `data-status`
/`data-error-message`/`data-deeplink`, `data-testid="open-app"`, and the
`data-app-status="ready"` app lifecycle (not the reference's stale selectors).
- The zero-click `autoOpen` path (#1577) alongside the click-to-open variant.
- `--use-stored-auth` now refreshes a stored refresh_token (#1665), so the
"token is not refreshed" caveat is replaced.
The per-client READMEs (web deep-link scheme + data-* contract, CLI handoff +
refresh + proxy) were updated in their own Wave 4 PRs; this adds the root README
pointer to docs/. Proxy/undici is already documented in the CLI README.
Part of the #1579 decomposition (Wave 4, docs — lands last).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
Follow-up on the re-review's two optional notes:
- Set `ENV HOME=/home/node` so the runtime-state dir (core/storage/store-io.ts
resolves it from HOME) is explicit, rather than relying on the "." → WORKDIR
fallback that happens to work because Docker doesn't set HOME on USER.
- Document that the HEALTHCHECK assumes the default --web mode; suggest
`--no-healthcheck` for --cli/--tui runs (no web server to probe).
Re-verified: build ok, HOME=/home/node, runs as node (uid 1000), `/` → 200,
health → healthy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
v1 published a container image but v2 arrived with no Dockerfile (the
client/server/cli layout is gone). Add a v2 image for the single-package /
launcher architecture and wire the release-gated GHCR publish job.
- `Dockerfile`: two-stage build — stage 1 installs + `npm pack`s the publishable
tarball; stage 2 `npm install -g`s that tarball, so the image ships the exact
same artifact as npm with a clean `mcp-inspector` bin. Defaults to `--web` on
`0.0.0.0:6274`, browser auto-open off. `.dockerignore` keeps the context lean
and forces a clean in-container install/build.
- `publish-github-container-registry` job in main.yml (ported from v1):
release-gated, multi-arch (amd64/arm64) build+push to GHCR with a provenance
attestation. Independent of the npm `publish` job.
- README: document the image + how to run it; the release section now covers
both publish jobs.
Verified locally: `docker build` succeeds, the container serves `/` (HTTP 200
with the injected token) and dispatches `--cli` / `--tui`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
Wire the v2 publish pipeline into main.yml. On a published GitHub release, a new
`publish` job runs `npm run pack:verify` (the pre-publish gate — builds, packs
the real tarball, installs it into a clean consumer, and drives the bin) then
`npm publish --access public --provenance`. This is a single `npm publish` (v2
is not an npm workspace, so no v1-style publish-all/--workspaces), with a signed
provenance attestation via GitHub OIDC (id-token: write, environment: release,
NPM_TOKEN). Add the `release: types: [published]` trigger; the existing `build`
job is unchanged and the publish job is gated on the release event.
Document the single-version release/tag flow in the root README (one `npm
version` at the root; no per-client sync / check-version since only the root
package.json carries a version).
Docker/GHCR is deferred: v2 has no Dockerfile (the v1 client/server/cli layout
is gone), and a correct image needs a net-new, docker-build-tested Dockerfile.
Split into #1646.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
The Inspector ships as one package with one version, but four client
package.jsons each carried their own `version` and were read at runtime
(cli.ts, tui App.tsx), and core hardcoded a third, stale value (0.20.0) that
the TUI and web actually reported to servers. Consolidate onto the root
package.json as the single source of truth.
- Add core/node/version.ts: shared, Node-only readInspectorVersion() that walks
up to the root package.json (always shipped in the tarball). 100% covered by a
new integration test.
- CLI and TUI read the version via the shared reader instead of their own
manifest; TUI now also passes a real clientIdentity (was falling through to
the stale core default).
- Drop the `version` field from all four client package.jsons; the root keeps
the only version. TUI header name/description become local display constants.
- core default client identity: replace the stale 0.20.0 hardcode with a neutral
0.0.0 placeholder (real clients pass clientIdentity; web will source its
version from GET /api/config, see #1639).
- Stop shipping clients/cli/package.json and clients/tui/package.json (no longer
read at runtime) — supersedes the earlier "ship them" fix; update pack:verify
and the root README packaging invariants accordingly.
- Add core/node/** to the web coverage include so the new module is gated.
- Add the shared-code architecture diagram to the root README.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
CLAUDE.md included @./README.md, which did not exist. Add a root README
covering project layout, setup, the @inspector/core shared package, the web
"dumb components" + Storybook approach, test-servers, build, the testing/quality
gate, publishing, and how AGENTS.md/CLAUDE.md govern contributions for humans
and agents alike.
Move the publishing/packaging documentation (single-package tarball, "files"
invariants, pack:verify) from the launcher README to the root README, where
whole-repo packaging belongs; the launcher README now points there.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
- Changed MCP_SERVER_REQUEST_TIMEOUT from 10s to 5m (300000ms)
- Supports elicitation tools and other long-running operations
- Updated documentation to clarify client-side vs server-side timeouts
- Users can still customize via UI config or URL parameters
- Document --header flag usage with examples
- Explain header format requirements
- Clarify transport support (HTTP, SSE, not STDIO)
- Add authentication examples with API keys and bearer tokens
- Add examples for stdio, sse, and streamable-http transport types
- Document automatic server selection behavior
- Show how to use default-server in config files
Attempts to execute this command will fail: npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8000 --transport http. The error indicates that --method is a mandatory parameter: "Method is required. Use --method to specify the method to invoke. Failed with exit code: 1". That's why the right command may look like this: npx @modelcontextprotocol/inspector --cli http://127.0.0.1:8000 --transport http --method tools/list.
- Extract duplicated URL generation code into getClientUrl() helper function in start.js
- Replace all 127.0.0.1 references with localhost for consistency across codebase
- Update server to respect HOST environment variable for URL generation
- Remove 127.0.0.1 from default allowed origins in CORS configuration
- Update documentation to use localhost instead of 127.0.0.1
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Complete the security hardening started in e8e9909 by also binding the client to localhost only.
Previously only the server was protected while the client remained exposed to the network,
allowing attackers to access the server through the client as a proxy.
Changes:
- Add HOST environment variable support to client (prod mode)
- Configure Vite dev server to bind to localhost by default
- Update browser auto-open URLs to use actual host instead of hardcoded 127.0.0.1
- Fix missing cancelled parameter in startProdClient function
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Removed outdated statement about auto-open being disabled with authentication
- Clarified that browser automatically opens with token pre-filled in URL
- Updated MCP_AUTO_OPEN_ENABLED description to note it works with authentication
The README now accurately reflects the improved developer experience where
authentication no longer prevents the browser from auto-opening.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Auto-open browser only works when DANGEROUSLY_OMIT_AUTH=true
- When auth is enabled (default), users must use the secure URL with token
- Server displays clear message that auto-open is disabled with auth
- Updates documentation to reflect this behavior
This improves UX by preventing the browser from opening without auth token,
forcing users to use the secure pre-filled URL instead.
- Add origin validation middleware to check Origin header
- Default allowed origins respect CLIENT_PORT environment variable
- Support ALLOWED_ORIGINS environment variable for additional origins
- Apply validation to all protected endpoints before auth check
- Return 403 Forbidden with clear message for invalid origins
- Add DNS Rebinding Protection section to README
This completes the security hardening by preventing malicious websites
from making requests to the local proxy server.
- Generate random session token on server startup using crypto.randomBytes
- Add authentication middleware requiring Bearer token for all sensitive endpoints
- Apply auth to /mcp, /stdio, /sse, and /message endpoints
- Display session token in console on startup
- Add DANGEROUSLY_OMIT_AUTH environment variable to disable auth for development
- Update README with authentication documentation
This prevents unauthorized access to the proxy server's ability to execute
local processes and connect to MCP servers.
- Change default binding from 0.0.0.0 (all interfaces) to 127.0.0.1 (localhost only)
- Add HOST environment variable to override binding address when needed
- Update console output to show actual binding address
- Document security change in README with warnings about binding to all interfaces
This prevents remote attackers from accessing the MCP Inspector proxy server,
which has the ability to execute local processes. The proxy should only be
accessible from the local machine unless explicitly configured otherwise.