Files
redmine/redMCP/app/McpStdioServer.php
T
Jason Thistlethwaite bd26c8894f Add production rollout tooling and semantic index ops docs
Capture the production plugin rollout workflow and Qdrant validation steps so operations stay repeatable. Also harden redMCP stdio/schema compatibility to keep diverse MCP clients and validators working.
2026-05-06 22:18:02 -04:00

91 lines
2.4 KiB
PHP

<?php
declare(strict_types=1);
namespace RedMCP;
final class McpStdioServer
{
private McpDispatcher $dispatcher;
private string $wireMode = 'content-length';
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 (!preg_match('/^[A-Za-z0-9-]+\s*:/', $line)) {
$this->wireMode = '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;
}
if ($this->wireMode === 'line') {
fwrite(STDOUT, $body . "\n");
fflush(STDOUT);
return;
}
fwrite(STDOUT, 'Content-Length: ' . strlen($body) . "\r\n\r\n" . $body);
fflush(STDOUT);
}
}