mcp: preserve SSE events buffered after the endpoint (#1270)

SSEClientTransport.Connect creates one event scanner to read the
endpoint event
and a second scanner to read subsequent messages. Each scanner owns a
buffered
reader, so bytes read ahead by the first scanner are discarded when it
is
abandoned. A message coalesced with the endpoint can therefore disappear
while
the connection remains open.

Reuse the event scanner across the endpoint handshake and the
message-reading
goroutine. This preserves unread bytes without changing the public API.

The regression test exercises Connect and Read with a controlled HTTP
response
body, covering both a message coalesced with the endpoint and a message
arriving
after Connect returns. On unmodified main, the separate-message control
passes
and the coalesced case times out; both pass with the fix.

Validation on linux/amd64 with Go 1.26.8:
- go test ./...
- go test -race ./...
- go vet ./...
- staticcheck v0.6.1 ./...
- gofmt and git diff --check

The HTTP response boundary is simulated; no third-party server or proxy
was
used. This change concerns the legacy SSE transport. The Streamable HTTP
conformance suite was not run.
This commit is contained in:
jackyyy 2026-09-15 17:17:47 +08:00 • committed by GitHub
commit c1918e19ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -437,9 +437,11 @@ func (c *SSEClientTransport) Connect(ctx context.Context) (Connection, error) {
maxEventSize = DefaultMaxEventSize
}
// Reuse the scanner so events buffered after the endpoint are not lost.
events := scanEventsLimited(resp.Body, maxEventSize)
msgEndpoint, err := func() (*url.URL, error) {
var evt Event
for evt, err = range scanEventsLimited(resp.Body, maxEventSize) {
for evt, err = range events {
break
}
if err != nil {
@ -468,7 +470,7 @@ func (c *SSEClientTransport) Connect(ctx context.Context) (Connection, error) {
go func() {
defer s.Close() // close the transport when the GET exits
for evt, err := range scanEventsLimited(resp.Body, maxEventSize) {
for evt, err := range events {
if err != nil {
return
}