Skip to content

Commit d56acce

Browse files
fix: skip leading UTF-8 BOM when detecting unframed JSON in MCP proxy
startsWithJSONObject treated a leading UTF-8 BOM as a non-JSON byte, so a BOM-prefixed unframed JSON body advertised as text/event-stream was handed to the SSE parser and silently dropped. Skip the BOM before scanning; the decode path (tryDecodeJSONRPCMessage) already strips it. Signed-off-by: RegondaChandan <sairegonda9@gmail.com>
1 parent be3f7b3 commit d56acce

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

internal/mcpproxy/handlers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,9 @@ func (m *mcpRequestContext) writeSingleJSONRPCMessage(ctx context.Context, w htt
838838
// startsWithJSONObject reports whether the stream begins with '{', consuming only
839839
// leading whitespace, which is insignificant to both SSE and JSON parsing.
840840
func startsWithJSONObject(br *bufio.Reader) bool {
841+
if buf, _ := br.Peek(len(utf8BOM)); bytes.Equal(buf, utf8BOM) {
842+
_, _ = br.Discard(len(utf8BOM))
843+
}
841844
for {
842845
b, err := br.ReadByte()
843846
if err != nil {

internal/mcpproxy/handlers_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,29 @@ func TestProxyResponseBody_SSEContentTypeUnframedJSONWithLeadingWhitespace(t *te
11101110
require.Contains(t, rr.Body.String(), id.Raw())
11111111
}
11121112

1113+
func TestProxyResponseBody_SSEContentTypeUnframedJSONWithBOM(t *testing.T) {
1114+
proxy := newTestMCPProxy()
1115+
1116+
id := mustJSONRPCRequestID()
1117+
resp := &jsonrpc.Response{ID: id, Result: []byte(`{"tools": [{"name": "bom-tool"}]}`)}
1118+
body, err := jsonrpc.EncodeMessage(resp)
1119+
require.NoError(t, err)
1120+
1121+
httpResp := &http.Response{
1122+
Header: http.Header{"Content-Type": []string{"text/event-stream"}},
1123+
Body: io.NopCloser(bytes.NewReader(append(append([]byte{}, utf8BOM...), body...))),
1124+
StatusCode: http.StatusOK,
1125+
}
1126+
1127+
rr := httptest.NewRecorder()
1128+
1129+
proxy.proxyResponseBody(t.Context(), nil, rr, httpResp, &jsonrpc.Request{ID: id}, filterapi.MCPBackend{Name: "mybackend"}) //nolint:errcheck
1130+
1131+
require.Contains(t, rr.Body.String(), "bom-tool")
1132+
require.Contains(t, rr.Body.String(), id.Raw())
1133+
require.Equal(t, "application/json", rr.Header().Get("Content-Type"))
1134+
}
1135+
11131136
func TestProxyResponseBody_SSEContentTypeInvalidJSONFallsBackToSSE(t *testing.T) {
11141137
// A body starting with '{' that is not a valid JSON-RPC message should fall back
11151138
// to SSE parsing without panicking or dropping the connection.

0 commit comments

Comments
 (0)