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.
This commit is contained in:
parent
76d5a54125
commit
27f29c1cef
2 changed files with 11 additions and 2 deletions
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"maps"
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
@ -264,8 +265,8 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
|
||||||
}
|
}
|
||||||
// Validate 'Content-Type' header.
|
// Validate 'Content-Type' header.
|
||||||
if req.Method == http.MethodPost {
|
if req.Method == http.MethodPost {
|
||||||
contentType := req.Header.Get("Content-Type")
|
mediaType, _, err := mime.ParseMediaType(req.Header.Get("Content-Type"))
|
||||||
if contentType != "application/json" {
|
if err != nil || mediaType != "application/json" {
|
||||||
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
|
http.Error(w, "Content-Type must be 'application/json'", http.StatusUnsupportedMediaType)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -862,6 +862,14 @@ func TestStreamableServerTransport(t *testing.T) {
|
||||||
wantStatusCode: http.StatusOK,
|
wantStatusCode: http.StatusOK,
|
||||||
wantMessages: []jsonrpc.Message{resp(5, &CallToolResult{Content: []Content{}}, nil)},
|
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,
|
wantSessions: 1,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue