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