Skip to content

Streamable HTTP: an unsupported protocol version below 2026-07-28 is a plain-text 400, above it a -32022 with data.supported #1173

Closed

Description

@erikryanmoore

Describe the bug

An unsupported protocol version gets one of two different responses, depending on how the version string sorts against 2026-07-28.

streamable.go#L369-L373:

protocolVersion := req.Header.Get(protocolVersionHeader)
if protocolVersion != "" && !slices.Contains(supportedProtocolVersions, protocolVersion) && protocolVersion <
protocolVersion20260728 {
      http.Error(w, fmt.Sprintf("Bad Request: Unsupported protocol version (supported versions: %s)",
strings.Join(supportedProtocolVersions, ",")), http.StatusBadRequest)
      return
}

Anything sorting below 2026-07-28 stops here and gets a plain-text 400. Anything sorting above it falls through to the SEP-2575 check in server.go#L1866-L1877 and gets -32022 with data.supported and data.requested.

Old versions are the common case, and they're the ones getting the response a client can't parse.

To Reproduce

A stateless server from mcp.NewServer + mcp.NewStreamableHTTPHandler, sent two tools/list requests that differ only in the version they name:

curl -i -sS -X POST "$ENDPOINT" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'Mcp-Method: tools/list' \
-H 'Mcp-Protocol-Version: 1999-01-01' \
--data '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{"_meta":{"io.modelcontextprotocol/protocolVersi
on":"1999-01-01","io.modelcontextprotocol/clientCapabilities":{}}}}'

Then the same request with 2027-01-01 in both places.

What did you see

1999-01-01 -> 400 Bad Request: Unsupported protocol version (supported versions:
2026-07-28,2025-11-25,2025-06-18,2025-03-26,2024-11-05)
2027-01-01 -> 400 {"jsonrpc":"2.0","id":1,"error":{"code":-32022,"message":"unsupported protocol version","data":
{"supported":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"requested":"2027-01-01"}}}

What did you expect to see

-32022 for both. A client that can't fall forward reads data.supported to pick its next attempt.

The supported list does reach the client either way, so negotiation isn't broken. But a client parsing the response as JSON-RPC gets text on the branch it's most likely to hit, and can only report the status code.

Additional context

This may be intentional. The check cites §2.7 of the 2025-06-18 spec, and a genuinely pre-SEP-2575 client wouldn't understand a -32022 body anyway.

If so, the comment above it doesn't say so. It explains what's validated, not why the < protocolVersion20260728 clause is there, and that clause is what picks the response shape.

Either way the comparison is lexical, so an unexpected version string gets whichever branch its bytes sort into.

If it isn't intentional, either drop the clause and let the SEP-2575 check answer both, or return the -32022 body from the header check.

What version of the Go MCP SDK are you using

v1.7.0, and main at 64e454e.

What version of Go are you using

go version go1.26.2 darwin/arm64. Not Go-version dependent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions