mcp: derive Mcp-Protocol-Version header from the outgoing message (#1107)

`streamableClientConn.setMCPHeaders` sets `Mcp-Protocol-Version` only
from `c.initializedResult`, which is populated exclusively through the
unexported `clientConnection.sessionUpdated` hook. `Client.Connect`
reaches that hook via a type assertion on the `Connection` returned by
the `Transport`:

```go
  if hc, ok := cs.mcpConn.(clientConnection); ok {
      hc.sessionUpdated(cs.state)
  }
```

That assertion silently fails whenever the `Connection` is wrapped by
another type that exposes only the exported `Connection` interface.
`c.initializedResult` then stays `nil` for the life of the session, and
every `SEP-2575` (>= `2026-07-28`) request sent through that connection
is missing the header. Servers that validate the header per spec reject
the request with `400 Bad Request`, closing the connection.

Every SEP-2575 request already carries its own `_meta.protocolVersion`
field via `injectRequestMeta`. `setMCPHeaders` now reads the protocol
version from the outgoing message first, falling back to
`c.initializedResult` and then the request context.

This is currently breaking the GitHub MCP with the 1.7-pre releases of
this module.

Fixes #1109

Co-authored-by: eu.anthropic.claude-sonnet-5 via Crush <charmcrush@users.noreply.github.com>
This commit is contained in:
Jan Steinke 2026-07-24 09:21:59 +02:00 • committed by GitHub
commit 2781b8f7e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -2249,7 +2249,7 @@ func (c *streamableClientConn) Write(ctx context.Context, msg jsonrpc.Message) e
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json, text/event-stream")
if err := c.setMCPHeaders(req); err != nil {
if err := c.setMCPHeaders(req, msg); err != nil {
// Failure to set headers means that the request was not sent.
// Wrap with ErrRejected so the jsonrpc2 connection doesn't set writeErr
// and permanently break the connection.
@ -2373,7 +2373,7 @@ func (c *streamableClientConn) Write(ctx context.Context, msg jsonrpc.Message) e
return nil
}
func (c *streamableClientConn) setMCPHeaders(req *http.Request) error {
func (c *streamableClientConn) setMCPHeaders(req *http.Request, msg jsonrpc.Message) error {
c.mu.Lock()
defer c.mu.Unlock()
@ -2403,10 +2403,13 @@ func (c *streamableClientConn) setMCPHeaders(req *http.Request) error {
}
}
}
if c.initializedResult != nil {
switch {
case protocolVersionFromMessage(msg) != "":
req.Header.Set(protocolVersionHeader, protocolVersionFromMessage(msg))
case c.initializedResult != nil:
req.Header.Set(protocolVersionHeader, c.initializedResult.ProtocolVersion)
} else if v := protocolVersionFromContext(req.Context()); v != "" {
req.Header.Set(protocolVersionHeader, v)
case protocolVersionFromContext(req.Context()) != "":
req.Header.Set(protocolVersionHeader, protocolVersionFromContext(req.Context()))
}
if c.sessionID != "" {
req.Header.Set(sessionIDHeader, c.sessionID)
@ -2415,6 +2418,23 @@ func (c *streamableClientConn) setMCPHeaders(req *http.Request) error {
return nil
}
// protocolVersionFromMessage recovers the SEP-2575 `_meta.protocolVersion`
// value from an outgoing JSON-RPC request, if present. It returns "" for
// notifications, responses, requests without a `_meta.protocolVersion`, or a
// nil msg.
func protocolVersionFromMessage(msg jsonrpc.Message) string {
req, ok := msg.(*jsonrpc.Request)
if !ok {
return ""
}
meta := extractRequestMeta(req.Params)
if meta == nil {
return ""
}
v, _ := meta[MetaKeyProtocolVersion].(string)
return v
}
func (c *streamableClientConn) handleJSON(requestSummary string, resp *http.Response) {
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
@ -2676,7 +2696,7 @@ func (c *streamableClientConn) connectSSE(ctx context.Context, lastEventID strin
if err != nil {
return nil, err
}
if err := c.setMCPHeaders(req); err != nil {
if err := c.setMCPHeaders(req, nil); err != nil {
return nil, err
}
if lastEventID != "" {
@ -2712,7 +2732,7 @@ func (c *streamableClientConn) Close() error {
if err != nil {
c.closeErr = err
} else {
if err := c.setMCPHeaders(req); err != nil {
if err := c.setMCPHeaders(req, nil); err != nil {
c.closeErr = err
} else if resp, err := c.client.Do(req); err != nil {
c.closeErr = err