mcp: don't persist failed streamable sessions (#605)

If a stateful streamable session fails to initialize, it is unusable.
Avoid allocating resources by closing the session immediately.

Also:
- Fix a bug where InitializeParams is not guarded with its mutex.
- Fix a resource leak where sessions are persisted with session id "",
even though they are unaddressable.
- Add a relevant benchmark, and update tests.

Fixes #578
This commit is contained in:
Robert Findley 2025-10-27 16:29:16 -04:00 • committed by GitHub
commit d256a9c109
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -324,10 +324,13 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
logger: h.opts.Logger,
}
// Sessions without a session ID are also stateless: there's no way to
// address them.
stateless := h.opts.Stateless || sessionID == ""
// To support stateless mode, we initialize the session with a default
// state, so that it doesn't reject subsequent requests.
var connectOpts *ServerSessionOptions
if h.opts.Stateless {
if stateless {
// Peek at the body to see if it is initialize or initialized.
// We want those to be handled as usual.
var hasInitialize, hasInitialized bool
@ -405,7 +408,7 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
transport: transport,
}
if h.opts.Stateless {
if stateless {
// Stateless mode: close the session when the request exits.
defer session.Close() // close the fake session after handling the request
} else {
@ -424,6 +427,13 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
h.mu.Lock()
h.sessions[transport.SessionID] = sessInfo
h.mu.Unlock()
defer func() {
// If initialization failed, clean up the session (#578).
if session.InitializeParams() == nil {
// Initialization failed.
session.Close()
}
}()
}
}