mcp: fix connectStandaloneSSE checking Content-Type header (#736)

Issue encountered: the MCP server request was unexpectedly redirected to
an HTML page. Because the client didn’t validate the response
Content-Type, it still entered handleSSE, which then kept treating the
HTML as an SSE stream and repeatedly re-requesting the same
page—creating an infinite request loop.
This commit is contained in:
Liu Shuang 2026-01-05 05:16:06 +08:00 • committed by GitHub
commit e66c23fae4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 19 additions and 8 deletions

View file

@ -1555,6 +1555,14 @@ func (c *streamableClientConn) connectStandaloneSSE() {
resp.Body.Close()
return
}
if resp.Header.Get("Content-Type") != "text/event-stream" {
// modelcontextprotocol/go-sdk#736: some servers return 200 OK or redirect with
// non-SSE content type instead of text/event-stream for the standalone
// SSE stream.
c.logger.Warn(fmt.Sprintf("got Content-Type %s instead of text/event-stream for standalone SSE stream", resp.Header.Get("Content-Type")))
resp.Body.Close()
return
}
if resp.StatusCode >= 400 && resp.StatusCode < 500 && !c.strict {
// modelcontextprotocol/go-sdk#393,#610: some servers return NotFound or
// other status codes instead of MethodNotAllowed for the standalone SSE

View file

@ -17,6 +17,7 @@ import (
"time"
"github.com/google/go-cmp/cmp"
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
)
@ -258,14 +259,16 @@ func TestStreamableClientGETHandling(t *testing.T) {
tests := []struct {
status int
wantErrorContaining string
contentType string
}{
{http.StatusOK, ""},
{http.StatusMethodNotAllowed, ""},
// The client error status code is not treated as an error in non-strict
// mode.
{http.StatusNotFound, ""},
{http.StatusBadRequest, ""},
{http.StatusInternalServerError, "standalone SSE"},
{http.StatusOK, "", "text/event-stream"},
{http.StatusMethodNotAllowed, "", "text/event-stream"},
//// The client error status code is not treated as an error in non-strict
//// mode.
{http.StatusNotFound, "", "text/event-stream"},
{http.StatusBadRequest, "", "text/event-stream"},
{http.StatusInternalServerError, "standalone SSE", "text/event-stream"},
{http.StatusOK, "", "text/html; charset=utf-8"},
}
for _, test := range tests {
@ -286,7 +289,7 @@ func TestStreamableClientGETHandling(t *testing.T) {
},
{"GET", "123", "", ""}: {
header: header{
"Content-Type": "text/event-stream",
"Content-Type": test.contentType,
},
status: test.status,
wantProtocolVersion: latestProtocolVersion,