go-sdk/mcp
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Benjamin Egelund-Müller 862d78a265
mcp: fix race condition in ServerSession.startKeepalive (#856)
This PR fixes a race condition where `ServerSession.keepaliveCancel` was
accessed in `Close` concurrently with its initialization in
`ServerSession.startKeepalive`. It also removes a now redundant test.

The fix moves keepalive initialization into `ServerSession.Connect`
similar to the implementation in `ClientSession.Connect`. This is safe
because the MCP spec allows keepalive pings before the initialization
messages. From the MCP
[spec](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle)
(emphasis added):
> The client SHOULD NOT send requests **other than pings** before the
server has responded to the initialize request.

The PR includes a reproducer test case, which fails with the following
error without this fix:
```
> go test -count=10 -race -run ^TestStreamableStatelessKeepaliveRace$ github.com/modelcontextprotocol/go-sdk/mcp                                                                                                                          8s
==================
WARNING: DATA RACE
Write at 0x00c00007fbf0 by goroutine 1095:
  github.com/modelcontextprotocol/go-sdk/mcp.startKeepalive()
      /Users/benjamin/Documents/go-sdk/mcp/shared.go:590 +0x58
  github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).startKeepalive()
      /Users/benjamin/Documents/go-sdk/mcp/server.go:1526 +0x130
  github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).initialized()
      /Users/benjamin/Documents/go-sdk/mcp/server.go:1059 +0xf8
  github.com/modelcontextprotocol/go-sdk/mcp.init.serverSessionMethod[go.shape.*uint8,go.shape.interface { GetMeta() map[string]interface {}; SetMeta(map[string]interface {}); github.com/modelcontextprotocol/go-sdk/mcp.isResult() }].func27()
      /Users/benjamin/Documents/go-sdk/mcp/shared.go:332 +0x98
  github.com/modelcontextprotocol/go-sdk/mcp.newServerMethodInfo[go.shape.*github.com/modelcontextprotocol/go-sdk/mcp.InitializedParams,go.shape.interface { GetMeta() map[string]interface {}; SetMeta(map[string]interface {}); github.com/modelcontextprotocol/go-sdk/mcp.isResult() },go.shape.struct { github.com/modelcontextprotocol/go-sdk/mcp.Meta "json:\"_meta,omitempty\"" }].func2()
      /Users/benjamin/Documents/go-sdk/mcp/shared.go:272 +0x84
  github.com/modelcontextprotocol/go-sdk/mcp.defaultReceivingMethodHandler[go.shape.*uint8]()
      /Users/benjamin/Documents/go-sdk/mcp/shared.go:152 +0xe8
  github.com/modelcontextprotocol/go-sdk/mcp.defaultReceivingMethodHandler[*github.com/modelcontextprotocol/go-sdk/mcp.ServerSession]()
      /Users/benjamin/Documents/go-sdk/mcp/shared.go:146 +0x60
  github.com/modelcontextprotocol/go-sdk/mcp.handleReceive[go.shape.*uint8]()
      /Users/benjamin/Documents/go-sdk/mcp/shared.go:169 +0x228
  github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).handle()
      /Users/benjamin/Documents/go-sdk/mcp/server.go:1445 +0x370
  github.com/modelcontextprotocol/go-sdk/mcp.connect[go.shape.*uint8,go.shape.*uint8].func1.1()
      /Users/benjamin/Documents/go-sdk/mcp/transport.go:170 +0x5c
  github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.HandlerFunc.Handle()
      /Users/benjamin/Documents/go-sdk/internal/jsonrpc2/jsonrpc2.go:84 +0x48
  github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.(*Connection).handleAsync.func3()
      /Users/benjamin/Documents/go-sdk/internal/jsonrpc2/conn.go:717 +0xd8

Previous read at 0x00c00007fbf0 by goroutine 1089:
  github.com/modelcontextprotocol/go-sdk/mcp.(*ServerSession).Close()
      /Users/benjamin/Documents/go-sdk/mcp/server.go:1502 +0x34
  github.com/modelcontextprotocol/go-sdk/mcp.(*StreamableHTTPHandler).ServeHTTP.deferwrap1()
      /Users/benjamin/Documents/go-sdk/mcp/streamable.go:519 +0x34
  runtime.deferreturn()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/runtime/panic.go:589 +0x5c
  github.com/modelcontextprotocol/go-sdk/mcp.TestStreamableStatelessKeepaliveRace.mustNotPanic.func2()
      /Users/benjamin/Documents/go-sdk/mcp/streamable_test.go:2156 +0x94
  net/http.HandlerFunc.ServeHTTP()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:2322 +0x48
  net/http.serverHandler.ServeHTTP()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:3340 +0x270
  net/http.(*conn).serve()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:2109 +0x9b0
  net/http.(*Server).Serve.gowrap3()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:3493 +0x48

Goroutine 1095 (running) created at:
  github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.(*Connection).handleAsync()
      /Users/benjamin/Documents/go-sdk/internal/jsonrpc2/conn.go:715 +0x334
  github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2.(*Connection).acceptRequest.func2.gowrap1()
      /Users/benjamin/Documents/go-sdk/internal/jsonrpc2/conn.go:676 +0x34

Goroutine 1089 (running) created at:
  net/http.(*Server).Serve()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/server.go:3493 +0x5dc
  net/http/httptest.(*Server).goServe.func1()
      /opt/homebrew/Cellar/go/1.25.4/libexec/src/net/http/httptest/server.go:311 +0x98
==================
--- FAIL: TestStreamableStatelessKeepaliveRace (0.14s)
    testing.go:1617: race detected during execution of test
FAIL
FAIL	github.com/modelcontextprotocol/go-sdk/mcp	1.727s
FAIL
```

---------

Co-authored-by: Maciej Kisiel <mkisiel@google.com>
2026-04-03 20:17:49 +02:00
..
testdata mcp: return input validation errors as tool results, not JSON-RPC errors (#863) 2026-03-31 10:14:36 +02:00
capabilities_test.go all: clean up Go 1.24 specific code. (#850) 2026-03-20 12:28:56 +01:00
client.go mcp: fix race condition in ServerSession.startKeepalive (#856) 2026-04-03 20:17:49 +02:00
client_example_test.go mcp: fix goroutine leaks in unit tests (#496) 2025-10-16 11:13:30 -04:00
client_list_test.go mcp: allow any value for schemas (#517) 2025-09-26 11:55:54 -04:00
client_test.go mcp: add Extensions field to capabilities per SEP-2133 (#794) 2026-02-17 16:10:07 +01:00
cmd.go mcp: speed up TestCommandTransportDuration 2025-09-12 12:00:29 -04:00
cmd_export_test.go mcp: speed up TestCommandTransportDuration 2025-09-12 12:00:29 -04:00
cmd_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
conformance_test.go all: fix typos (#869) 2026-03-30 18:43:01 +02:00
content.go all: use case-sensitive JSON unmarshaling (#807) 2026-02-18 16:38:36 +01:00
content_nil_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
content_test.go mcp: switch icon theme to named string type (#733) 2025-12-19 16:44:10 -05:00
elicitation_test.go all: clean up Go 1.24 specific code. (#850) 2026-03-20 12:28:56 +01:00
error_test.go mcp: return input validation errors as tool results, not JSON-RPC errors (#863) 2026-03-31 10:14:36 +02:00
event.go mcp: use http.ResponseController to ensure writes are flushed (#870) 2026-03-31 13:13:16 +02:00
event_test.go mcp: handle empty chunks in MemoryEventStore (#862) 2026-03-27 16:58:03 +01:00
features.go mcp: minor cleanup of some TODOs (#332) 2025-08-20 12:55:19 -04:00
features_test.go mcp: change tool handler design (#325) 2025-08-20 19:36:34 -04:00
logging.go mcp: use rand.Text and slog.DiscardHandler over internal implementation (#773) 2026-02-03 14:23:59 +01:00
mcp.go mcp: fix inaccurate package doc (#534) 2025-09-26 14:16:10 -04:00
mcp_example_test.go docs: add feature and troubleshooting documentation (#463) 2025-09-16 11:13:40 -04:00
mcp_test.go mcp: preserve existing Content in SetError (#864) 2026-04-03 20:15:24 +02:00
prompt.go mcp: align PromptHandler args with other handlers (#348) 2025-08-22 10:44:05 -04:00
protocol.go mcp: preserve existing Content in SetError (#864) 2026-04-03 20:15:24 +02:00
protocol_test.go mcp: fix setProgressToken when Meta is nil (#846) 2026-03-18 10:57:45 +01:00
requests.go mcp: implement sampling with tools (#699) 2026-02-17 12:07:08 +01:00
resource.go mcp: finalize cleanup of Go 1.23, make Go version support explicit 2026-02-06 12:50:01 +01:00
resource_test.go mcp/Resource: Refactor URI Template Matching with uritemplate Library (#100) 2025-07-08 15:52:54 -04:00
sampling_test.go mcp: simplify and unify unit tests introduced for sampling with tools. (#799) 2026-03-02 10:55:35 +01:00
schema_cache.go perf: add schema caching to avoid repeated reflection (#685) 2026-01-20 20:16:46 -05:00
schema_cache_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
server.go mcp: fix race condition in ServerSession.startKeepalive (#856) 2026-04-03 20:17:49 +02:00
server_example_test.go doc: document logging (#497) 2025-09-19 11:58:58 -04:00
server_test.go mcp: fix race condition in ServerSession.startKeepalive (#856) 2026-04-03 20:17:49 +02:00
session.go mcp: support stateless streamable sessions (#277) 2025-08-11 16:43:46 -04:00
shared.go mcp: fix setProgressToken when Meta is nil (#846) 2026-03-18 10:57:45 +01:00
shared_test.go all: remove ServerRequest[T] for concrete T (#342) 2025-08-21 12:10:53 -04:00
sse.go mcp: use rand.Text and slog.DiscardHandler over internal implementation (#773) 2026-02-03 14:23:59 +01:00
sse_example_test.go SSE: Add support for SSE handler options (#508) 2025-09-19 13:01:57 -04:00
sse_test.go mcp: add Allow header to 405 responses per RFC 9110 §15.5.6 (#757) 2026-01-22 09:43:45 +01:00
streamable.go mcp: allow Content-Type parameters in streamable transport (#878) 2026-04-03 17:42:20 +02:00
streamable_bench_test.go all: modernize code (#868) 2026-03-31 10:58:02 +02:00
streamable_client.go mcp: export session missing error (#771) 2026-02-03 15:46:49 +01:00
streamable_client_test.go mcp: use http.ResponseController to ensure writes are flushed (#870) 2026-03-31 13:13:16 +02:00
streamable_example_test.go mcp: update latestProtocolVersion to 2025-11-25 (#724) 2026-03-03 10:30:11 +01:00
streamable_server.go mcp: simplify stream state management in streamable transport 2025-12-10 13:28:25 -05:00
streamable_test.go mcp: fix race condition in ServerSession.startKeepalive (#856) 2026-04-03 20:17:49 +02:00
tool.go all: use case-sensitive JSON unmarshaling (#807) 2026-02-18 16:38:36 +01:00
tool_example_test.go mcp: allow any value for schemas (#517) 2025-09-26 11:55:54 -04:00
tool_test.go all: expose JSON-RPC error codes, and support URLElicitationRequired errors (#655) 2025-12-01 11:29:17 -05:00
transport.go mcp: protect ioConn.protocolVersion with a mutex (#832) 2026-03-03 15:13:10 +01:00
transport_example_test.go all: clean up Go 1.24 specific code. (#850) 2026-03-20 12:28:56 +01:00
transport_test.go fix: handle Windows CRLF in MCP client(#664) (#665) 2025-12-01 12:16:39 -05:00
util.go all: use case-sensitive JSON unmarshaling (#807) 2026-02-18 16:38:36 +01:00