Repository navigation
Description
Summary
In this repo's pkgs/mcpx copy, a daemon takeover (mcpx upgrade / mcpx daemon --takeover) orphans the upstream streamable-HTTP session of any server that was removed from the configuration between the old and new daemon: the successor never sends the session-termination DELETE, so the session is left to expire on the upstream on its own. pkgs/mcpx/TODO.md line 6 records this as known-not-done.
This is the same defect that was filed as dezren39/darwin-nix#390 and fixed there by dezren39/nix PR #391 (merge 723e091); this repo's mcpx copy does not have that fix.
Evidence (verified on main 54c223c)
pkgs/mcpx/TODO.md:6— "An upstream session whose server is removed from the configuration during a takeover is not adopted and expires upstream on its own; it is not DELETEd."pkgs/mcpx/internal/pool/handoff.go(pool.Adopt, ~line 193): HTTP instances are explicitly skipped —if h.Stdio == nil { if h.HTTP != nil { continue } }— so a handed HTTP session produces noAdoptedat all.pkgs/mcpx/internal/daemon/registry_handoff.go(Registry.Adopt):AdoptSessionsis only called for pools that match the new configuration; for an unmatched pool only the stdio children becomeorphans, and the HTTP session is dropped on the floor.Adoption.Attachtherefore has nothing to close for it.pkgs/mcpx/internal/mcpclient/http.go:588—HTTPHandoffcarries onlySessionID+Negotiated: no endpoint URL or headers, so a successor could not construct a transport for the orphaned session even if it wanted to terminate it.PoolHandoff(pool/handoff.go:38) likewise has no server name for logging.grep -rn TerminateHTTPSession pkgs/mcpx— no matches: the termination helper does not exist in this copy.- The primitive does exist:
HTTPTransport.Close()(mcpclient/http.go:566) already sends exactly oneDELETEfor its session id — it is simply never invoked for a removed server's session, because no transport is ever built for it.
Why it matters
Streamable HTTP upstreams hold per-session state (auth context, subscriptions, server-side resources). A config edit that removes a server followed by a routine mcpx upgrade silently leaks that session until the upstream's own expiry, and the leak is invisible — nothing is logged. The stdio side of the same handoff handles orphans correctly (children are closed post-commit), so the HTTP side is an asymmetric gap in an otherwise careful protocol.
Suggested fix (reference: dezren39/nix PR #391)
- Carry the endpoint in the handoff: add
URL+Headerstomcpclient.HTTPHandoff(populated from the transport atDetachtime; the handoff travels only over the takeover unix socket), and add aServername topool.PoolHandoff. - In
Registry.Adopt, record the HTTP sessions of pools with no match as orphan sessions (no upstream contact at adopt time). - In
Adoption.Attach(post-commit only), end each orphan session with exactly oneDELETEvia apool.TerminateOrphanedSessionhelper built onmcpclient's session termination;Abortmust leave the predecessor's session untouched. Log success/failure (e.g.upstream.session.terminated/upstream.session.orphanedwith server + session + reason). - Remove the TODO.md line 6 entry.
Acceptance criteria
- A daemon-level regression test (predecessor hands off a pool whose server is absent from the successor's config, with a live HTTP session) shows exactly one
DELETEwith that session id reaching the upstream after the handoff commits — and zero before the fix. - An aborted handoff sends no DELETE (the predecessor keeps its session).
- A matched pool's sessions are adopted as today (no behaviour change; existing takeover/handoff tests pass).
go test ./internal/pool/ ./internal/daemon/ ./internal/mcpclient/green inpkgs/mcpx.
Activity
Merge pull request #267 from developing-today/sweep/260-orphan-http-s…