From 27f29c1cef3b6b8dcab41e3e1997fc773595b6d5 Mon Sep 17 00:00:00 2001 From: Rafael Dantas Justo Date: Fri, 3 Apr 2026 12:42:20 -0300 Subject: [PATCH] mcp: allow Content-Type parameters in streamable transport (#878) Use `mime.ParseMediaType` to parse the `Content-Type` header instead of exact string matching. Previously, the server required an exact match on `application/json`. This allows the server to accept `application/json` with parameters (e.g. `charset=utf-8`), even though [they are not required](https://datatracker.ietf.org/doc/html/rfc4627.html#section-6), following the robustness principle. Per [RFC 8259 (section 8.1)](https://datatracker.ietf.org/doc/html/rfc8259.html#section-8.1), `application/json` is always UTF-8, so such parameters are technically redundant. However, parsing the media type makes the server more tolerant of client behaviour, improving compatibility without compromising security. --- mcp/streamable.go | 5 +++-- mcp/streamable_test.go | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/mcp/streamable.go b/mcp/streamable.go index b0b1795..76f2b0e 100644 --- a/mcp/streamable.go +++ b/mcp/streamable.go @@ -20,6 +20,7 @@ import ( "maps" "math" "math/rand/v2" + "mime" "net" "net/http" "slices" @@ -264,8 +265,8 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque } // Validate 'Content-Type' header. if req.Method == http.MethodPost { - contentType := req.Header.Get("Content-Type") - if contentType != "application/json" { + mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type")) + if err != nil || mediaType != "application/json" { http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType) return } diff --git a/mcp/streamable_test.go b/mcp/streamable_test.go index d6eb8e9..a275d3c 100644 --- a/mcp/streamable_test.go +++ b/mcp/streamable_test.go @@ -862,6 +862,14 @@ func TestStreamableServerTransport(t *testing.T) { wantStatusCode: http.StatusOK, wantMessages: []jsonrpc.Message{resp(5, &CallToolResult{Content: []Content{}}, nil)}, }, + { + // Correct Content-Type with parameters should pass. + method: "POST", + headers: http.Header{"Content-Type": {"application/json; charset=utf-8"}}, + messages: []jsonrpc.Message{req(5, "tools/call", &CallToolParams{Name: "tool"})}, + wantStatusCode: http.StatusOK, + wantMessages: []jsonrpc.Message{resp(5, &CallToolResult{Content: []Content{}}, nil)}, + }, }, wantSessions: 1, },