83 lines
2.1 KiB
PHP
83 lines
2.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace RedMCP;
|
|
|
|
final class McpStdioServer
|
|
{
|
|
private McpDispatcher $dispatcher;
|
|
|
|
public function __construct(McpDispatcher $dispatcher)
|
|
{
|
|
$this->dispatcher = $dispatcher;
|
|
}
|
|
|
|
public function run(): void
|
|
{
|
|
while (($message = $this->readMessage(STDIN)) !== null) {
|
|
$response = $this->dispatcher->handleMessage($message, ['transport' => 'stdio']);
|
|
if ($response !== null) {
|
|
$this->writeMessage($response);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param resource $stream
|
|
*
|
|
* @return array<string,mixed>|null
|
|
*/
|
|
private function readMessage($stream): ?array
|
|
{
|
|
$headers = [];
|
|
while (($line = fgets($stream)) !== false) {
|
|
$line = rtrim($line, "\r\n");
|
|
if ($line === '') {
|
|
break;
|
|
}
|
|
if (!str_contains($line, ':')) {
|
|
$decoded = json_decode($line, true);
|
|
return is_array($decoded) ? $decoded : null;
|
|
}
|
|
[$name, $value] = explode(':', $line, 2);
|
|
$headers[strtolower(trim($name))] = trim($value);
|
|
}
|
|
|
|
if ($line === false && $headers === []) {
|
|
return null;
|
|
}
|
|
|
|
$length = isset($headers['content-length']) ? (int) $headers['content-length'] : 0;
|
|
if ($length <= 0) {
|
|
return null;
|
|
}
|
|
|
|
$body = '';
|
|
while (strlen($body) < $length && !feof($stream)) {
|
|
$chunk = fread($stream, $length - strlen($body));
|
|
if ($chunk === false || $chunk === '') {
|
|
break;
|
|
}
|
|
$body .= $chunk;
|
|
}
|
|
|
|
$decoded = json_decode($body, true);
|
|
return is_array($decoded) ? $decoded : null;
|
|
}
|
|
|
|
/**
|
|
* @param array<string,mixed> $message
|
|
*/
|
|
private function writeMessage(array $message): void
|
|
{
|
|
$body = json_encode($message, JSON_UNESCAPED_SLASHES);
|
|
if ($body === false) {
|
|
return;
|
|
}
|
|
|
|
fwrite(STDOUT, 'Content-Length: ' . strlen($body) . "\r\n\r\n" . $body);
|
|
fflush(STDOUT);
|
|
}
|
|
}
|