Skip to content

Commit 213a374

Browse files
Theodor N. EngøyTheodor N. Engøy
authored andcommitted
mcpserver: expose max_body_bytes transport option
1 parent 9e52110 commit 213a374

File tree

2 files changed

+19
-1
lines changed

2 files changed

+19
-1
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,6 +1429,8 @@ app = Starlette(
14291429
)
14301430
```
14311431

1432+
Security note: StreamableHTTP enforces a default `max_body_bytes=1_000_000` limit for incoming `application/json` POST bodies (413 on oversized payloads). Override via `mcp.streamable_http_app(max_body_bytes=...)` or `mcp.run("streamable-http", ..., max_body_bytes=...)`. Set to `None` to disable (not recommended).
1433+
14321434
_Full example: [examples/snippets/servers/streamable_http_basic_mounting.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/streamable_http_basic_mounting.py)_
14331435
<!-- /snippet-source -->
14341436

@@ -1601,6 +1603,8 @@ app = Starlette(
16011603
app.router.routes.append(Host('mcp.acme.corp', app=mcp.sse_app()))
16021604
```
16031605

1606+
Security note: SSE message endpoints enforce a default `max_body_bytes=1_000_000` limit for incoming `application/json` POST bodies (413 on oversized payloads). Override via `mcp.sse_app(max_body_bytes=...)` or `mcp.run("sse", ..., max_body_bytes=...)`. Set to `None` to disable (not recommended).
1607+
16041608
You can also mount multiple MCP servers at different sub-paths. The SSE transport automatically detects the mount path via ASGI's `root_path` mechanism, so message endpoints are correctly routed:
16051609

16061610
```python

src/mcp/server/mcpserver/server.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from mcp.server.context import LifespanContextT, RequestT, ServerRequestContext
2929
from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, UrlElicitationResult, elicit_with_validation
3030
from mcp.server.elicitation import elicit_url as _elicit_url
31+
from mcp.server.http_body import DEFAULT_MAX_BODY_BYTES
3132
from mcp.server.lowlevel.helper_types import ReadResourceContents
3233
from mcp.server.lowlevel.server import LifespanResultT, Server
3334
from mcp.server.lowlevel.server import lifespan as default_lifespan
@@ -223,6 +224,7 @@ def run(
223224
sse_path: str = ...,
224225
message_path: str = ...,
225226
transport_security: TransportSecuritySettings | None = ...,
227+
max_body_bytes: int | None = ...,
226228
) -> None: ...
227229

228230
@overload
@@ -238,6 +240,7 @@ def run(
238240
event_store: EventStore | None = ...,
239241
retry_interval: int | None = ...,
240242
transport_security: TransportSecuritySettings | None = ...,
243+
max_body_bytes: int | None = ...,
241244
) -> None: ...
242245

243246
def run(
@@ -725,6 +728,7 @@ async def run_sse_async( # pragma: no cover
725728
sse_path: str = "/sse",
726729
message_path: str = "/messages/",
727730
transport_security: TransportSecuritySettings | None = None,
731+
max_body_bytes: int | None = DEFAULT_MAX_BODY_BYTES,
728732
) -> None:
729733
"""Run the server using SSE transport."""
730734
import uvicorn
@@ -734,6 +738,7 @@ async def run_sse_async( # pragma: no cover
734738
message_path=message_path,
735739
transport_security=transport_security,
736740
host=host,
741+
max_body_bytes=max_body_bytes,
737742
)
738743

739744
config = uvicorn.Config(
@@ -756,6 +761,7 @@ async def run_streamable_http_async( # pragma: no cover
756761
event_store: EventStore | None = None,
757762
retry_interval: int | None = None,
758763
transport_security: TransportSecuritySettings | None = None,
764+
max_body_bytes: int | None = DEFAULT_MAX_BODY_BYTES,
759765
) -> None:
760766
"""Run the server using StreamableHTTP transport."""
761767
import uvicorn
@@ -768,6 +774,7 @@ async def run_streamable_http_async( # pragma: no cover
768774
retry_interval=retry_interval,
769775
transport_security=transport_security,
770776
host=host,
777+
max_body_bytes=max_body_bytes,
771778
)
772779

773780
config = uvicorn.Config(
@@ -785,6 +792,7 @@ def sse_app(
785792
sse_path: str = "/sse",
786793
message_path: str = "/messages/",
787794
transport_security: TransportSecuritySettings | None = None,
795+
max_body_bytes: int | None = DEFAULT_MAX_BODY_BYTES,
788796
host: str = "127.0.0.1",
789797
) -> Starlette:
790798
"""Return an instance of the SSE server app."""
@@ -796,7 +804,11 @@ def sse_app(
796804
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
797805
)
798806

799-
sse = SseServerTransport(message_path, security_settings=transport_security)
807+
sse = SseServerTransport(
808+
message_path,
809+
security_settings=transport_security,
810+
max_body_bytes=max_body_bytes,
811+
)
800812

801813
async def handle_sse(scope: Scope, receive: Receive, send: Send): # pragma: no cover
802814
# Add client ID from auth context into request context if available
@@ -914,6 +926,7 @@ def streamable_http_app(
914926
event_store: EventStore | None = None,
915927
retry_interval: int | None = None,
916928
transport_security: TransportSecuritySettings | None = None,
929+
max_body_bytes: int | None = DEFAULT_MAX_BODY_BYTES,
917930
host: str = "127.0.0.1",
918931
) -> Starlette:
919932
"""Return an instance of the StreamableHTTP server app."""
@@ -924,6 +937,7 @@ def streamable_http_app(
924937
event_store=event_store,
925938
retry_interval=retry_interval,
926939
transport_security=transport_security,
940+
max_body_bytes=max_body_bytes,
927941
host=host,
928942
auth=self.settings.auth,
929943
token_verifier=self._token_verifier,

0 commit comments

Comments
 (0)