mcp: allow restricting the protocol versions a server supports (#1199)

Fixes #945
This commit is contained in:
Guglielmo Colombo 2026-08-28 16:06:10 +02:00 • committed by GitHub
commit 21c18c6229
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -4141,3 +4141,80 @@ func TestStreamableMaxRequestBodyBytes(t *testing.T) {
})
}
}
// TestStreamableSupportedProtocolVersions_Header verifies that the
// MCP-Protocol-Version header is validated against
// [ServerOptions.SupportedProtocolVersions], and not merely against the
// versions the SDK knows about.
func TestStreamableSupportedProtocolVersions_Header(t *testing.T) {
const initBody = `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}`
newServer := func(t *testing.T, stateless bool) *httptest.Server {
server := NewServer(testImpl, &ServerOptions{
SupportedProtocolVersions: []string{protocolVersion20251125},
})
handler := NewStreamableHTTPHandler(func(*http.Request) *Server { return server },
&StreamableHTTPOptions{Stateless: stateless})
httpServer := httptest.NewServer(handler)
t.Cleanup(httpServer.Close)
return httpServer
}
post := func(t *testing.T, url, version, sessionID string) *http.Response {
t.Helper()
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(initBody))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json, text/event-stream")
req.Header.Set(protocolVersionHeader, version)
if sessionID != "" {
req.Header.Set(sessionIDHeader, sessionID)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
return resp
}
for _, stateless := range []bool{false, true} {
for _, test := range []struct {
version string
wantStatus int
}{
{protocolVersion20251125, http.StatusOK},
// Supported by the SDK, but excluded by the server.
{protocolVersion20250618, http.StatusBadRequest},
// Unknown to the SDK: rejected before any server is consulted.
{"1999-01-01", http.StatusBadRequest},
} {
t.Run(fmt.Sprintf("stateless=%v/%s", stateless, test.version), func(t *testing.T) {
resp := post(t, newServer(t, stateless).URL, test.version, "")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != test.wantStatus {
t.Errorf("status = %d, want %d; body = %s", resp.StatusCode, test.wantStatus, body)
}
})
}
}
t.Run("existing session", func(t *testing.T) {
httpServer := newServer(t, false)
resp := post(t, httpServer.URL, protocolVersion20251125, "")
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
sessionID := resp.Header.Get(sessionIDHeader)
if sessionID == "" {
t.Fatalf("initialize response missing %s header", sessionIDHeader)
}
resp = post(t, httpServer.URL, protocolVersion20250618, sessionID)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusBadRequest {
t.Errorf("status = %d, want %d; body = %s", resp.StatusCode, http.StatusBadRequest, body)
}
})
}