[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
This commit is contained in:
parent
42b5261612
commit
48a19b93a7
3 changed files with 59 additions and 12 deletions
|
|
@ -110,6 +110,7 @@ $transport = new StreamableHttpTransport(
|
|||
- **`request`** (required): `ServerRequestInterface` - The incoming PSR-7 HTTP request
|
||||
- **`responseFactory`** (optional): `ResponseFactoryInterface` - PSR-17 factory for creating HTTP responses. Auto-discovered if not provided.
|
||||
- **`streamFactory`** (optional): `StreamFactoryInterface` - PSR-17 factory for creating response body streams. Auto-discovered if not provided.
|
||||
- **`corsHeaders`** (optional): `array` - Custom CORS headers to override defaults. Merges with secure defaults. Defaults to `[]`.
|
||||
- **`logger`** (optional): `LoggerInterface` - PSR-3 logger for debugging. Defaults to `NullLogger`.
|
||||
|
||||
### PSR-17 Auto-Discovery
|
||||
|
|
@ -136,6 +137,48 @@ $psr17Factory = new Psr17Factory();
|
|||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory);
|
||||
```
|
||||
|
||||
### CORS Configuration
|
||||
|
||||
The transport sets secure CORS defaults that can be customized or disabled:
|
||||
|
||||
```php
|
||||
// Default CORS headers (backward compatible)
|
||||
$transport = new StreamableHttpTransport($request, $responseFactory, $streamFactory);
|
||||
|
||||
// Restrict to specific origin
|
||||
$transport = new StreamableHttpTransport(
|
||||
$request,
|
||||
$responseFactory,
|
||||
$streamFactory,
|
||||
['Access-Control-Allow-Origin' => 'https://myapp.com']
|
||||
);
|
||||
|
||||
// Disable CORS for proxy scenarios
|
||||
$transport = new StreamableHttpTransport(
|
||||
$request,
|
||||
$responseFactory,
|
||||
$streamFactory,
|
||||
['Access-Control-Allow-Origin' => '']
|
||||
);
|
||||
|
||||
// Custom headers with logger
|
||||
$transport = new StreamableHttpTransport(
|
||||
$request,
|
||||
$responseFactory,
|
||||
$streamFactory,
|
||||
[
|
||||
'Access-Control-Allow-Origin' => 'https://api.example.com',
|
||||
'Access-Control-Max-Age' => '86400'
|
||||
],
|
||||
$logger
|
||||
);
|
||||
```
|
||||
|
||||
Default CORS headers:
|
||||
- `Access-Control-Allow-Origin: *`
|
||||
- `Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS`
|
||||
- `Access-Control-Allow-Headers: Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept`
|
||||
|
||||
### Architecture
|
||||
|
||||
The HTTP transport doesn't run its own web server. Instead, it processes PSR-7 requests and returns PSR-7 responses that
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
require_once dirname(__DIR__).'/bootstrap.php';
|
||||
chdir(__DIR__);
|
||||
|
||||
use Http\Discovery\Psr17Factory;
|
||||
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
|
||||
use Mcp\Schema\Content\TextContent;
|
||||
use Mcp\Schema\Enum\LoggingLevel;
|
||||
|
|
@ -21,19 +22,16 @@ use Mcp\Server;
|
|||
use Mcp\Server\ClientGateway;
|
||||
use Mcp\Server\Session\FileSessionStore;
|
||||
use Mcp\Server\Transport\StreamableHttpTransport;
|
||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use Nyholm\Psr7Server\ServerRequestCreator;
|
||||
|
||||
$psr17Factory = new Psr17Factory();
|
||||
$creator = new ServerRequestCreator($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
|
||||
$request = $creator->fromGlobals();
|
||||
$request = (new Psr17Factory())->createServerRequestFromGlobals();
|
||||
|
||||
$sessionDir = __DIR__.'/sessions';
|
||||
$capabilities = new ServerCapabilities(logging: true, tools: true);
|
||||
$logger = logger();
|
||||
|
||||
$server = Server::builder()
|
||||
->setServerInfo('HTTP Client Communication Demo', '1.0.0')
|
||||
->setLogger(logger())
|
||||
->setLogger($logger)
|
||||
->setContainer(container())
|
||||
->setSession(new FileSessionStore($sessionDir))
|
||||
->setCapabilities($capabilities)
|
||||
|
|
@ -117,7 +115,7 @@ $server = Server::builder()
|
|||
)
|
||||
->build();
|
||||
|
||||
$transport = new StreamableHttpTransport($request, $psr17Factory, $psr17Factory, logger());
|
||||
$transport = new StreamableHttpTransport($request, logger: $logger);
|
||||
|
||||
$response = $server->run($transport);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,16 +35,16 @@ class StreamableHttpTransport extends BaseTransport implements TransportInterfac
|
|||
private ?int $immediateStatusCode = null;
|
||||
|
||||
/** @var array<string, string> */
|
||||
private array $corsHeaders = [
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'GET, POST, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers' => 'Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept',
|
||||
];
|
||||
private array $corsHeaders;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $corsHeaders
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ServerRequestInterface $request,
|
||||
?ResponseFactoryInterface $responseFactory = null,
|
||||
?StreamFactoryInterface $streamFactory = null,
|
||||
array $corsHeaders = [],
|
||||
LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
parent::__construct($logger);
|
||||
|
|
@ -53,6 +53,12 @@ class StreamableHttpTransport extends BaseTransport implements TransportInterfac
|
|||
|
||||
$this->responseFactory = $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory();
|
||||
$this->streamFactory = $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory();
|
||||
|
||||
$this->corsHeaders = array_merge([
|
||||
'Access-Control-Allow-Origin' => '*',
|
||||
'Access-Control-Allow-Methods' => 'GET, POST, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers' => 'Content-Type, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID, Authorization, Accept',
|
||||
], $corsHeaders);
|
||||
}
|
||||
|
||||
public function initialize(): void
|
||||
|
|
|
|||
Loading…
Reference in a new issue