1212namespace Mcp \Server \Transport ;
1313
1414use Http \Discovery \Psr17FactoryDiscovery ;
15+ use Mcp \Exception \InvalidArgumentException ;
1516use Mcp \Schema \JsonRpc \Error ;
17+ use Mcp \Server \Transport \Http \MiddlewareRequestHandler ;
1618use Psr \Http \Message \ResponseFactoryInterface ;
1719use Psr \Http \Message \ResponseInterface ;
1820use Psr \Http \Message \ServerRequestInterface ;
1921use Psr \Http \Message \StreamFactoryInterface ;
22+ use Psr \Http \Server \MiddlewareInterface ;
23+ use Psr \Http \Server \RequestHandlerInterface ;
2024use Psr \Log \LoggerInterface ;
2125use Symfony \Component \Uid \Uuid ;
2226
@@ -36,19 +40,22 @@ class StreamableHttpTransport extends BaseTransport
3640 /** @var array<string, string> */
3741 private array $ corsHeaders ;
3842
43+ /** @var list<MiddlewareInterface> */
44+ private array $ middleware = [];
45+
3946 /**
40- * @param array<string, string> $corsHeaders
47+ * @param array<string, string> $corsHeaders
48+ * @param iterable<MiddlewareInterface> $middleware
4149 */
4250 public function __construct (
43- private readonly ServerRequestInterface $ request ,
51+ private ServerRequestInterface $ request ,
4452 ?ResponseFactoryInterface $ responseFactory = null ,
4553 ?StreamFactoryInterface $ streamFactory = null ,
4654 array $ corsHeaders = [],
4755 ?LoggerInterface $ logger = null ,
56+ iterable $ middleware = [],
4857 ) {
4958 parent ::__construct ($ logger );
50- $ sessionIdString = $ this ->request ->getHeaderLine ('Mcp-Session-Id ' );
51- $ this ->sessionId = $ sessionIdString ? Uuid::fromString ($ sessionIdString ) : null ;
5259
5360 $ this ->responseFactory = $ responseFactory ?? Psr17FactoryDiscovery::findResponseFactory ();
5461 $ this ->streamFactory = $ streamFactory ?? Psr17FactoryDiscovery::findStreamFactory ();
@@ -59,6 +66,13 @@ public function __construct(
5966 'Access-Control-Allow-Headers ' => 'Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept ' ,
6067 'Access-Control-Expose-Headers ' => 'Mcp-Session-Id ' ,
6168 ], $ corsHeaders );
69+
70+ foreach ($ middleware as $ m ) {
71+ if (!$ m instanceof MiddlewareInterface) {
72+ throw new InvalidArgumentException ('Streamable HTTP middleware must implement Psr \\Http \\Server \\MiddlewareInterface. ' );
73+ }
74+ $ this ->middleware [] = $ m ;
75+ }
6276 }
6377
6478 public function send (string $ data , array $ context ): void
@@ -69,17 +83,15 @@ public function send(string $data, array $context): void
6983
7084 public function listen (): ResponseInterface
7185 {
72- return match ($ this ->request ->getMethod ()) {
73- 'OPTIONS ' => $ this ->handleOptionsRequest (),
74- 'POST ' => $ this ->handlePostRequest (),
75- 'DELETE ' => $ this ->handleDeleteRequest (),
76- default => $ this ->createErrorResponse (Error::forInvalidRequest ('Method Not Allowed ' ), 405 ),
77- };
86+ $ handler = $ this ->createRequestHandler ();
87+ $ response = $ handler ->handle ($ this ->request );
88+
89+ return $ this ->withCorsHeaders ($ response );
7890 }
7991
8092 protected function handleOptionsRequest (): ResponseInterface
8193 {
82- return $ this ->withCorsHeaders ( $ this -> responseFactory ->createResponse (204 ) );
94+ return $ this ->responseFactory ->createResponse (204 );
8395 }
8496
8597 protected function handlePostRequest (): ResponseInterface
@@ -92,7 +104,7 @@ protected function handlePostRequest(): ResponseInterface
92104 ->withHeader ('Content-Type ' , 'application/json ' )
93105 ->withBody ($ this ->streamFactory ->createStream ($ this ->immediateResponse ));
94106
95- return $ this -> withCorsHeaders ( $ response) ;
107+ return $ response ;
96108 }
97109
98110 if (null !== $ this ->sessionFiber ) {
@@ -112,15 +124,15 @@ protected function handleDeleteRequest(): ResponseInterface
112124
113125 $ this ->handleSessionEnd ($ this ->sessionId );
114126
115- return $ this ->withCorsHeaders ( $ this -> responseFactory ->createResponse (200 ) );
127+ return $ this ->responseFactory ->createResponse (200 );
116128 }
117129
118130 protected function createJsonResponse (): ResponseInterface
119131 {
120132 $ outgoingMessages = $ this ->getOutgoingMessages ($ this ->sessionId );
121133
122134 if (empty ($ outgoingMessages )) {
123- return $ this ->withCorsHeaders ( $ this -> responseFactory ->createResponse (202 ) );
135+ return $ this ->responseFactory ->createResponse (202 );
124136 }
125137
126138 $ messages = array_column ($ outgoingMessages , 'message ' );
@@ -134,7 +146,7 @@ protected function createJsonResponse(): ResponseInterface
134146 $ response = $ response ->withHeader ('Mcp-Session-Id ' , $ this ->sessionId ->toRfc4122 ());
135147 }
136148
137- return $ this -> withCorsHeaders ( $ response) ;
149+ return $ response ;
138150 }
139151
140152 protected function createStreamedResponse (): ResponseInterface
@@ -201,7 +213,7 @@ protected function createStreamedResponse(): ResponseInterface
201213 $ response = $ response ->withHeader ('Mcp-Session-Id ' , $ this ->sessionId ->toRfc4122 ());
202214 }
203215
204- return $ this -> withCorsHeaders ( $ response) ;
216+ return $ response ;
205217 }
206218
207219 protected function handleFiberTermination (): void
@@ -246,15 +258,42 @@ protected function createErrorResponse(Error $jsonRpcError, int $statusCode): Re
246258 $ response = $ response ->withHeader ('Allow ' , 'POST, DELETE, OPTIONS ' );
247259 }
248260
249- return $ this -> withCorsHeaders ( $ response) ;
261+ return $ response ;
250262 }
251263
252264 protected function withCorsHeaders (ResponseInterface $ response ): ResponseInterface
253265 {
254266 foreach ($ this ->corsHeaders as $ name => $ value ) {
255- $ response = $ response ->withHeader ($ name , $ value );
267+ if (!$ response ->hasHeader ($ name )) {
268+ $ response = $ response ->withHeader ($ name , $ value );
269+ }
256270 }
257271
258272 return $ response ;
259273 }
274+
275+ private function handleRequest (ServerRequestInterface $ request ): ResponseInterface
276+ {
277+ $ this ->request = $ request ;
278+ $ sessionIdString = $ request ->getHeaderLine ('Mcp-Session-Id ' );
279+ $ this ->sessionId = $ sessionIdString ? Uuid::fromString ($ sessionIdString ) : null ;
280+
281+ return match ($ request ->getMethod ()) {
282+ 'OPTIONS ' => $ this ->handleOptionsRequest (),
283+ 'POST ' => $ this ->handlePostRequest (),
284+ 'DELETE ' => $ this ->handleDeleteRequest (),
285+ default => $ this ->createErrorResponse (Error::forInvalidRequest ('Method Not Allowed ' ), 405 ),
286+ };
287+ }
288+
289+ private function createRequestHandler (): RequestHandlerInterface
290+ {
291+ /**
292+ * @see self::handleRequest
293+ */
294+ return new MiddlewareRequestHandler (
295+ $ this ->middleware ,
296+ \Closure::fromCallable ([$ this , 'handleRequest ' ]),
297+ );
298+ }
260299}
0 commit comments