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