Skip to content

Commit b3ecab4

Browse files
Set the request-body limit to 5 MiB at both layers
Bounds the total HTTP request, so allow modest headroom over the MCP SDK's 4 MiB default for JSON-RPC and tool-call envelope overhead rather than spending the whole budget on tool content. Because the limit now exceeds the SDK default, passing it to StreamableHTTPOptions is load-bearing: without it the SDK would cap requests at 4 MiB and the headroom would not exist. Covered by a test that sends a request between the two limits. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent b0b41a5 commit b3ecab4

3 files changed

Lines changed: 38 additions & 16 deletions

File tree

pkg/http/handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
250250
return ghServer
251251
}, &mcp.StreamableHTTPOptions{
252252
Stateless: true,
253-
// Keep the SDK's own guard in step with the middleware, otherwise its
254-
// default would silently cap a larger configured limit.
253+
// Required, not just belt-and-braces: the effective limit exceeds the
254+
// SDK's own default, which would otherwise cap it.
255255
MaxRequestBodyBytes: h.maxRequestBodyBytes(),
256256
})
257257

pkg/http/handler_test.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
ghcontext "github.com/github/github-mcp-server/pkg/context"
1616
"github.com/github/github-mcp-server/pkg/github"
1717
"github.com/github/github-mcp-server/pkg/http/headers"
18+
"github.com/github/github-mcp-server/pkg/http/middleware"
1819
"github.com/github/github-mcp-server/pkg/inventory"
1920
"github.com/github/github-mcp-server/pkg/scopes"
2021
"github.com/github/github-mcp-server/pkg/translations"
@@ -1289,12 +1290,16 @@ func TestUIMetaStrippedWhenClientLacksCapability(t *testing.T) {
12891290
require.NotNil(t, unknown[0].Tool.Meta["ui"], "_meta.ui should be preserved when capability is unknown and FF is on")
12901291
}
12911292

