Network: modern Mcp-* headers, new error codes, cancellation-as-abort (#1628)#1722
Conversation
…#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
… 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
Smoke-tested end-to-end + one bug fix + a mirroring correctionAdded a composable Bug the smoke test caught (fixed here): the fetch-tracking body redaction masked any field named Mirroring finding — corrects the earlier claim. Verified live that the SDK builds the standard mirrored headers ( Screenshots (live showcase + the three browser-path-impossible states via Storybook): all four error codes render as distinct badges; the |
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
…case)
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
…econd 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
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
…rotocol 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
… 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
…TP 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
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
Design decisions: relocating the spec-error UX to the Protocol tab + "reveal in Network"This documents the second half of the PR (commits after the initial Network-tab work), where the modern spec-error rendering was moved out of the Network tab and into the Protocol tab, with a cross-tab link back to the HTTP entry. 1. Why move the chip + alert to the Protocol tabThe Network tab is focused on the HTTP transaction (status, mirrored 2. Surfacing SDK-thrown errors into the Protocol log (the correlation)The two logs (
To make Decision: this is a derived view, not a mutation of the core log. The 3. Chip placement
4. "Reveal in Network" link + mode-aware navigationThe expanded alert can show a "View the HTTP request in the Network tab →" link. Clicking it correlates the message to its fetch, clears any filter that would hide the target (network text/category, or the embedded search), switches to the Network view in whichever mode is active, and scrolls to + force-expands the matching entry.
5. When to show the link (the gating rule)The link only appears when the Network entry adds diagnostic value the Protocol alert lacks — i.e. the error is thrown or tied to the HTTP request headers. A delivered, protocol-only error gets no link. Encoded as an
Testing
|
…c'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
|
@claude review |
|
Claude finished @cliffhall's task in 3m 18s —— View job Review: Network modern
|
…ment 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
|
Thanks for the thorough review — addressed in 99aaf8a. 🟠 1. Reveal one-shot cancelling its own scroll — fixed. Good catch; the race was real. Moved 🟡 2. Double-fire from the shared 🟢 3. Doc drift after the Network → Protocol relocation — fixed. Updated 🟢 4.
|
|
@claude review |
|
Claude finished @cliffhall's task in 2m 30s —— View job Review: Network modern
|
…rset 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
|
Thanks — both non-blocking polish notes applied in b80b744. 🟢 1. Redaction array-of-scalars edge — commented. Added a note that the masking assumes a sensitive value is a scalar or an object (recursed, so an inner sensitive string key is still masked), and that a sensitive key holding an array of scalars would slip through — no OAuth/token payload has that shape, so it's not handled. Left the logic as-is per your read. 🟢 2. 🟢 3 / 4. Double-fire safety confirmation and the accepted nits — no change needed; thanks for tracing the re-render-mid-reveal case.
|
|
@claude review |
|
Claude finished @cliffhall's task in 2m 38s —— View job Review: Network modern
|
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
|
Thanks — the 🟡 1.
🟢 2 / 3. Test-server injector notes — acknowledged, no change. The
|
|
@claude review |
|
Claude finished @cliffhall's task in 2m 55s —— View job Review: Network modern
|
…s 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
|
Great catch — the escape hatch was inverted logic. Fixed in 729e056 by requiring a genuine 404. 🟡 1. Dropped the escape hatch — the guard is now simply: if (code === ProtocolErrorCode.MethodNotFound && httpStatus !== 404) return null;Nothing intended is lost (the genuine modern case always carries its 404). Updated the doc comment to explain why unknown-status is deliberately not treated as a 404, and the tests: 🟢 2. Notes — acknowledged, no action. Last-wins on repeated ids (aligned with
|
|
@claude review |
|
Claude finished @cliffhall's task in 1m 13s —— View job Review: Network modern
|
|
Thanks for the thorough five-round pass — 🚢 confirmed, no actionable items remaining. Summary of what the review surfaced and how it landed:
All commits green under |
Closes #1628
Upgrades the Inspector's monitoring tabs for the modern (2026-07-28) Streamable HTTP transport — the standardized HTTP headers (SEP-2243) and the new JSON-RPC error taxonomy (SEP-2575). The Network tab owns the HTTP transaction; the modern protocol errors are surfaced distinctly in the Protocol tab, with a cross-tab link back to the correlated HTTP request.
Network tab (HTTP transaction)
Mcp-Method,Mcp-Name,Mcp-Param-*,MCP-Protocol-Versionare recognised and highlighted (violet) in the request/response header tables.=?base64?{b64}?=sentinel values are decoded inline and flagged with abase64chip (raw value on hover).Mcp-Method↔method,Mcp-Name↔params.name/uri,MCP-Protocol-Version↔_metaversion). A disagreement is flagged in the danger colour with a labelled marker — aHeaderMismatchis visible before the server rejects it. This is a header-level concern, so it stays in the Network tab.notifications/cancelledframe (SEP §7.5)./api/fetchforwards them verbatim — integration-tested). It gatesMcp-Param-*on a non-browser environment (detectProbeEnvironment() !== "browser"), so the web client never emits them — anx-mcp-headertool is therefore uncallable from the web client against a strict modern server (it answers-32020); it works from the Node CLI/TUI. Documented increateRemoteFetch.ts.The Network tab does not show the spec-error chip/alert — those live in the Protocol tab.
Protocol tab (protocol errors)
-32020 HeaderMismatch,-32021 MissingRequiredClientCapability,-32022 UnsupportedProtocolVersion,-32601 MethodNotFound, colour-coded and shown in the entry's own casing (e.g.UnsupportedProtocolVersion, not run-together uppercase). Placement: wide layout → right after the method chip; compact sidebar row → no chip (theERRORstatus badge suffices). An explanatory alert in the expanded detail names the code, describes it, and lists the server'ssupportedversions for-32022.-32020/-32021/-32022(HTTP 400) as JSON-RPC error responses, so they land in the Protocol log natively.-32601(HTTP 404) and other non-modern-enveloped errors are thrown — the request stays pending.enrichProtocolEntries(inApp) correlates a pending Protocol request to its transport fetch by JSON-RPC id (parsed fromFetchRequestEntry.requestBody, which is the serialized message) and folds the fetch's error onto it — so-32601appears in the Protocol tab too. This is a derived view: the underlyingMessageLogStateis not mutated, and the memo returns the same array reference when nothing matched."Reveal in Network" link
The expanded Protocol alert can show a "View the HTTP request in the Network tab →" link. Clicking it correlates the message to its fetch, clears any filter that would hide the target, switches to the Network view in whichever mode is active, and scrolls to + force-expands the matching entry:
pinMonitor).onActiveTabChange("Network").revealIdthreadsNetworkScreen → NetworkStreamPanel → NetworkEntry, where the target scrolls itself into view in arequestAnimationFrame(landing afteruseScrollMemory's restore), expands, then clears the signal.The link only appears when the Network entry adds value the alert lacks — i.e. the error is thrown or tied to the HTTP headers:
-32020HeaderMismatch-32601MethodNotFound-32021MissingRequiredClientCapability-32022UnsupportedProtocolVersionsupportedlist is already in the alertOther changes
code(targeting the OAuth authorization code), which clobbered the numeric JSON-RPCerror.codeand silently broke the spec-error classification against real traffic. Redaction now only masks string values of a sensitive field.test-servers/configs/modern-network-http.jsonserves aget_weathertool with anx-mcp-headerannotation plus fourtrigger_*tools backed by a gatedtransport.modern.injectSpecErrorsmiddleware that returns real400/-32020,400/-32021,400/-32022(+supported),404/-32601responses — so the whole taxonomy can be driven live. Documented in the root README.AGENTS.mdboard Status option-IDs refreshed (theBuilding …columns were removed).Verification
npm run cigreen on every commit (format, lint, build, per-file ≥90 coverage incl. the web integration project, smoke, Storybook).-32601, the link gating, and the reveal (force-expand +scrollIntoView+ one-shot clear).-32601), and the reveal link jumps to + expands the correlated HTTP entry in both the pinned-sidebar and closed/full-screen layouts.Screenshots
1. Protocol-only errors: An error that is only contained in a protocol response body has no link to the corresponding network entry in the error description.

2. HTTP-relevant protocol errors: Header-related protocol errors, or those thrown from server with a specific HTTP status code instead of just included in a protocol response have a link to the corresponding network entry in the error description.

3. Follow Link to Network entry (pinned sidebar): the corresponding HTTP entry scrolled to + expanded, with the highlighted

Mcp-*headers4. Follow Link to Network entry (closed sidebar / full-screen): same jump, full-screen Network view

5. HTTP-relevant protocol errors — thrown variant (

-32601): A method-not-found is thrown by the server (HTTP 404) rather than returned in a protocol response; it's surfaced into the Protocol tab by correlation and, being HTTP-relevant, also carries the link.6. Follow Link to Network entry (thrown

-32601): the correlated404 NOT FOUNDentry scrolled to + expanded — the real response that lives only in the Network log.7. Cancellation-as-abort: Shown as "Cancelled" status in protocol entry + the SEP-2575 note

8. Header/body mismatch marker (Network tab): A mirrored header that disagrees with the body is flagged in red with a ⊘ marker (rendered from the NetworkEntry component; a conformant client never produces this on the wire).

🤖 Generated with Claude Code
https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5