mcp: fix cancellation for HTTP transport

In #202, I added the checkRequest helper to validate incoming requests,
and invoked it in the stremable transports to preemptively reject
invalid HTTP requests, so that a jsonrpc error could be translated to an
HTTP error.

However, this introduced a bug: since cancellation was handled in the
jsonrpc2 layer, we never had to validate it in the mcp layer, and
therefore never added methodInfo. As a result, it was reported as an
invalid request in the http layer.

Add a test, and a fix. The simplest fix was to create stubs that are
placeholders for cancellation.

This was discovered in the course of investigating #285.
This commit is contained in:
Rob Findley 2025-08-13 15:37:56 +00:00 • committed by Robert Findley
commit bb6dadecca

View file

@ -118,12 +118,12 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
return
}
var session *StreamableServerTransport
var transport *StreamableServerTransport
if id := req.Header.Get(sessionIDHeader); id != "" {
h.mu.Lock()
session, _ = h.transports[id]
transport, _ = h.transports[id]
h.mu.Unlock()
if session == nil {
if transport == nil {
http.Error(w, "session not found", http.StatusNotFound)
return
}
@ -132,22 +132,22 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
// TODO(rfindley): simplify the locking so that each request has only one
// critical section.
if req.Method == http.MethodDelete {
if session == nil {
if transport == nil {
// => Mcp-Session-Id was not set; else we'd have returned NotFound above.
http.Error(w, "DELETE requires an Mcp-Session-Id header", http.StatusBadRequest)
return
}
h.mu.Lock()
delete(h.transports, session.SessionID)
delete(h.transports, transport.SessionID)
h.mu.Unlock()
session.connection.Close()
transport.connection.Close()
w.WriteHeader(http.StatusNoContent)
return
}
switch req.Method {
case http.MethodPost, http.MethodGet:
if req.Method == http.MethodGet && session == nil {
if req.Method == http.MethodGet && transport == nil {
http.Error(w, "GET requires an active session", http.StatusMethodNotAllowed)
return
}
@ -157,7 +157,7 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
return
}
if session == nil {
if transport == nil {
server := h.getServer(req)
if server == nil {
// The getServer argument to NewStreamableHTTPHandler returned nil.
@ -194,10 +194,10 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
h.transports[s.SessionID] = s
h.mu.Unlock()
}
session = s
transport = s
}
session.ServeHTTP(w, req)
transport.ServeHTTP(w, req)
}
// StreamableServerTransportOptions configures the stramable server transport.