From f764a6e9838cf55810b199af16f4c598e24059e8 Mon Sep 17 00:00:00 2001 From: ugurtafrali Date: Wed, 15 Apr 2026 16:15:56 +0300 Subject: [PATCH] Add middleware to normalize Content-Type header Strip MIME parameters like charset from Content-Type so handlers don't reject requests with charset=utf-8 and similar. --- pkg/http/handler.go | 1 + pkg/http/middleware/content_type.go | 23 +++++++++ pkg/http/middleware/content_type_test.go | 61 ++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 pkg/http/middleware/content_type.go create mode 100644 pkg/http/middleware/content_type_test.go diff --git a/pkg/http/handler.go b/pkg/http/handler.go index 37906a03e6..41632b79df 100644 --- a/pkg/http/handler.go +++ b/pkg/http/handler.go @@ -127,6 +127,7 @@ func NewHTTPMcpHandler( func (h *Handler) RegisterMiddleware(r chi.Router) { r.Use( + middleware.NormalizeContentType, middleware.ExtractUserToken(h.oauthCfg), middleware.WithRequestConfig, middleware.WithMCPParse(), diff --git a/pkg/http/middleware/content_type.go b/pkg/http/middleware/content_type.go new file mode 100644 index 0000000000..4a30eee016 --- /dev/null +++ b/pkg/http/middleware/content_type.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "mime" + "net/http" + + "github.com/github/github-mcp-server/pkg/http/headers" +) + +// NormalizeContentType strips MIME parameters from the Content-Type header so +// "application/json; charset=utf-8" is treated as "application/json". +func NormalizeContentType(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if ct := r.Header.Get(headers.ContentTypeHeader); ct != "" { + mediaType, _, err := mime.ParseMediaType(ct) + if err == nil && mediaType != ct { + r = r.Clone(r.Context()) + r.Header.Set(headers.ContentTypeHeader, mediaType) + } + } + next.ServeHTTP(w, r) + }) +} diff --git a/pkg/http/middleware/content_type_test.go b/pkg/http/middleware/content_type_test.go new file mode 100644 index 0000000000..0bb872396c --- /dev/null +++ b/pkg/http/middleware/content_type_test.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/github/github-mcp-server/pkg/http/headers" + "github.com/stretchr/testify/assert" +) + +func TestNormalizeContentType(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "no parameters unchanged", + input: "application/json", + expected: "application/json", + }, + { + name: "charset parameter stripped", + input: "application/json; charset=utf-8", + expected: "application/json", + }, + { + name: "multiple parameters stripped", + input: "application/json; charset=utf-8; boundary=foo", + expected: "application/json", + }, + { + name: "empty header unchanged", + input: "", + expected: "", + }, + { + name: "other content type with params stripped", + input: "text/plain; charset=utf-8", + expected: "text/plain", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got string + handler := NormalizeContentType(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { + got = r.Header.Get(headers.ContentTypeHeader) + })) + + req := httptest.NewRequest(http.MethodPost, "/", nil) + if tt.input != "" { + req.Header.Set(headers.ContentTypeHeader, tt.input) + } + handler.ServeHTTP(httptest.NewRecorder(), req) + + assert.Equal(t, tt.expected, got) + }) + } +}