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\MetaObject;
15:
16: use Nexus\Assert\Assert;
17: use Nexus\Mcp\Core\Schema\Arrayable;
18: use Nexus\Mcp\Core\Schema\ClientCapabilities;
19: use Nexus\Mcp\Core\Schema\Enum\LoggingLevel;
20: use Nexus\Mcp\Core\Schema\Implementation;
21: use Nexus\Mcp\Core\Schema\MetaObject;
22: use Nexus\Mcp\Core\Schema\ProgressToken;
23: use Nexus\Mcp\Core\Schema\ProtocolVersion;
24: use Nexus\Mcp\Core\Validation\EnumValueValidator;
25:
26: /**
27: * Extends `MetaObject` with additional request-specific fields.
28: * All key naming rules from `MetaObject` apply.
29: *
30: * @extends MetaObject<array{
31: * 'io.modelcontextprotocol/protocolVersion': non-empty-string,
32: * 'io.modelcontextprotocol/clientInfo'?: template-type<Implementation, Arrayable, 'T'>,
33: * 'io.modelcontextprotocol/clientCapabilities': template-type<ClientCapabilities, Arrayable, 'T'>,
34: * 'io.modelcontextprotocol/logLevel'?: value-of<LoggingLevel>,
35: * progressToken?: int|non-empty-string,
36: * ...<string, mixed>,
37: * }>
38: *
39: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#requestmetaobject
40: */
41: final readonly class RequestMetaObject extends MetaObject
42: {
43: public const string PROTOCOL_VERSION_KEY = 'io.modelcontextprotocol/protocolVersion';
44: public const string CLIENT_INFO_KEY = 'io.modelcontextprotocol/clientInfo';
45: public const string CLIENT_CAPABILITIES_KEY = 'io.modelcontextprotocol/clientCapabilities';
46: public const string LOG_LEVEL_KEY = 'io.modelcontextprotocol/logLevel';
47:
48: /**
49: * @param array<string, mixed> $extras
50: */
51: public function __construct(
52: public ProtocolVersion $protocolVersion,
53: public ClientCapabilities $clientCapabilities,
54: public ?Implementation $clientInfo = null,
55: public ?LoggingLevel $logLevel = null,
56: public ?ProgressToken $progressToken = null,
57: array $extras = [],
58: ) {
59: parent::__construct(extras: $extras);
60: }
61:
62: #[\Override]
63: public static function fromArray(array $data): static
64: {
65: Assert::that($data)->hasOffset(self::PROTOCOL_VERSION_KEY, '"_meta" is missing the required "{key}" key.');
66: $protocolVersion = $data[self::PROTOCOL_VERSION_KEY];
67: Assert::that($protocolVersion)->isString(\sprintf('"_meta.%s" must be a string, {type} given.', self::PROTOCOL_VERSION_KEY));
68: unset($data[self::PROTOCOL_VERSION_KEY]);
69:
70: Assert::that($data)->hasOffset(self::CLIENT_CAPABILITIES_KEY, '"_meta" is missing the required "{key}" key.');
71: Assert::that($data[self::CLIENT_CAPABILITIES_KEY])
72: ->isArray(\sprintf('"_meta.%s" must be an object, {type} given.', self::CLIENT_CAPABILITIES_KEY))
73: ->isMap(\sprintf('"_meta.%s" must be a string-keyed object.', self::CLIENT_CAPABILITIES_KEY))
74: ;
75: $clientCapabilities = ClientCapabilities::fromArray($data[self::CLIENT_CAPABILITIES_KEY]);
76: unset($data[self::CLIENT_CAPABILITIES_KEY]);
77:
78: $clientInfo = null;
79:
80: if (\array_key_exists(self::CLIENT_INFO_KEY, $data)) {
81: Assert::that($data[self::CLIENT_INFO_KEY])
82: ->isArray(\sprintf('"_meta.%s" must be an object, {type} given.', self::CLIENT_INFO_KEY))
83: ->isMap(\sprintf('"_meta.%s" must be a string-keyed object.', self::CLIENT_INFO_KEY))
84: ;
85: $clientInfo = Implementation::fromArray($data[self::CLIENT_INFO_KEY]);
86: unset($data[self::CLIENT_INFO_KEY]);
87: }
88:
89: $logLevel = null;
90:
91: if (\array_key_exists(self::LOG_LEVEL_KEY, $data)) {
92: $logLevel = EnumValueValidator::parse(LoggingLevel::class, $data[self::LOG_LEVEL_KEY], \sprintf('"_meta.%s"', self::LOG_LEVEL_KEY));
93: unset($data[self::LOG_LEVEL_KEY]);
94: }
95:
96: $progressToken = null;
97:
98: if (\array_key_exists('progressToken', $data)) {
99: $raw = $data['progressToken'];
100: Assert::that($raw)->isIntOrNonEmptyString('"_meta.progressToken" must be an int or non-empty string, {type} given.');
101:
102: $progressToken = new ProgressToken(token: $raw);
103: unset($data['progressToken']);
104: }
105:
106: return new self(
107: protocolVersion: new ProtocolVersion(version: $protocolVersion),
108: clientCapabilities: $clientCapabilities,
109: clientInfo: $clientInfo,
110: logLevel: $logLevel,
111: progressToken: $progressToken,
112: extras: $data,
113: );
114: }
115:
116: #[\Override]
117: public function toArray(): array
118: {
119: $out = [];
120:
121: $out[self::PROTOCOL_VERSION_KEY] = $this->protocolVersion->version;
122:
123: if (null !== $this->clientInfo) {
124: $out[self::CLIENT_INFO_KEY] = $this->clientInfo->toArray();
125: }
126:
127: $out[self::CLIENT_CAPABILITIES_KEY] = $this->clientCapabilities->toArray();
128:
129: if (null !== $this->logLevel) {
130: $out[self::LOG_LEVEL_KEY] = $this->logLevel->value;
131: }
132:
133: if (null !== $this->progressToken) {
134: $out['progressToken'] = $this->progressToken->token;
135: }
136:
137: $out += $this->extras;
138:
139: return $out;
140: }
141:
142: #[\Override]
143: public function jsonSerialize(): array
144: {
145: $out = $this->toArray();
146: $out[self::CLIENT_CAPABILITIES_KEY] = $this->clientCapabilities->jsonSerialize();
147:
148: return $out;
149: }
150: }
151: