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\ParsesNumber;
18:
19: /**
20: * Schema for a numeric elicitation field.
21: *
22: * @implements PrimitiveSchemaDefinition<array{
23: * type: 'integer'|'number',
24: * title?: non-empty-string,
25: * description?: non-empty-string,
26: * minimum?: float,
27: * maximum?: float,
28: * default?: float,
29: * }>
30: *
31: * @see https://modelcontextprotocol.io/specification/draft/schema#numberschema
32: */
33: final readonly class NumberSchema implements PrimitiveSchemaDefinition
34: {
35: use ParsesNumber;
36:
37: public const string TYPE = 'number';
38: public const string TYPE_INTEGER = 'integer';
39:
40: /**
41: * @var 'integer'|'number'
42: */
43: public string $type;
44:
45: /**
46: * @var null|non-empty-string
47: */
48: public ?string $title;
49:
50: /**
51: * @var null|non-empty-string
52: */
53: public ?string $description;
54:
55: public function __construct(
56: string $type = self::TYPE,
57: ?string $title = null,
58: ?string $description = null,
59: public ?float $minimum = null,
60: public ?float $maximum = null,
61: public ?float $default = null,
62: ) {
63: Assert::that($type)->isOneOf([self::TYPE, self::TYPE_INTEGER], 'number schema "type" must be one of {choices}.');
64: Assert::that($title)
65: ->nullOr()
66: ->isNonEmptyString('number schema "title" must be a non-empty string or null.')
67: ;
68: Assert::that($description)
69: ->nullOr()
70: ->isNonEmptyString('number schema "description" must be a non-empty string or null.')
71: ;
72:
73: $this->type = $type;
74: $this->title = $title;
75: $this->description = $description;
76: }
77:
78: #[\Override]
79: public static function fromArray(array $data): static
80: {
81: Assert::that($data)->hasOffset('type', 'number schema is missing the required "type" key.');
82: $type = $data['type'];
83: Assert::that($type)->isString('number schema "type" must be a string, {type} given.');
84:
85: $title = $data['title'] ?? null;
86: Assert::that($title)->nullOr()->isString('number schema "title" must be a string or null, {type} given.');
87:
88: $description = $data['description'] ?? null;
89: Assert::that($description)->nullOr()->isString('number schema "description" must be a string or null, {type} given.');
90:
91: $minimum = $data['minimum'] ?? null;
92:
93: if (null !== $minimum) {
94: $minimum = self::parseNumber($minimum, 'number schema "minimum" must be a number or null, {type} given.');
95: }
96:
97: $maximum = $data['maximum'] ?? null;
98:
99: if (null !== $maximum) {
100: $maximum = self::parseNumber($maximum, 'number schema "maximum" must be a number or null, {type} given.');
101: }
102:
103: $default = $data['default'] ?? null;
104:
105: if (null !== $default) {
106: $default = self::parseNumber($default, 'number schema "default" must be a number or null, {type} given.');
107: }
108:
109: return new self(
110: type: $type,
111: title: $title,
112: description: $description,
113: minimum: $minimum,
114: maximum: $maximum,
115: default: $default,
116: );
117: }
118:
119: #[\Override]
120: public function toArray(): array
121: {
122: $data = ['type' => $this->type];
123:
124: if (null !== $this->title) {
125: $data['title'] = $this->title;
126: }
127:
128: if (null !== $this->description) {
129: $data['description'] = $this->description;
130: }
131:
132: if (null !== $this->minimum) {
133: $data['minimum'] = $this->minimum;
134: }
135:
136: if (null !== $this->maximum) {
137: $data['maximum'] = $this->maximum;
138: }
139:
140: if (null !== $this->default) {
141: $data['default'] = $this->default;
142: }
143:
144: return $data;
145: }
146:
147: #[\Override]
148: public function jsonSerialize(): array
149: {
150: return $this->toArray();
151: }
152: }
153: