Skip to content

Commit 16d990b

Browse files
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
1 parent 4e1c3a3 commit 16d990b

1 file changed

Lines changed: 18 additions & 4 deletions

File tree

mcp/transport.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,11 @@ func (r rwc) Close() error {
344344
//
345345
// See [msgBatch] for more discussion of message batching.
346346
type ioConn struct {
347-
protocolVersion string // negotiated version, set during session initialization.
347+
// protocolVersion is the negotiated version of the protocol,
348+
// set during session initialization.
349+
// Since writes may be concurrent to reads, we need to guard this with a mutex.
350+
sessionMu sync.Mutex
351+
protocolVersion string
348352

349353
writeMu sync.Mutex // guards Write, which must be concurrency safe.
350354
rwc io.ReadWriteCloser // the underlying stream
@@ -430,9 +434,15 @@ func (c *ioConn) sessionUpdated(state ServerSessionState) {
430434
protocolVersion = state.InitializeParams.ProtocolVersion
431435
}
432436
if protocolVersion == "" {
437+
// 2025-03-26 is used, because it's the last spec version
438+
// where specifying the protocol version in the HTTP header
439+
// was not required.
433440
protocolVersion = protocolVersion20250326
434441
}
435-
c.protocolVersion = negotiatedVersion(protocolVersion)
442+
protocolVersion = negotiatedVersion(protocolVersion)
443+
c.sessionMu.Lock()
444+
c.protocolVersion = protocolVersion
445+
c.sessionMu.Unlock()
436446
}
437447

438448
// addBatch records a msgBatch for an incoming batch payload.
@@ -533,8 +543,12 @@ func (t *ioConn) Read(ctx context.Context) (jsonrpc.Message, error) {
533543
if err != nil {
534544
return nil, err
535545
}
536-
if batch && t.protocolVersion >= protocolVersion20250618 {
537-
return nil, fmt.Errorf("JSON-RPC batching is not supported in %s and later (request version: %s)", protocolVersion20250618, t.protocolVersion)
546+
var protocolVersion string
547+
t.sessionMu.Lock()
548+
protocolVersion = t.protocolVersion
549+
t.sessionMu.Unlock()
550+
if batch && protocolVersion >= protocolVersion20250618 {
551+
return nil, fmt.Errorf("JSON-RPC batching is not supported in %s and later (request version: %s)", protocolVersion20250618, protocolVersion)
538552
}
539553

540554
t.queue = msgs[1:]

0 commit comments

Comments
 (0)