@@ -139,45 +139,50 @@ $transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory)
139139
140140### CORS Configuration
141141
142- The transport sets secure CORS defaults that can be customized or disabled:
142+ CORS is handled by the ` CorsMiddleware ` , which is automatically prepended to the middleware chain. By default,
143+ no ` Access-Control-Allow-Origin ` header is set, which effectively blocks cross-origin browser requests.
143144
144145``` php
145- // Default CORS headers (backward compatible)
146- $transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
146+ use Mcp\Server\Transport\Http\Middleware\CorsMiddleware;
147+ use Mcp\Server\Transport\StreamableHttpTransport;
148+
149+ // Default: cross-origin requests are blocked (no Access-Control-Allow-Origin header)
150+ $transport = new StreamableHttpTransport($request);
147151
148- // Restrict to specific origin
152+ // Allow specific origins
149153$transport = new StreamableHttpTransport(
150154 $request,
151- $responseFactory,
152- $streamFactory ,
153- ['Access-Control-Allow-Origin' => 'https://myapp.com']
155+ corsMiddleware: new CorsMiddleware(
156+ allowedOrigins: ['https://myapp.com', 'https://staging.myapp.com'] ,
157+ ),
154158);
155159
156- // Disable CORS for proxy scenarios
160+ // Allow all origins (e.g. for development)
157161$transport = new StreamableHttpTransport(
158162 $request,
159- $responseFactory,
160- $streamFactory,
161- ['Access-Control-Allow-Origin' => '']
163+ corsMiddleware: new CorsMiddleware(allowedOrigins: ['*']),
162164);
163165
164- // Custom headers with logger
166+ // Full configuration
165167$transport = new StreamableHttpTransport(
166168 $request,
167- $responseFactory,
168- $streamFactory,
169- [
170- 'Access-Control-Allow-Origin' => 'https://api.example.com',
171- 'Access-Control-Max-Age' => '86400'
172- ],
173- $logger
169+ corsMiddleware: new CorsMiddleware(
170+ allowedOrigins: ['https://myapp.com'],
171+ allowedMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
172+ allowedHeaders: ['Accept', 'Authorization', 'Content-Type', 'Last-Event-ID', 'Mcp-Protocol-Version', 'Mcp-Session-Id'],
173+ exposedHeaders: ['Mcp-Session-Id'],
174+ ),
174175);
175176```
176177
177- Default CORS headers:
178- - ` Access-Control-Allow-Origin: * `
178+ Default CORS headers (always set unless overridden by middleware):
179179- ` Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS `
180- - ` Access-Control-Allow-Headers: Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept `
180+ - ` Access-Control-Allow-Headers: Accept, Authorization, Content-Type, Last-Event-ID, Mcp-Protocol-Version, Mcp-Session-Id `
181+ - ` Access-Control-Expose-Headers: Mcp-Session-Id `
182+
183+ The ` CorsMiddleware ` is always the first middleware in the chain, ensuring CORS headers are applied to all responses —
184+ including those from other middleware that short-circuit (e.g. an auth middleware returning ` 401 ` ). The transport itself
185+ handles ` OPTIONS ` preflight requests by returning a ` 204 ` response.
181186
182187### PSR-15 Middleware
183188
@@ -209,15 +214,13 @@ final class AuthMiddleware implements MiddlewareInterface
209214
210215$transport = new StreamableHttpTransport(
211216 $request,
212- $responseFactory,
213- $streamFactory,
214- [],
215- $logger,
216- [new AuthMiddleware($responseFactory)],
217+ logger: $logger,
218+ middleware: [new AuthMiddleware($responseFactory)],
217219);
218220```
219221
220- If middleware returns a response, the transport will still ensure CORS headers are present unless you set them yourself.
222+ The ` CorsMiddleware ` is always prepended before user middleware, so CORS headers are applied to all responses
223+ even when middleware short-circuits.
221224
222225### Architecture
223226
0 commit comments