From e889dd9e8ad68bed9609b9027da04281b8b35c4a Mon Sep 17 00:00:00 2001 From: Jeroen van Leeuwen Date: Fri, 5 Dec 2025 16:23:35 +0100 Subject: [PATCH] feat: support custom HTTP response headers --- src/Response.php | 23 ++++++++++++++ src/ResponseFactory.php | 12 ++++++++ src/Server.php | 30 +++++++++++++++++++ .../Concerns/InteractsWithResponses.php | 2 ++ src/Server/Transport/HttpTransport.php | 17 ++++++++++- 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/src/Response.php b/src/Response.php index a579c811..84db4414 100644 --- a/src/Response.php +++ b/src/Response.php @@ -20,6 +20,11 @@ class Response use Conditionable; use Macroable; + /** + * @var array + */ + protected array $headers = []; + protected function __construct( protected Content $content, protected Role $role = Role::User, @@ -109,6 +114,24 @@ public function withMeta(array|string $meta, mixed $value = null): static return $this; } + /** + * @param array $headers + */ + public function withHeaders(array $headers): static + { + $this->headers = array_merge($this->headers, $headers); + + return $this; + } + + /** + * @return array + */ + public function headers(): array + { + return $this->headers; + } + /** * @throws NotImplementedException */ diff --git a/src/ResponseFactory.php b/src/ResponseFactory.php index 7cc25178..90ade339 100644 --- a/src/ResponseFactory.php +++ b/src/ResponseFactory.php @@ -85,4 +85,16 @@ public function getStructuredContent(): ?array { return $this->structuredContent; } + + /** + * Get all HTTP headers from all responses. + * + * @return array + */ + public function headers(): array + { + return $this->responses + ->flatMap(fn (Response $response) => $response->headers()) + ->all(); + } } diff --git a/src/Server.php b/src/Server.php index f3481bca..09bdbe17 100644 --- a/src/Server.php +++ b/src/Server.php @@ -24,6 +24,7 @@ use Laravel\Mcp\Server\Testing\PendingTestResponse; use Laravel\Mcp\Server\Testing\TestResponse; use Laravel\Mcp\Server\Tool; +use Laravel\Mcp\Server\Transport\HttpTransport; use Laravel\Mcp\Server\Transport\JsonRpcNotification; use Laravel\Mcp\Server\Transport\JsonRpcRequest; use Laravel\Mcp\Server\Transport\JsonRpcResponse; @@ -233,6 +234,8 @@ protected function handleMessage(JsonRpcRequest $request, ServerContext $context { $response = $this->runMethodHandle($request, $context); + $this->applyResponseHeaders(); + if (! is_iterable($response)) { $this->transport->send($response->toJson()); @@ -283,6 +286,33 @@ protected function generateSessionId(): string return Str::uuid()->toString(); } + protected function applyResponseHeaders(): void + { + $container = Container::getInstance(); + + if (! $container->bound('mcp.response.factory')) { + return; + } + + $responseFactory = $container->make('mcp.response.factory'); + + if (! $responseFactory instanceof ResponseFactory) { + return; + } + + $headers = $responseFactory->headers(); + + if ($headers === []) { + return; + } + + if ($this->transport instanceof HttpTransport) { + $this->transport->withHeaders($headers); + } + + $container->forgetInstance('mcp.response.factory'); + } + /** * @param array $arguments */ diff --git a/src/Server/Methods/Concerns/InteractsWithResponses.php b/src/Server/Methods/Concerns/InteractsWithResponses.php index 75e8b9fb..e979541a 100644 --- a/src/Server/Methods/Concerns/InteractsWithResponses.php +++ b/src/Server/Methods/Concerns/InteractsWithResponses.php @@ -34,6 +34,8 @@ protected function toJsonRpcResponse(JsonRpcRequest $request, Response|ResponseF } }); + \Illuminate\Container\Container::getInstance()->instance('mcp.response.factory', $responseFactory); + return JsonRpcResponse::result($request->id, $serializable($responseFactory)); } diff --git a/src/Server/Transport/HttpTransport.php b/src/Server/Transport/HttpTransport.php index 607ce902..bf93d5f2 100644 --- a/src/Server/Transport/HttpTransport.php +++ b/src/Server/Transport/HttpTransport.php @@ -12,6 +12,11 @@ class HttpTransport implements Transport { + /** + * @var array + */ + protected array $customHeaders = []; + /** * @param (Closure(string): void)|null $handler */ @@ -70,6 +75,16 @@ public function stream(Closure $stream): void $this->stream = $stream; } + /** + * @param array $headers + */ + public function withHeaders(array $headers): static + { + $this->customHeaders = array_merge($this->customHeaders, $headers); + + return $this; + } + protected function sendStreamMessage(string $message): void { echo 'data: '.$message."\n\n"; @@ -98,6 +113,6 @@ protected function getHeaders(): array $headers['X-Accel-Buffering'] = 'no'; } - return $headers; + return array_merge($headers, $this->customHeaders); } }