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\Elicitation;
15:
16: use Nexus\Assert\Assert;
17:
18: /**
19: * Schema for multiple-selection enumeration without display titles for options.
20: *
21: * @implements MultiSelectEnumSchema<array{
22: * type: 'array',
23: * items: array{type: 'string', enum: list<non-empty-string>},
24: * title?: non-empty-string,
25: * description?: non-empty-string,
26: * minItems?: int<0, max>,
27: * maxItems?: int<0, max>,
28: * default?: list<string>,
29: * }>
30: *
31: * @see https://modelcontextprotocol.io/specification/2026-07-28/schema#untitledmultiselectenumschema
32: */
33: final readonly class UntitledMultiSelectEnumSchema implements MultiSelectEnumSchema
34: {
35: public const string TYPE = 'array';
36: public const string ITEMS_TYPE = 'string';
37:
38: /**
39: * @param list<non-empty-string> $items The inner `enum` values
40: * @param null|non-empty-string $title
41: * @param null|non-empty-string $description
42: * @param null|int<0, max> $minItems
43: * @param null|int<0, max> $maxItems
44: * @param null|list<string> $default
45: */
46: public function __construct(
47: public array $items,
48: public ?string $title = null,
49: public ?string $description = null,
50: public ?int $minItems = null,
51: public ?int $maxItems = null,
52: public ?array $default = null,
53: ) {
54: Assert::that($items)
55: ->isList('untitled multi-select enum schema "items" must be a list, non-list array given.')
56: ->values()->isNonEmptyString('each untitled multi-select enum schema "items" must be a non-empty string.')
57: ;
58: Assert::that($title)
59: ->nullOr()
60: ->isNonEmptyString('untitled multi-select enum schema "title" must be a non-empty string or null.')
61: ;
62: Assert::that($description)
63: ->nullOr()
64: ->isNonEmptyString('untitled multi-select enum schema "description" must be a non-empty string or null.')
65: ;
66: Assert::that($minItems)
67: ->nullOr()
68: ->isNaturalInt('untitled multi-select enum schema "minItems" must be a non-negative integer or null.')
69: ;
70: Assert::that($maxItems)
71: ->nullOr()
72: ->isNaturalInt('untitled multi-select enum schema "maxItems" must be a non-negative integer or null.')
73: ;
74:
75: if (null !== $default) {
76: Assert::that($default)
77: ->isList('untitled multi-select enum schema "default" must be a list, non-list array given.')
78: ->values()->isString('each untitled multi-select enum schema "default" must be a string.')
79: ;
80: }
81: }
82:
83: #[\Override]
84: public static function fromArray(array $data): static
85: {
86: Assert::that($data)->hasOffset('type', 'untitled multi-select enum schema is missing the required "type" key.');
87: $type = $data['type'];
88: Assert::that($type)->isIdentical(self::TYPE, 'untitled multi-select enum schema "type" must be {other}, {value} given.');
89:
90: Assert::that($data)->hasOffset('items', 'untitled multi-select enum schema is missing the required "items" key.');
91: Assert::that($data['items'])
92: ->isArray('untitled multi-select enum schema "items" must be an object, {type} given.')
93: ->isMap('untitled multi-select enum schema "items" must be a string-keyed object.')
94: ;
95:
96: $itemsType = $data['items']['type'] ?? null;
97: Assert::that($itemsType)->isIdentical(self::ITEMS_TYPE, 'untitled multi-select enum schema "items.type" must be {other}, {value} given.');
98:
99: $itemsEnum = $data['items']['enum'] ?? null;
100: Assert::that($itemsEnum)
101: ->isArray('untitled multi-select enum schema "items.enum" must be a list, {type} given.')
102: ->isList('untitled multi-select enum schema "items.enum" must be a list, non-list array given.')
103: ->values()->isNonEmptyString('each untitled multi-select enum schema "items.enum" must be a non-empty string, {type} given.')
104: ;
105:
106: $title = $data['title'] ?? null;
107: Assert::that($title)->nullOr()->isNonEmptyString('untitled multi-select enum schema "title" must be a non-empty string or null, {type} given.');
108:
109: $description = $data['description'] ?? null;
110: Assert::that($description)->nullOr()->isNonEmptyString('untitled multi-select enum schema "description" must be a non-empty string or null, {type} given.');
111:
112: $minItems = $data['minItems'] ?? null;
113: Assert::that($minItems)->nullOr()->isNaturalInt('untitled multi-select enum schema "minItems" must be a non-negative integer or null, {type} given.');
114:
115: $maxItems = $data['maxItems'] ?? null;
116: Assert::that($maxItems)->nullOr()->isNaturalInt('untitled multi-select enum schema "maxItems" must be a non-negative integer or null, {type} given.');
117:
118: $default = null;
119:
120: if (isset($data['default'])) {
121: Assert::that($data['default'])
122: ->isList('untitled multi-select enum schema "default" must be a list, non-list array given.')
123: ->values()->isString('each untitled multi-select enum schema "default" must be a string, {type} given.')
124: ;
125: $default = $data['default'];
126: }
127:
128: return new self(
129: items: $itemsEnum,
130: title: $title,
131: description: $description,
132: minItems: $minItems,
133: maxItems: $maxItems,
134: default: $default,
135: );
136: }
137:
138: #[\Override]
139: public function toArray(): array
140: {
141: $data = [
142: 'type' => self::TYPE,
143: 'items' => ['type' => self::ITEMS_TYPE, 'enum' => $this->items],
144: ];
145:
146: if (null !== $this->title) {
147: $data['title'] = $this->title;
148: }
149:
150: if (null !== $this->description) {
151: $data['description'] = $this->description;
152: }
153:
154: if (null !== $this->minItems) {
155: $data['minItems'] = $this->minItems;
156: }
157:
158: if (null !== $this->maxItems) {
159: $data['maxItems'] = $this->maxItems;
160: }
161:
162: if (null !== $this->default) {
163: $data['default'] = $this->default;
164: }
165:
166: return $data;
167: }
168:
169: #[\Override]
170: public function jsonSerialize(): array
171: {
172: return $this->toArray();
173: }
174: }
175: