Limit HTTP request bodies before MCP middleware parsing - #3111
Merged
SamMorrowDrums merged 6 commits intoAug 19, 2026
Merged
Conversation
Add WithMaxBodySize middleware that bounds the request body via http.MaxBytesReader (with a fast Content-Length rejection when known), registered first in RegisterMiddleware so it runs before any other middleware or the MCP SDK reads or buffers the body. WithMCPParse and WithScopeChallenge now return a clear 413 "request body too large" response when their body read hits the limit, instead of silently continuing. Defaults to 10 MiB, overridable via ServerConfig.MaxRequestBodyBytes. Fixes #3102 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Adds an early, configurable HTTP request-body limit to prevent unbounded buffering.
Changes:
- Adds
WithMaxBodySizewith a 10 MiB default. - Returns HTTP 413 for oversized bodies during middleware parsing.
- Adds unit and integration coverage for size limits and body replay.
Show a summary per file
| File | Description |
|---|---|
pkg/http/server.go |
Adds request-size configuration. |
pkg/http/handler.go |
Registers the limiter first. |
pkg/http/handler_test.go |
Tests handler-level enforcement. |
pkg/http/middleware/body_limit.go |
Implements bounded request bodies. |
pkg/http/middleware/body_limit_test.go |
Tests limit boundaries and lengths. |
pkg/http/middleware/mcp_parse.go |
Handles limit errors with 413. |
pkg/http/middleware/mcp_parse_test.go |
Tests parser composition. |
pkg/http/middleware/scope_challenge.go |
Handles limit errors in fallback parsing. |
pkg/http/middleware/scope_challenge_test.go |
Tests scope fallback composition. |
Review details
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- Files reviewed: 9/9 changed files
- Comments generated: 2
- Review effort level: Balanced
WithMCPParse and WithScopeChallenge tests for oversized requests were using strings.NewReader, which gives httptest.NewRequest a known Content-Length. That let WithMaxBodySize reject the request in its fast path before the request ever reached the middleware's own io.ReadAll/isMaxBytesError handling, leaving those branches untested. Reuse the existing unknownLengthBody helper (body_limit_test.go) so these tests actually reach the fallback read path and cover the *http.MaxBytesError handling added in WithMCPParse and WithScopeChallenge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-bodies-before-mcp-mid-d0a507
The middleware default was an arbitrary 10 MiB, above the 4 MiB the SDK already enforces, so it never changed which requests were accepted. Alias mcp.DefaultMaxRequestBodyBytes instead, making the earlier enforcement point behaviour-preserving by construction. Also pass the effective limit to StreamableHTTPOptions. Previously the SDK kept its own 4 MiB default, so a larger configured MaxRequestBodyBytes was silently capped; both layers now agree. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…pr-3111-body-limit
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>
SamMorrowDrums
deleted the
sammorrowdrums-issue-3102-limit-http-request-bodies-before-mcp-mid-d0a507
branch
August 19, 2026 14:30
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an application-level HTTP request-body size limit that is enforced before any middleware or the MCP SDK reads or buffers the body.
Previously
WithMCPParseand theWithScopeChallengefallback path calledio.ReadAll(r.Body)on an unbounded body with no size guard anywhere ahead of them in the HTTP stack. A large payload would be fully buffered in memory by those middlewares before the SDK's own guard inservePOSThad a chance to apply.Changes
middleware.WithMaxBodySize(maxBytes)(pkg/http/middleware/body_limit.go): wraps the request body withhttp.MaxBytesReader, rejecting immediately via a knownContent-Lengthfast path, or erroring on read once the limit is hit for chunked/unknown-length bodies. Registered as the first middleware inHandler.RegisterMiddleware, ahead ofExtractUserToken,WithMCPParse,WithPATScopes, andWithScopeChallenge.WithMCPParseandWithScopeChallengenow detect the resulting*http.MaxBytesErrorand return a clear413 Request Entity Too Large("request body too large") response, following the existinghttp.Error(w, ..., statusCode)convention used elsewhere in this package, instead of silently falling through.ServerConfig.MaxRequestBodyBytes(new, optional) lets operators override the default.mcp.StreamableHTTPOptions.MaxRequestBodyBytes. This is required rather than defensive — see below.Why 5 MiB
The limit applies to the total HTTP request, not to the content carried within it. The JSON-RPC frame, the
tools/callenvelope, per-item arrays, paths and other argument metadata, and JSON string escaping all consume part of the budget, so the usable content is meaningfully smaller than the limit.The MCP SDK already enforced 4 MiB on every request. Spending that entire budget on tool content leaves nothing for the envelope, so the default here is 5 MiB — a modest 1 MiB of headroom over the SDK default, chosen so that envelope overhead does not eat into the practical payload size. The intent is to keep the effective request ceiling in the same range as before while moving rejection earlier, not to raise the practical payload ceiling.
Because 5 MiB is above the SDK's own default, passing it to
StreamableHTTPOptions.MaxRequestBodyBytesis load-bearing: leaving the SDK on its default would silently cap requests at 4 MiB and the headroom would not exist. A test sends a request sized between the two limits to prove this.push_filesis the main tool that can approach the limit, since it batches every file into a single JSON-RPC request. Note that this is a bound on total request size, not a guarantee that any particular file size will fit. Larger uploads are better served by pushing over Git directly, by Git LFS for large binaries, or by the release asset APIs, none of which route through the MCP JSON-RPC endpoint.Operators who need a different bound can set
ServerConfig.MaxRequestBodyBytes, and that value applies at both enforcement points.Tests
pkg/http/middleware/body_limit_test.go: allowed request, boundary size (exact limit), oversized with knownContent-Length(rejected beforenextruns), oversized with unknown length (rejected on downstream read).pkg/http/middleware/mcp_parse_test.go: composition ofWithMaxBodySize+WithMCPParse— oversized body never reaches parsing/next handler; boundary-size body still parses and preserves the body.pkg/http/middleware/scope_challenge_test.go: composition ofWithMaxBodySize+WithScopeChallenge's fallback body-read path.pkg/http/handler_test.go: the default exceedsmcp.DefaultMaxRequestBodyBytes; an oversized request throughRegisterMiddleware/RegisterRoutesnever constructs the MCP server; a boundary-size request succeeds; a configured override reaches the SDK layer (verified by the SDK reporting the configured limit rather than its own default); and an unconfigured handler accepts a request sized between the SDK default and the 5 MiB default, which fails if the SDK option is dropped.Verification
script/lint— 0 issuesscript/test— full suite passesFixes #3102
Acknowledgments
Thanks @EQSTLab, @sondt99, @manus-use, and @YuvalElbar6 for the reports that led to this hardening.