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\Core\Schema\Result;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\Enum\CacheScope;
19: use Nexus\Mcp\Core\Schema\Enum\ResultType;
20: use Nexus\Mcp\Core\Schema\MetaObject;
21: use Nexus\Mcp\Core\Schema\MetaObject\GenericResultMetaObject;
22: use Nexus\Mcp\Core\Schema\MetaObject\ResultMetaObject;
23: use Nexus\Mcp\Core\Schema\ServerCapabilities;
24: use Nexus\Mcp\Core\Validation\EnumValueValidator;
25:
26: /**
27: * The result returned by the server for a `server/discover` request.
28: *
29: * @extends CacheableResult<array{
30: * _meta?: template-type<ResultMetaObject, MetaObject, 'T'>,
31: * resultType: non-empty-string,
32: * supportedVersions: list<non-empty-string>,
33: * capabilities: template-type<ServerCapabilities, Arrayable, 'T'>,
34: * instructions?: non-empty-string,
35: * ttlMs: int,
36: * cacheScope: value-of<CacheScope>,
37: * }>
38: *
39: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#discoverresult
40: */
41: final readonly class DiscoverResult extends CacheableResult implements ServerResult
42: {
43: /**
44: * @var list<non-empty-string>
45: */
46: public array $supportedVersions;
47:
48: /**
49: * @var null|non-empty-string
50: */
51: public ?string $instructions;
52:
53: /**
54: * @param list<string> $supportedVersions
55: */
56: public function __construct(
57: array $supportedVersions,
58: public ServerCapabilities $capabilities,
59: int $ttlMs,
60: CacheScope $cacheScope,
61: ?string $instructions = null,
62: ResultMetaObject $meta = new GenericResultMetaObject(),
63: ) {
64: Assert::that($supportedVersions)
65: ->isList('"result.supportedVersions" must be a list, non-list array given.')
66: ->values()->isNonEmptyString('each "result.supportedVersions" must be a non-empty string.')
67: ;
68: Assert::that($instructions)->nullOr()->isNonEmptyString('"result.instructions" must be a non-empty string or null.');
69:
70: $this->supportedVersions = $supportedVersions;
71: $this->instructions = $instructions;
72:
73: parent::__construct(ttlMs: $ttlMs, cacheScope: $cacheScope, meta: $meta);
74: }
75:
76: #[\Override]
77: public static function fromArray(array $data): static
78: {
79: Assert::that($data)->hasOffset('supportedVersions', '"result" is missing the required "supportedVersions" key.');
80: Assert::that($data['supportedVersions'])
81: ->isList('"result.supportedVersions" must be a list, {type} given.')
82: ->values()->isString('each "result.supportedVersions" must be a string, {type} given.')
83: ;
84: $supportedVersions = $data['supportedVersions'];
85:
86: Assert::that($data)->hasOffset('capabilities', '"result" is missing the required "capabilities" key.');
87: Assert::that($data['capabilities'])
88: ->isArray('"result.capabilities" must be an object, {type} given.')
89: ->isMap('"result.capabilities" must be a string-keyed object.')
90: ;
91: $capabilities = ServerCapabilities::fromArray($data['capabilities']);
92:
93: Assert::that($data)->hasOffset('ttlMs', '"result" is missing the required "ttlMs" key.');
94: $ttlMs = $data['ttlMs'];
95: Assert::that($ttlMs)->isInt('"result.ttlMs" must be an integer, {type} given.');
96:
97: Assert::that($data)->hasOffset('cacheScope', '"result" is missing the required "cacheScope" key.');
98: $cacheScope = EnumValueValidator::parse(CacheScope::class, $data['cacheScope'], '"result.cacheScope"');
99:
100: $instructions = null;
101:
102: if (\array_key_exists('instructions', $data)) {
103: $raw = $data['instructions'];
104: Assert::that($raw)->isString('"result.instructions" must be a string, {type} given.');
105: $instructions = $raw;
106: }
107:
108: $meta = new GenericResultMetaObject();
109:
110: if (\array_key_exists('_meta', $data)) {
111: Assert::that($data['_meta'])
112: ->isArray('"result._meta" must be an object, {type} given.')
113: ->isMap('"result._meta" must be a string-keyed object.')
114: ;
115: $meta = GenericResultMetaObject::fromArray($data['_meta']);
116: }
117:
118: return new self(
119: supportedVersions: $supportedVersions,
120: capabilities: $capabilities,
121: ttlMs: $ttlMs,
122: cacheScope: $cacheScope,
123: instructions: $instructions,
124: meta: $meta,
125: );
126: }
127:
128: #[\Override]
129: public function toArray(): array
130: {
131: $data = [];
132: $meta = $this->meta->toArray();
133:
134: if ([] !== $meta) {
135: $data['_meta'] = $meta;
136: }
137:
138: $data['resultType'] = self::getResultType();
139: $data['supportedVersions'] = $this->supportedVersions;
140: $data['capabilities'] = $this->capabilities->toArray();
141:
142: if (null !== $this->instructions) {
143: $data['instructions'] = $this->instructions;
144: }
145:
146: $data['ttlMs'] = $this->ttlMs;
147: $data['cacheScope'] = $this->cacheScope->value;
148:
149: return $data;
150: }
151:
152: #[\Override]
153: public function jsonSerialize(): array
154: {
155: $data = $this->toArray();
156: $data['capabilities'] = $this->capabilities->jsonSerialize();
157:
158: return $data;
159: }
160:
161: #[\Override]
162: public function rebuildWithMeta(ResultMetaObject $meta): static
163: {
164: return new self(
165: supportedVersions: $this->supportedVersions,
166: capabilities: $this->capabilities,
167: ttlMs: $this->ttlMs,
168: cacheScope: $this->cacheScope,
169: instructions: $this->instructions,
170: meta: $meta,
171: );
172: }
173:
174: #[\Override]
175: protected function getResultType(): string
176: {
177: return ResultType::Complete->value;
178: }
179: }
180: