Add redMCP Streamable HTTP server

This commit is contained in:
Jason Thistlethwaite
2026-04-25 02:23:48 +00:00
parent 3b6b4d6dba
commit 05c1a4bc97
11 changed files with 631 additions and 15 deletions
+106
View File
@@ -0,0 +1,106 @@
<?php
declare(strict_types=1);
namespace RedMCP;
final class McpHttpHandler
{
private McpDispatcher $dispatcher;
private string $token;
private string $path;
public function __construct(McpDispatcher $dispatcher, string $token, string $path = '/mcp')
{
$this->dispatcher = $dispatcher;
$this->token = $token;
$this->path = '/' . trim($path, '/');
}
public function handle(): void
{
if (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) !== $this->path) {
$this->sendJson(404, ['error' => 'not found']);
return;
}
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
$this->sendJson(405, ['error' => 'method not allowed']);
return;
}
if (!$this->authorized()) {
header('WWW-Authenticate: Bearer');
$this->sendJson(401, ['error' => 'unauthorized']);
return;
}
$raw = file_get_contents('php://input');
$decoded = json_decode(is_string($raw) ? $raw : '', true);
if (!is_array($decoded)) {
$this->sendJson(400, $this->errorResponse(null, -32700, 'Invalid JSON.'));
return;
}
if (array_is_list($decoded)) {
$responses = [];
foreach ($decoded as $message) {
if (!is_array($message)) {
$responses[] = $this->errorResponse(null, -32600, 'Invalid request.');
continue;
}
$response = $this->dispatcher->handleMessage($message);
if ($response !== null) {
$responses[] = $response;
}
}
if ($responses === []) {
http_response_code(202);
return;
}
$this->sendJson(200, $responses);
return;
}
$response = $this->dispatcher->handleMessage($decoded);
if ($response === null) {
http_response_code(202);
return;
}
$this->sendJson(200, $response);
}
private function authorized(): bool
{
$header = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
if (!is_string($header) || !str_starts_with($header, 'Bearer ')) {
return false;
}
return hash_equals($this->token, substr($header, 7));
}
/**
* @param mixed $payload
*/
private function sendJson(int $status, $payload): void
{
http_response_code($status);
header('Content-Type: application/json');
echo json_encode($payload, JSON_UNESCAPED_SLASHES);
}
/**
* @return array<string,mixed>
*/
private function errorResponse(mixed $id, int $code, string $message): array
{
return [
'jsonrpc' => '2.0',
'id' => $id,
'error' => [
'code' => $code,
'message' => $message,
],
];
}
}