mcp: relax SSE connection response handling in non-strict mode (#611)

Some MCP servers do not support SSE, but when receiving a request, they
return 400 Bad Request or 404 Not Found instead of the standard 405
Method Not Allowed. The TypeScript and Python SDK ignore this error,
while the Go SDK terminates the session. This patch addresses the issue
in the non-strict mode.

Fixes #610

---------

Signed-off-by: Xie Zhihao <zhihao.xie@bytedance.com>
This commit is contained in:
Xie Zhihao 2025-11-03 23:12:28 +08:00 • committed by GitHub
commit d297272705
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 15 additions and 6 deletions

View file

@ -1377,13 +1377,14 @@ func (c *streamableClientConn) connectStandaloneSSE() {
resp.Body.Close() resp.Body.Close()
return return
} }
if resp.StatusCode == http.StatusNotFound && !c.strict { if resp.StatusCode >= 400 && resp.StatusCode < 500 && !c.strict {
// modelcontextprotocol/gosdk#393: some servers return NotFound instead // modelcontextprotocol/go-sdk#393,#610: some servers return NotFound or
// of MethodNotAllowed for the standalone SSE stream. // other status codes instead of MethodNotAllowed for the standalone SSE
// stream.
// //
// Treat this like MethodNotAllowed in non-strict mode. // Treat this like MethodNotAllowed in non-strict mode.
if c.logger != nil { if c.logger != nil {
c.logger.Warn("got 404 instead of 405 for standalone SSE stream") c.logger.Warn(fmt.Sprintf("got %d instead of 405 for standalone SSE stream", resp.StatusCode))
} }
resp.Body.Close() resp.Body.Close()
return return

View file

@ -239,7 +239,11 @@ func TestStreamableClientGETHandling(t *testing.T) {
}{ }{
{http.StatusOK, ""}, {http.StatusOK, ""},
{http.StatusMethodNotAllowed, ""}, {http.StatusMethodNotAllowed, ""},
{http.StatusBadRequest, "standalone SSE"}, // The client error status code is not treated as an error in non-strict
// mode.
{http.StatusNotFound, ""},
{http.StatusBadRequest, ""},
{http.StatusInternalServerError, "standalone SSE"},
} }
for _, test := range tests { for _, test := range tests {
@ -305,7 +309,11 @@ func TestStreamableClientStrictness(t *testing.T) {
{"strict initialized", true, http.StatusOK, http.StatusMethodNotAllowed, true}, {"strict initialized", true, http.StatusOK, http.StatusMethodNotAllowed, true},
{"unstrict initialized", false, http.StatusOK, http.StatusMethodNotAllowed, false}, {"unstrict initialized", false, http.StatusOK, http.StatusMethodNotAllowed, false},
{"strict GET", true, http.StatusAccepted, http.StatusNotFound, true}, {"strict GET", true, http.StatusAccepted, http.StatusNotFound, true},
{"unstrict GET", false, http.StatusOK, http.StatusNotFound, false}, // The client error status code is not treated as an error in non-strict
// mode.
{"unstrict GET on StatusNotFound", false, http.StatusOK, http.StatusNotFound, false},
{"unstrict GET on StatusBadRequest", false, http.StatusOK, http.StatusBadRequest, false},
{"GET on InternlServerError", false, http.StatusOK, http.StatusInternalServerError, true},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.label, func(t *testing.T) { t.Run(test.label, func(t *testing.T) {