From 16d990b0416f63ca4948a5d8ba8f54ac6114b5a9 Mon Sep 17 00:00:00 2001 From: Maciej Kisiel Date: Tue, 3 Mar 2026 15:13:10 +0100 Subject: [PATCH] mcp: protect ioConn.protocolVersion with a mutex (#832) This is done to prevent concurrent reads and writes, which violate Go's memory model. The concurrent write may be triggered e.g. by setting the log level on the client side. Fixes #828 --- mcp/transport.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/mcp/transport.go b/mcp/transport.go index 5f2a500..23dccf8 100644 --- a/mcp/transport.go +++ b/mcp/transport.go @@ -344,7 +344,11 @@ func (r rwc) Close() error { // // See [msgBatch] for more discussion of message batching. type ioConn struct { - protocolVersion string // negotiated version, set during session initialization. + // protocolVersion is the negotiated version of the protocol, + // set during session initialization. + // Since writes may be concurrent to reads, we need to guard this with a mutex. + sessionMu sync.Mutex + protocolVersion string writeMu sync.Mutex // guards Write, which must be concurrency safe. rwc io.ReadWriteCloser // the underlying stream @@ -430,9 +434,15 @@ func (c *ioConn) sessionUpdated(state ServerSessionState) { protocolVersion = state.InitializeParams.ProtocolVersion } if protocolVersion == "" { + // 2025-03-26 is used, because it's the last spec version + // where specifying the protocol version in the HTTP header + // was not required. protocolVersion = protocolVersion20250326 } - c.protocolVersion = negotiatedVersion(protocolVersion) + protocolVersion = negotiatedVersion(protocolVersion) + c.sessionMu.Lock() + c.protocolVersion = protocolVersion + c.sessionMu.Unlock() } // addBatch records a msgBatch for an incoming batch payload. @@ -533,8 +543,12 @@ func (t *ioConn) Read(ctx context.Context) (jsonrpc.Message, error) { if err != nil { return nil, err } - if batch && t.protocolVersion >= protocolVersion20250618 { - return nil, fmt.Errorf("JSON-RPC batching is not supported in %s and later (request version: %s)", protocolVersion20250618, t.protocolVersion) + var protocolVersion string + t.sessionMu.Lock() + protocolVersion = t.protocolVersion + t.sessionMu.Unlock() + if batch && protocolVersion >= protocolVersion20250618 { + return nil, fmt.Errorf("JSON-RPC batching is not supported in %s and later (request version: %s)", protocolVersion20250618, protocolVersion) } t.queue = msgs[1:]