Repository navigation
Closed
Labels
app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)Package/app: mcpx (pkgs/ or apps/ in this repo)kind/featureNew feature or capability requestNew feature or capability requestpriority/normalOrdinary priority (the default)Ordinary priority (the default)source/design-architectFiled by the repo design architect jobFiled by the repo design architect jobstatus:donetask/triageQueue packet kind: verify, reproduce, and route this itemQueue packet kind: verify, reproduce, and route this item
Description
Phase 1 of design #1634 (mcpx client session & stream recovery). Design doc: files/designs/code-mcpx-client-session-stream-recovery.md in the keep-the-four-repos-healthy goal workspace.
Scope
Recover transparently when a legacy HTTP upstream forgets the session (restarts) mid-life: forget the dead session, re-initialize once, retry the request once.
- Trigger, narrowly: a request (never a notification, never
initializeitself) fails withHTTPStatusError404, the client era is legacy, and the failed request actually carried a session id. Any other 404 behaves exactly as today. internal/mcpclient/http.go: add a small transport seam the client can interface-assert (theinterface{ setNegotiated(string) }pattern inclient.go): atomically report-and-clear the held session id (working nameforgetSession() bool). The freshinitializemust go out with no session id, and no later request may echo the dead one. Stdio transports do not implement the seam; recovery is HTTP-only.internal/mcpclient/client.go: recovery in the request path (roundTrip/calllayer) reusing the existinginitializeLegacy/finishLegacyunchanged. Re-initialize is singleflight across concurrent calls — a client-level mutex plus a session generation, so N concurrent 404s from one upstream restart produce exactly oneinitialize; calls whose generation was already replaced just retry on the new session.- Retry the original request exactly once through
roundTrip(fresh pending entry, new request id). A second 404, or a failed re-initialize, surfaces the error as today. No retry loop. - After a successful re-initialize, fire the existing catalogue-invalidation signal (
invalidatepath →Notifications.OnListChanged) fortools,resourcesandprompts— a restarted upstream's catalogue is not the cached one.finishLegacyalready refreshes ServerInfo/Capabilities and restarts the GET listen stream for ≥2025-03-26. - Modern era is excluded: 2026 has no sessions (TR-30); a modern 404 stays an ordinary error.
- Retire conformance gap
streamable-http-client-reinit-on-404(internal/conformance/coverage_transport.go).
Evidence (main ed8e591, 2026-10-28)
- Gap at
internal/conformance/coverage_transport.go:115(origin mcpx client: legacy Streamable HTTP and stdio robustness — session 404, GET stream, SSE retry, string ids, stream timeout, version header dezren39/darwin-nix#203): "a 404 for the session is returned as an error; no new initialize". HTTPTransport.Send(internal/mcpclient/http.go~L166-180) returns any non-2xx asHTTPStatusError; the session id is captured at ~L157-160 and echoed bysetHeaders.- Register TR-29 (
docs/compare/register/transports.md:550-565): legacy spec 2025-03-26..2025-11-25 — a client receiving 404 for a session-carrying request MUST start a new session; mcpx @05c78b2 missing; "a restarted remote server leaves its mcpx instance failing every call" (the pool cannot reap an HTTP client that never dies). - Decision recorded on Design: mcpx client session & stream recovery — re-initialize on a legacy session 404, re-issue a broken modern request once #1634: recovery lives in the client, not the transport (protocol-free layer) and not the pool (reaping loses the in-flight call).
Acceptance
- Fake legacy HTTP upstream test: session dropped upstream → the call succeeds after exactly one re-initialize, and the retried request carries the new session id.
- N concurrent calls 404ing from the same restart → exactly one
initializeobserved upstream. - 404 with no session held (stateless legacy server) → error as today, no
initializesent. - A second 404 after re-initialize → error surfaces; no further re-initialize for that call.
- Modern-era 404 → no recovery attempt.
- Gap
streamable-http-client-reinit-on-404retired;go test ./...for pkgs/mcpx passes in CI.
Out of scope
Modern broken-stream re-issue (Phase 2), register truth-up (Phase 3), persisting the new session into #1414's store (open question on #1634), notification-triggered lazy re-initialize (open question on #1634).
Metadata
Metadata
Assignees
Labels
app/mcpxPackage/app: mcpx (pkgs/ or apps/ in this repo)Package/app: mcpx (pkgs/ or apps/ in this repo)kind/featureNew feature or capability requestNew feature or capability requestpriority/normalOrdinary priority (the default)Ordinary priority (the default)source/design-architectFiled by the repo design architect jobFiled by the repo design architect jobstatus:donetask/triageQueue packet kind: verify, reproduce, and route this itemQueue packet kind: verify, reproduce, and route this item
Activity
developing-today commentedon Oct 28, 2026
CODE-656
dezren39 commentedon Oct 28, 2026
agent-note:v1 — triage verdict: CONFIRMED, scope as-built verified (queue task 1770, lane A, 2026-10-28)
Verified fresh on main ed8e591 (laptop primary clone, matching the issue's evidence base):
internal/conformance/coverage_transport.go:115—streamable-http-client-reinit-on-404("a 404 for the session is returned as an error; no new initialize", origin mcpx client: legacy Streamable HTTP and stdio robustness — session 404, GET stream, SSE retry, string ids, stream timeout, version header dezren39/darwin-nix#203).internal/mcpclient/http.go:sessionIDheld at :31, captured from responses at :158, non-2xx returned asHTTPStatusErrorat :169 (type at :690) — the trigger surface exists exactly as scoped.client.go:377interface-assertsinterface{ setNegotiated(string) }(implhttp.go:79) — theforgetSession() boolseam follows it directly; stdio transports simply won't implement it.initializeLegacyclient.go:343 /finishLegacy:362, request pathcall:902 /roundTrip:953, client mutexmu sync.Mutexclient.go:78, catalogue invalidationinvalidateclient.go:744 →Notifications.OnListChanged— all reusable unchanged, as the design (Design: mcpx client session & stream recovery — re-initialize on a legacy session 404, re-issue a broken modern request once #1634) requires.Shapes settled by this triage:
forgetSession() boolon HTTPTransport — atomically reports whether a session id was held and clears it (plus the negotiated state tied to it), so the fresh initialize goes out with no session id and no later request echoes the dead one.Follow-up: implementation packet enqueued as a separate queue task (small_fix) with #1637's acceptance criteria intact. This triage does not implement.
137 remaining items
dezren39 commentedon Oct 28, 2026
🤖 Agent note · parked
queue/1777-1637-legacy-reinitdezren39 commentedon Oct 28, 2026
🤖 Agent note · picked up
queue/1777-1637-legacy-reinit@ 5407a41Merge pull request #1642 from developing-today/queue/1777-1637-legacy…
dezren39 commentedon Oct 28, 2026
🤖 Agent note · done
docs(mcpx): register truth-up TR-29 / PM-06 as-built + roadmap row (#…