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\Client\Subscription;
15:
16: use Amp\Future;
17: use Nexus\Mcp\Client\Exception\SubscriptionDeliveryDroppedException;
18: use Nexus\Mcp\Core\Exception\LogicException;
19: use Nexus\Mcp\Core\Exception\RemoteCallFailedException;
20: use Nexus\Mcp\Core\Schema\RequestId;
21: use Nexus\Mcp\Core\Schema\Result\SubscriptionsListenResult;
22:
23: /**
24: * An open `subscriptions/listen` stream, surviving a restart under the same subscription id.
25: */
26: final class SubscriptionStream
27: {
28: private bool $closed = false;
29:
30: /**
31: * @param Future<SubscriptionsListenResult> $outcome
32: * @param \Closure(): void $onClose
33: *
34: * @internal
35: */
36: public function __construct(
37: public readonly RequestId $subscriptionId,
38: private readonly Future $outcome,
39: private readonly \Closure $onClose,
40: ) {
41: }
42:
43: /**
44: * Stops the stream, with subsequent calls no-ops.
45: */
46: public function close(): void
47: {
48: if ($this->closed) {
49: return;
50: }
51:
52: $this->closed = true;
53: ($this->onClose)();
54: }
55:
56: /**
57: * Blocks until the server tears the subscription down of its own accord.
58: *
59: * @throws LogicException
60: * @throws RemoteCallFailedException
61: * @throws SubscriptionDeliveryDroppedException
62: */
63: public function await(): SubscriptionsListenResult
64: {
65: if ($this->closed) {
66: throw new LogicException(\sprintf(
67: 'Subscription %s was closed by this client, so it carries no response to await.',
68: var_export($this->subscriptionId->id, true),
69: ));
70: }
71:
72: return $this->outcome->await();
73: }
74: }
75: