mcp: return json-rpc error on stateful server serving protocolVersion20260728 requests (#1143)

Fixes #1130
This commit is contained in:
Guglielmo Colombo 2026-08-05 12:16:39 +02:00 • committed by GitHub
commit 6b1518cb0b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -314,6 +314,18 @@ var noprotocolerrorbody = mcpgodebug.Value("noprotocolerrorbody")
// The option will be removed in the 1.8.0 version of the SDK.
var disablecontenttypecheck = mcpgodebug.Value("disablecontenttypecheck")
// plaintextstatefulrejection is a compatibility parameter that restores the
// previous behavior of a stateful [StreamableHTTPHandler] when it receives a
// request carrying SEP-2575 per-request metadata (i.e. an
// `io.modelcontextprotocol/protocolVersion` `_meta` field, or an
// `MCP-Protocol-Version` header >= 2026-07-28). When unset (the default), the
// server responds with a JSON-RPC error of code
// [CodeUnsupportedProtocolVersion] and an [UnsupportedProtocolVersionData]
// payload listing its supported legacy versions, as required by SEP-2575. When
// set to "1", the server instead responds with a plain-text `http.Error` body
// mentioning the `StreamableHTTPOptions.Stateless` field.
var plaintextstatefulrejection = mcpgodebug.Value("plaintextstatefulrejection")
// writeJSONRPCError writes a JSON-RPC error response with the given HTTP
// status code, request ID (may be a zero ID for errors that occur before the
// request body has been parsed), and JSON-RPC error.
@ -1519,10 +1531,31 @@ func (c *streamableServerConn) servePOST(w http.ResponseWriter, req *http.Reques
// rejection as it should learn about the supported protocols from the
// DiscoverResult response.
if !c.stateless && jreq.Method != methodDiscover {
http.Error(w, fmt.Sprintf(
"Bad Request: protocol version %q is only supported on stateless HTTP servers (set StreamableHTTPOptions.Stateless = true)",
protocolVersion),
http.StatusBadRequest)
if plaintextstatefulrejection == "1" {
http.Error(w, fmt.Sprintf(
"Bad Request: protocol version %q is only supported on stateless HTTP servers (set StreamableHTTPOptions.Stateless = true)",
protocolVersion),
http.StatusBadRequest)
return
}
// Advertise only the legacy versions this stateful server accepts
// (i.e. exclude 2026-07-28, since that's what the request asked for
// and what we're rejecting).
legacyVersions := slices.DeleteFunc(slices.Clone(supportedProtocolVersions), func(v string) bool {
return v >= protocolVersion20260728
})
data, _ := json.Marshal(UnsupportedProtocolVersionData{
Supported: legacyVersions,
Requested: protocolVersion,
})
c.logger.Warn(fmt.Sprintf(
"rejecting request with protocol version %q: this server is stateful; set StreamableHTTPOptions.Stateless = true to accept it",
protocolVersion))
writeJSONRPCError(w, http.StatusBadRequest, jreq.ID, &jsonrpc.Error{
Code: CodeUnsupportedProtocolVersion,
Message: fmt.Sprintf("protocol version %q is not supported by this server", protocolVersion),
Data: data,
})
return
}
if headerVersion == "" {