1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2026 John Paul E. Balandan, CPA <paulbalandan@gmail.com>
9: *
10: * For the full copyright and license information, please view
11: * the LICENSE file that was distributed with this source code.
12: */
13:
14: namespace Nexus\Mcp\Server\Transport;
15:
16: use Amp\ByteStream\ReadableResourceStream;
17: use Amp\ByteStream\ReadableStream;
18: use Amp\ByteStream\WritableResourceStream;
19: use Amp\ByteStream\WritableStream;
20: use Nexus\Mcp\Core\Schema\JsonRpc\JsonRpcErrorResponse;
21: use Nexus\Mcp\Core\Schema\JsonRpc\JsonRpcMessage;
22: use Nexus\Mcp\Core\Transport\LineDuplex;
23: use Nexus\Mcp\Core\Transport\LineReader;
24: use Nexus\Mcp\Core\Transport\SendContext;
25: use Nexus\Mcp\Core\Transport\SubscriptionInterface;
26: use Nexus\Mcp\Core\Transport\TransportInterface;
27: use Psr\Log\LoggerInterface;
28: use Psr\Log\NullLogger;
29:
30: /**
31: * Stdio MCP server transport over line-framed JSON-RPC on STDIN/STDOUT.
32: */
33: final class StdioServerTransport implements TransportInterface
34: {
35: private readonly LineDuplex $duplex;
36:
37: public function __construct(
38: private readonly ReadableStream $stdin = new ReadableResourceStream(\STDIN),
39: private readonly WritableStream $stdout = new WritableResourceStream(\STDOUT),
40: LoggerInterface $logger = new NullLogger(),
41: int $maxLineBytes = LineReader::DEFAULT_MAX_LINE_BYTES,
42: ) {
43: $this->duplex = new LineDuplex(
44: hostTransport: self::class,
45: label: 'Stdio server',
46: logger: $logger,
47: maxLineBytes: $maxLineBytes,
48: onParseFailure: function (JsonRpcErrorResponse $response): void {
49: $this->send($response);
50: },
51: onBeforeClose: function (): void {
52: $this->stdin->close();
53: $this->stdout->close();
54: },
55: );
56: }
57:
58: #[\Override]
59: public function start(): void
60: {
61: $this->duplex->start($this->stdin, $this->stdout);
62: }
63:
64: #[\Override]
65: public function send(JsonRpcMessage $message, ?SendContext $context = null): void
66: {
67: $this->duplex->send($message);
68: }
69:
70: #[\Override]
71: public function close(): void
72: {
73: $this->duplex->close();
74: }
75:
76: #[\Override]
77: public function getSessionId(): ?string
78: {
79: return null;
80: }
81:
82: #[\Override]
83: public function onMessage(\Closure $listener): SubscriptionInterface
84: {
85: return $this->duplex->onMessage($listener);
86: }
87:
88: #[\Override]
89: public function onError(\Closure $listener): SubscriptionInterface
90: {
91: return $this->duplex->onError($listener);
92: }
93:
94: #[\Override]
95: public function onDrain(\Closure $listener): SubscriptionInterface
96: {
97: return $this->duplex->onDrain($listener);
98: }
99:
100: #[\Override]
101: public function onClose(\Closure $listener): SubscriptionInterface
102: {
103: return $this->duplex->onClose($listener);
104: }
105: }
106: