-
Notifications
You must be signed in to change notification settings - Fork 4k
Fix Content-Type rejection for application/json; charset=utf-8 #2362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
302c35b
eb23136
d3985f7
6ec21b0
452637d
709b07d
ed05d6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "mime" | ||
| "net/http" | ||
| ) | ||
|
|
||
| // NormalizeContentType is a middleware that normalizes the Content-Type header | ||
| // by stripping optional parameters (e.g. charset=utf-8) when the media type | ||
| // is "application/json". This works around strict Content-Type matching in | ||
| // the Go MCP SDK's StreamableHTTP handler which rejects valid JSON media | ||
| // types that include parameters. | ||
| // | ||
| // Per RFC 8259, JSON text exchanged between systems that are not part of a | ||
| // closed ecosystem MUST be encoded using UTF-8, so the charset parameter is | ||
| // redundant but MUST be accepted per HTTP semantics. | ||
| // | ||
| // See: https://github.com/github/github-mcp-server/issues/2333 | ||
| func NormalizeContentType(next http.Handler) http.Handler { | ||
| return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| if ct := r.Header.Get("Content-Type"); ct != "" { | ||
| mediaType, _, err := mime.ParseMediaType(ct) | ||
| if err == nil && mediaType == "application/json" { | ||
| r.Header.Set("Content-Type", "application/json") | ||
| } | ||
|
||
| } | ||
| next.ServeHTTP(w, r) | ||
| }) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| package middleware | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func TestNormalizeContentType(t *testing.T) { | ||
| tests := []struct { | ||
| name string | ||
| inputCT string | ||
| expectedCT string | ||
| }{ | ||
| { | ||
| name: "exact application/json unchanged", | ||
| inputCT: "application/json", | ||
| expectedCT: "application/json", | ||
| }, | ||
| { | ||
| name: "strips charset=utf-8", | ||
| inputCT: "application/json; charset=utf-8", | ||
| expectedCT: "application/json", | ||
| }, | ||
| { | ||
| name: "strips charset=UTF-8", | ||
| inputCT: "application/json; charset=UTF-8", | ||
| expectedCT: "application/json", | ||
| }, | ||
| { | ||
| name: "strips multiple parameters", | ||
| inputCT: "application/json; charset=utf-8; boundary=something", | ||
| expectedCT: "application/json", | ||
| }, | ||
| { | ||
| name: "non-json content type left unchanged", | ||
| inputCT: "text/plain; charset=utf-8", | ||
| expectedCT: "text/plain; charset=utf-8", | ||
| }, | ||
| { | ||
| name: "text/event-stream left unchanged", | ||
| inputCT: "text/event-stream", | ||
| expectedCT: "text/event-stream", | ||
| }, | ||
| { | ||
| name: "empty content type left unchanged", | ||
| inputCT: "", | ||
| expectedCT: "", | ||
| }, | ||
| } | ||
|
|
||
| for _, tt := range tests { | ||
| t.Run(tt.name, func(t *testing.T) { | ||
| var capturedCT string | ||
| inner := http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { | ||
| capturedCT = r.Header.Get("Content-Type") | ||
| }) | ||
|
|
||
| handler := NormalizeContentType(inner) | ||
| req := httptest.NewRequest(http.MethodPost, "/", nil) | ||
| if tt.inputCT != "" { | ||
| req.Header.Set("Content-Type", tt.inputCT) | ||
| } | ||
|
||
|
|
||
| handler.ServeHTTP(httptest.NewRecorder(), req) | ||
|
|
||
| assert.Equal(t, tt.expectedCT, capturedCT) | ||
| }) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.