2424from starlette .responses import Response
2525from starlette .types import Receive , Scope , Send
2626
27+ from mcp .server .http_body import DEFAULT_MAX_BODY_BYTES , BodyTooLargeError , read_request_body
2728from mcp .server .transport_security import TransportSecurityMiddleware , TransportSecuritySettings
2829from mcp .shared .message import ServerMessageMetadata , SessionMessage
2930from mcp .shared .version import SUPPORTED_PROTOCOL_VERSIONS
@@ -132,6 +133,7 @@ def __init__(
132133 event_store : EventStore | None = None ,
133134 security_settings : TransportSecuritySettings | None = None ,
134135 retry_interval : int | None = None ,
136+ max_body_bytes : int | None = DEFAULT_MAX_BODY_BYTES ,
135137 ) -> None :
136138 """Initialize a new StreamableHTTP server transport.
137139
@@ -148,18 +150,23 @@ def __init__(
148150 retry field. When set, the server will send a retry field in
149151 SSE priming events to control client reconnection timing for
150152 polling behavior. Only used when event_store is provided.
153+ max_body_bytes: Maximum size (in bytes) for JSON POST request bodies. Defaults
154+ to 1_000_000. Set to None to disable this guard.
151155
152156 Raises:
153157 ValueError: If the session ID contains invalid characters.
154158 """
155159 if mcp_session_id is not None and not SESSION_ID_PATTERN .fullmatch (mcp_session_id ):
156160 raise ValueError ("Session ID must only contain visible ASCII characters (0x21-0x7E)" )
161+ if max_body_bytes is not None and max_body_bytes <= 0 :
162+ raise ValueError ("max_body_bytes must be positive or None" )
157163
158164 self .mcp_session_id = mcp_session_id
159165 self .is_json_response_enabled = is_json_response_enabled
160166 self ._event_store = event_store
161167 self ._security = TransportSecurityMiddleware (security_settings )
162168 self ._retry_interval = retry_interval
169+ self ._max_body_bytes = max_body_bytes
163170 self ._request_streams : dict [
164171 RequestId ,
165172 tuple [
@@ -427,6 +434,43 @@ async def _validate_accept_header(self, request: Request, scope: Scope, send: Se
427434 return False
428435 return True
429436
437+ async def _parse_jsonrpc_message (
438+ self ,
439+ request : Request ,
440+ scope : Scope ,
441+ receive : Receive ,
442+ send : Send ,
443+ ) -> JSONRPCMessage | None :
444+ """Read + parse a JSON-RPC message from an HTTP request body."""
445+ try :
446+ body = await read_request_body (request , max_body_bytes = self ._max_body_bytes )
447+ except BodyTooLargeError as e :
448+ response = self ._create_error_response (
449+ f"Payload too large: { e } " ,
450+ HTTPStatus .REQUEST_ENTITY_TOO_LARGE ,
451+ headers = {"Connection" : "close" },
452+ )
453+ await response (scope , receive , send )
454+ return None
455+
456+ try :
457+ raw_message = pydantic_core .from_json (body )
458+ except ValueError as e :
459+ response = self ._create_error_response (f"Parse error: { str (e )} " , HTTPStatus .BAD_REQUEST , PARSE_ERROR )
460+ await response (scope , receive , send )
461+ return None
462+
463+ try :
464+ return jsonrpc_message_adapter .validate_python (raw_message , by_name = False )
465+ except ValidationError as e : # pragma: no cover
466+ response = self ._create_error_response (
467+ f"Validation error: { str (e )} " ,
468+ HTTPStatus .BAD_REQUEST ,
469+ INVALID_PARAMS ,
470+ )
471+ await response (scope , receive , send )
472+ return None
473+
430474 async def _handle_post_request (self , scope : Scope , request : Request , receive : Receive , send : Send ) -> None :
431475 """Handle POST requests containing JSON-RPC messages."""
432476 writer = self ._read_stream_writer
@@ -446,25 +490,8 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re
446490 await response (scope , receive , send )
447491 return
448492
449- # Parse the body - only read it once
450- body = await request .body ()
451-
452- try :
453- raw_message = pydantic_core .from_json (body )
454- except ValueError as e :
455- response = self ._create_error_response (f"Parse error: { str (e )} " , HTTPStatus .BAD_REQUEST , PARSE_ERROR )
456- await response (scope , receive , send )
457- return
458-
459- try :
460- message = jsonrpc_message_adapter .validate_python (raw_message , by_name = False )
461- except ValidationError as e : # pragma: no cover
462- response = self ._create_error_response (
463- f"Validation error: { str (e )} " ,
464- HTTPStatus .BAD_REQUEST ,
465- INVALID_PARAMS ,
466- )
467- await response (scope , receive , send )
493+ message = await self ._parse_jsonrpc_message (request , scope , receive , send )
494+ if message is None :
468495 return
469496
470497 # Check if this is an initialization request
0 commit comments