1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Server\Transport\Http\Middleware;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Http\HttpStatus;
18: use Nexus\Mcp\Core\Schema\Error\InvalidRequestError;
19: use Nexus\Mcp\Core\Schema\JsonRpc\JsonRpcErrorResponse;
20: use Psr\Http\Message\ResponseFactoryInterface;
21: use Psr\Http\Message\ResponseInterface;
22: use Psr\Http\Message\ServerRequestInterface;
23: use Psr\Http\Message\StreamFactoryInterface;
24: use Psr\Http\Server\MiddlewareInterface;
25: use Psr\Http\Server\RequestHandlerInterface;
26:
27: /**
28: * Rejects a request whose body exceeds a configured byte cap before it reaches the transport.
29: *
30: * An oversized body is answered with an id-less JSON-RPC error on HTTP 413, sparing the transport the cost of
31: * stringifying and parsing it. The cap is measured against the buffered body size. A body whose size cannot be
32: * determined passes through, leaving a streaming cap to the HTTP server.
33: */
34: final readonly class RequestBodySizeLimitMiddleware implements MiddlewareInterface
35: {
36: /**
37: * @var int<0, max>
38: */
39: private int $maxBytes;
40:
41: /**
42: * @param int $maxBytes Maximum permitted request body size in bytes
43: */
44: public function __construct(
45: int $maxBytes,
46: private ResponseFactoryInterface $responseFactory,
47: private StreamFactoryInterface $streamFactory,
48: ) {
49: Assert::that($maxBytes)->isNaturalInt('The maximum request body size must be a non-negative integer, {value} given.');
50:
51: $this->maxBytes = $maxBytes;
52: }
53:
54: #[\Override]
55: public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
56: {
57: $size = $request->getBody()->getSize();
58:
59: if (null !== $size && $size > $this->maxBytes) {
60: return $this->reject();
61: }
62:
63: return $handler->handle($request);
64: }
65:
66: private function reject(): ResponseInterface
67: {
68: $envelope = new JsonRpcErrorResponse(
69: id: null,
70: error: new InvalidRequestError(message: 'The request body exceeds the permitted size.'),
71: );
72:
73: return $this->responseFactory->createResponse(HttpStatus::ContentTooLarge->value)
74: ->withHeader('Content-Type', 'application/json')
75: ->withBody($this->streamFactory->createStream(json_encode($envelope, \JSON_THROW_ON_ERROR)))
76: ;
77: }
78: }
79: