Skip to content

Commit 48a19b9

Browse files
[Server] Make CORS headers configurable in StreamableHttpTransport (#118)
* feat: make CORS headers configurable in StreamableHttpTransport * docs: add CORS configuration documentation for HTTP transport * refactor: streamline request creation and transport initialization in HTTP client example
1 parent 42b5261 commit 48a19b9

File tree

3 files changed

+59
-12
lines changed

3 files changed

+59
-12
lines changed

docs/transports.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ $transport = new StreamableHttpTransport(
110110
- **`request`** (required): `ServerRequestInterface` - The incoming PSR-7 HTTP request
111111
- **`responseFactory`** (optional): `ResponseFactoryInterface` - PSR-17 factory for creating HTTP responses. Auto-discovered if not provided.
112112
- **`streamFactory`** (optional): `StreamFactoryInterface` - PSR-17 factory for creating response body streams. Auto-discovered if not provided.
113+
- **`corsHeaders`** (optional): `array` - Custom CORS headers to override defaults. Merges with secure defaults. Defaults to `[]`.
113114
- **`logger`** (optional): `LoggerInterface` - PSR-3 logger for debugging. Defaults to `NullLogger`.
114115

115116
### PSR-17 Auto-Discovery
@@ -136,6 +137,48 @@ $psr17Factory = new Psr17Factory();
136137
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
137138
```
138139

140+
### CORS Configuration
141+
142+
The transport sets secure CORS defaults that can be customized or disabled:
143+
144+
```php
145+
// Default CORS headers (backward compatible)
146+
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
147+
148+
// Restrict to specific origin
149+
$transport = new StreamableHttpTransport(
150+
$request,
151+
$responseFactory,
152+
$streamFactory,
153+
['Access-Control-Allow-Origin' => 'https://myapp.com']
154+
);
155+
156+
// Disable CORS for proxy scenarios
157+
$transport = new StreamableHttpTransport(
158+
$request,
159+
$responseFactory,
160+
$streamFactory,
161+
['Access-Control-Allow-Origin' => '']
162+
);
163+
164+
// Custom headers with logger
165+
$transport = new StreamableHttpTransport(
166+
$request,
167+
$responseFactory,
168+
$streamFactory,
169+
[
170+
'Access-Control-Allow-Origin' => 'https://api.example.com',
171+
'Access-Control-Max-Age' => '86400'
172+
],
173+
$logger
174+
);
175+
```
176+
177+
Default CORS headers:
178+
- `Access-Control-Allow-Origin: *`
179+
- `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`
181+
139182
### Architecture
140183

141184
The HTTP transport doesn't run its own web server. Instead, it processes PSR-7 requests and returns PSR-7 responses that

examples/http-client-communication/server.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
require_once dirname(__DIR__).'/bootstrap.php';
1313
chdir(__DIR__);
1414

15+
use Http\Discovery\Psr17Factory;
1516
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
1617
use Mcp\Schema\Content\TextContent;
1718
use Mcp\Schema\Enum\LoggingLevel;
@@ -21,19 +22,16 @@
2122
use Mcp\Server\ClientGateway;
2223
use Mcp\Server\Session\FileSessionStore;
2324
use Mcp\Server\Transport\StreamableHttpTransport;
24-
use Nyholm\Psr7\Factory\Psr17Factory;
25-
use Nyholm\Psr7Server\ServerRequestCreator;
2625

27-
$psr17Factory = new Psr17Factory();
28-
$creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
29-
$request = $creator->fromGlobals();
26+
$request = (new Psr17Factory())->createServerRequestFromGlobals();
3027

3128
$sessionDir = __DIR__.'/sessions';
3229
$capabilities = new ServerCapabilities(logging: true, tools: true);
30+
$logger = logger();
3331

3432
$server = Server::builder()
3533
->setServerInfo('HTTP Client Communication Demo', '1.0.0')
36-
->setLogger(logger())
34+
->setLogger($logger)
3735
->setContainer(container())
3836
->setSession(new FileSessionStore($sessionDir))
3937
->setCapabilities($capabilities)
@@ -117,7 +115,7 @@ function (string $serviceName, ClientGateway $client): array {
117115
)
118116
->build();
119117

120-
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory, logger());
118+
$transport = new StreamableHttpTransport($request, logger: $logger);
121119

122120
$response = $server->run($transport);
123121

src/Server/Transport/StreamableHttpTransport.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ class StreamableHttpTransport extends BaseTransport implements TransportInterfac
3535
private ?int $immediateStatusCode = null;
3636

3737
/** @var array<string, string> */
38-
private array $corsHeaders = [
39-
'Access-Control-Allow-Origin' => '*',
40-
'Access-Control-Allow-Methods' => 'GET, POST, DELETE, OPTIONS',
41-
'Access-Control-Allow-Headers' => 'Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept',
42-
];
38+
private array $corsHeaders;
4339

40+
/**
41+
* @param array<string, string> $corsHeaders
42+
*/
4443
public function __construct(
4544
private readonly ServerRequestInterface $request,
4645
?ResponseFactoryInterface $responseFactory = null,
4746
?StreamFactoryInterface $streamFactory = null,
47+
array $corsHeaders = [],
4848
LoggerInterface $logger = new NullLogger(),
4949
) {
5050
parent::__construct($logger);
@@ -53,6 +53,12 @@ public function __construct(
5353

5454
$this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory();
5555
$this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory();
56+
57+
$this->corsHeaders = array_merge([
58+
'Access-Control-Allow-Origin' => '*',
59+
'Access-Control-Allow-Methods' => 'GET, POST, DELETE, OPTIONS',
60+
'Access-Control-Allow-Headers' => 'Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept',
61+
], $corsHeaders);
5662
}
5763

5864
public function initialize(): void

0 commit comments

Comments
 (0)