1292-
// TestMaxRequestBodyBytes checks the effective limit tracks the MCP SDK
1293-
// default and honours an operator override.
1293+
// TestMaxRequestBodyBytes checks the effective limit and, critically, that it
1294+
// sits above the MCP SDK default — which is why it must also be passed to
1295+
// StreamableHTTPOptions rather than left to the SDK.
12941296
func TestMaxRequestBodyBytes(t *testing.T) {
1295-
t.Run("defaults to the MCP SDK limit", func(t *testing.T) {
1297+
t.Run("default leaves headroom above the MCP SDK limit", func(t *testing.T) {
12961298
h := &Handler{config: &ServerConfig{}}
1297-
assert.Equal(t, int64(mcp.DefaultMaxRequestBodyBytes), h.maxRequestBodyBytes())
1299+
1300+
assert.Equal(t, int64(5<<20), h.maxRequestBodyBytes())
1301+
assert.Greater(t, h.maxRequestBodyBytes(), int64(mcp.DefaultMaxRequestBodyBytes),
1302+
"the default intentionally exceeds the SDK limit, so the SDK must be told about it")
12981303
})
12991304

13001305
t.Run("configured value overrides the default", func(t *testing.T) {
@@ -1320,11 +1325,11 @@ func TestMaxRequestBodySizeEnforcement(t *testing.T) {
13201325
return strings.Replace(payload, "PADDING", "PADDING"+pad, 1)
13211326
}
13221327

1323-
newHandler := func(t *testing.T, mcpServerFactoryCalled *bool) *Handler {
1328+
newHandler := func(t *testing.T, maxBytes int64, mcpServerFactoryCalled *bool) *Handler {
13241329
t.Helper()
13251330
return NewHTTPMcpHandler(
13261331
context.Background(),
1327-
&ServerConfig{Version: "test", MaxRequestBodyBytes: limit},
1332+
&ServerConfig{Version: "test", MaxRequestBodyBytes: maxBytes},
13281333
nil,
13291334
translations.NullTranslationHelper,
13301335
slog.Default(),
@@ -1359,7 +1364,7 @@ func TestMaxRequestBodySizeEnforcement(t *testing.T) {
13591364

13601365
t.Run("middleware rejects an oversized request before the MCP server is built", func(t *testing.T) {
13611366
var mcpServerFactoryCalled bool
1362-
r := newRouter(newHandler(t, &mcpServerFactoryCalled))
1367+
r := newRouter(newHandler(t, limit, &mcpServerFactoryCalled))
13631368

13641369
body := buildBody(limit + 1)
13651370
require.Greater(t, len(body), limit)
@@ -1374,7 +1379,7 @@ func TestMaxRequestBodySizeEnforcement(t *testing.T) {
13741379

13751380
t.Run("request at the configured limit succeeds", func(t *testing.T) {
13761381
var mcpServerFactoryCalled bool
1377-
r := newRouter(newHandler(t, &mcpServerFactoryCalled))
1382+
r := newRouter(newHandler(t, limit, &mcpServerFactoryCalled))
13781383

13791384
body := buildBody(limit)
13801385
require.Len(t, body, limit)
@@ -1387,7 +1392,7 @@ func TestMaxRequestBodySizeEnforcement(t *testing.T) {
13871392
})
13881393

13891394
t.Run("SDK handler enforces the configured limit when the middleware is bypassed", func(t *testing.T) {
1390-
h := newHandler(t, nil)
1395+
h := newHandler(t, limit, nil)
13911396

13921397
body := buildBody(limit + 1)
13931398
require.Greater(t, len(body), limit)
@@ -1399,4 +1404,21 @@ func TestMaxRequestBodySizeEnforcement(t *testing.T) {
13991404
assert.Contains(t, rr.Body.String(), fmt.Sprintf("request body exceeds %d bytes", limit),
14001405
"the SDK should report the configured limit, not its own default")
14011406
})
1407+
1408+
// The default headroom only exists if it reaches the SDK as well; leaving
1409+
// the SDK on its own default would silently reject this request.
1410+
t.Run("unconfigured handler accepts a request above the MCP SDK limit", func(t *testing.T) {
1411+
var mcpServerFactoryCalled bool
1412+
r := newRouter(newHandler(t, 0, &mcpServerFactoryCalled))
1413+
1414+
body := buildBody(mcp.DefaultMaxRequestBodyBytes + 1024)
1415+
require.Greater(t, int64(len(body)), int64(mcp.DefaultMaxRequestBodyBytes))
1416+
require.Less(t, int64(len(body)), middleware.DefaultMaxRequestBodyBytes)
1417+
1418+
rr := httptest.NewRecorder()
1419+
r.ServeHTTP(rr, newRequest(body))
1420+
1421+
assert.Equal(t, http.StatusOK, rr.Code, "response body: %s", rr.Body.String())
1422+
assert.True(t, mcpServerFactoryCalled, "the MCP server should be constructed for an allowed request")
1423+
})
14021424
}

pkg/http/middleware/body_limit.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package middleware
33
import (
44
"errors"
55
"net/http"
6-
7-
"github.com/modelcontextprotocol/go-sdk/mcp"
86
)
97

10-
// DefaultMaxRequestBodyBytes tracks the MCP SDK's own request-body limit, so
11-
// enforcing it earlier in the chain does not change which requests are accepted.
12-
const DefaultMaxRequestBodyBytes int64 = mcp.DefaultMaxRequestBodyBytes
8+
// DefaultMaxRequestBodyBytes bounds the total HTTP request, not just the tool
9+
// payload within it. It sits modestly above the MCP SDK's own default to leave
10+
// room for JSON-RPC and tool-call envelope overhead; because it is the larger
11+
// of the two, callers must also pass it to the SDK or the SDK would cap it.
12+
const DefaultMaxRequestBodyBytes int64 = 5 << 20 // 5 MiB
1313

1414
// WithMaxBodySize bounds the size of the request body. It must be registered
1515
// before any middleware that reads or buffers the body (WithMCPParse,

0 commit comments

Comments
 (0)