Skip to content

Commit c887c90

Browse files
committed
docs: document input-hardening limits in CHANGELOG and transports guide
Add CHANGELOG entries and docs/transports.md sections for the JSON-RPC batch size cap (maxBatchSize) and the StreamableHttpTransport request body size cap (maxBodyBytes), plus the type-based single-vs-batch message detection.
1 parent 7521f89 commit c887c90

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ All notable changes to `mcp/sdk` will be documented in this file.
3131
* [BC Break] `ResourceDefinition::__construct()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
3232
* [BC Break] `ResourceTemplate::__construct()` signature changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
3333
* [BC Break] `McpResource` and `McpResourceTemplate` attribute signatures changed — `$title` parameter added between `$name` and `$description`. Callers using positional arguments must switch to named arguments.
34+
* Harden JSON-RPC input parsing: single-message vs batch is now decided from the decoded JSON type (object → single, list array → batch) instead of the raw first byte. Scalars, empty payloads, and non-object batch elements are surfaced as `InvalidInputMessageException` entries instead of triggering warnings or a `TypeError`.
35+
* Add `maxBatchSize` (default `100`) to `MessageFactory` — oversized JSON-RPC batches are rejected before any message is constructed, guarding against amplification.
36+
* Add `maxBodyBytes` (default 4 MiB) to `StreamableHttpTransport` — POST bodies exceeding the cap are rejected with `413`. Unknown-size/chunked bodies are read incrementally and stopped at the cap so they cannot exhaust memory.
3437

3538
0.5.0
3639
-----

docs/transports.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ $transport = new StreamableHttpTransport(
112112
- **`streamFactory`** (optional): `StreamFactoryInterface` - PSR-17 factory for creating response body streams. Auto-discovered if not provided.
113113
- **`logger`** (optional): `LoggerInterface` - PSR-3 logger for debugging. Defaults to `NullLogger`.
114114
- **`middleware`** (optional): `iterable<MiddlewareInterface>|null` - PSR-15 middleware chain. `null` (omitted) installs the [default stack](#default-middleware). `[]` disables all defaults — useful when the surrounding application already handles CORS, host validation, etc.
115+
- **`maxBodyBytes`** (optional): `int` - Upper bound on the POST request body read, in bytes. Defaults to 4 MiB (`StreamableHttpTransport::DEFAULT_MAX_BODY_BYTES`). See [Request Body Size Limit](#request-body-size-limit).
115116

116117
### PSR-17 Auto-Discovery
117118

@@ -230,6 +231,40 @@ use Mcp\Server\Transport\Http\Middleware\ProtocolVersionMiddleware;
230231
new ProtocolVersionMiddleware(supportedVersions: [ProtocolVersion::V2025_11_25]);
231232
```
232233

234+
### Request Body Size Limit
235+
236+
`StreamableHttpTransport` caps the POST body it reads to guard against memory exhaustion from an oversized or
237+
unbounded (chunked) payload. The default cap is 4 MiB. A body over the cap is rejected with `413` and never reaches
238+
message parsing.
239+
240+
```php
241+
use Mcp\Server\Transport\StreamableHttpTransport;
242+
243+
// Raise the cap to 16 MiB
244+
$transport = new StreamableHttpTransport($request, maxBodyBytes: 16 * 1024 * 1024);
245+
```
246+
247+
When the request stream advertises a size, the transport rejects it up-front. Otherwise (e.g. chunked transfer with
248+
unknown size) the body is read incrementally and aborted as soon as it crosses the cap, so an unbounded stream cannot
249+
exhaust memory. A value below `1` throws `InvalidArgumentException`.
250+
251+
### JSON-RPC Batch Size Limit
252+
253+
A JSON-RPC batch (top-level array) is capped at 100 messages by default. Oversized batches are rejected before any
254+
message is constructed, so a single small request cannot amplify into arbitrarily many operations. The cap lives on
255+
`MessageFactory`:
256+
257+
```php
258+
use Mcp\JsonRpc\MessageFactory;
259+
260+
$factory = MessageFactory::make(maxBatchSize: 50);
261+
```
262+
263+
Single-message vs batch is determined from the decoded JSON type — a JSON object is a single message, a JSON array
264+
is a batch. Scalars, empty payloads, and non-object batch elements are returned as `InvalidInputMessageException`
265+
entries (the existing per-message error contract), not parse errors or crashes. A `maxBatchSize` below `1` throws
266+
`InvalidArgumentException`.
267+
233268
### Custom PSR-15 Middleware
234269

235270
`StreamableHttpTransport` accepts any PSR-15 middleware chain. To extend the defaults, spread them and append

0 commit comments

Comments
 (0)