1: <?php
2:
3: declare(strict_types=1);
4:
5: /**
6: * This file is part of the Nexus MCP SDK package.
7: *
8: * (c) 2025 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\Schema\Tool\ToolSchema;
15:
16: /**
17: * The `object` type is used for validating key-value maps (objects).
18: *
19: * @extends ToolSchema<array<string, mixed>, array{
20: * type: 'object',
21: * description?: non-empty-string,
22: * title?: non-empty-string,
23: * properties?: array<non-empty-string, array<string, mixed>>,
24: * required?: list<non-empty-string>,
25: * additionalProperties: bool,
26: * }>
27: */
28: final class ObjectType extends ToolSchema
29: {
30: /**
31: * @var array<non-empty-string, array<string, mixed>>
32: */
33: private array $properties = [];
34:
35: /**
36: * @var list<non-empty-string>
37: */
38: private array $required = [];
39:
40: private bool $additionalProperties = false;
41:
42: /**
43: * @template T
44: * @template U of array<string, mixed>
45: *
46: * @param non-empty-string $name
47: * @param ToolSchema<covariant T, covariant U> ...$properties
48: */
49: protected function __construct(string $name, ToolSchema ...$properties)
50: {
51: parent::__construct($name);
52:
53: foreach ($properties as $property) {
54: $this->addProperty($property);
55:
56: if ($property->isRequired()) {
57: $this->markAsRequired($property->name);
58: }
59: }
60: }
61:
62: /**
63: * Adds a property to the schema.
64: *
65: * If a property with the same name already exists, it will not be added again.
66: * This method does not mark the property as required. Use `markAsRequired` for that.
67: *
68: * @template T
69: * @template U of array<string, mixed>
70: *
71: * @param ToolSchema<covariant T, covariant U> ...$property
72: */
73: public function addProperty(ToolSchema $property): self
74: {
75: if (! \array_key_exists($property->name, $this->properties)) {
76: $this->properties[$property->name] = $property->toArray();
77: }
78:
79: return $this;
80: }
81:
82: /**
83: * @param non-empty-string $name
84: */
85: public function markAsRequired(string $name): self
86: {
87: if (! \array_key_exists($name, $this->properties)) {
88: throw new \InvalidArgumentException(\sprintf(
89: 'Cannot mark property "%s" as required because it does not exist in the schema.',
90: $name,
91: ));
92: }
93:
94: if (! \in_array($name, $this->required, true)) {
95: $this->required[] = $name;
96: }
97:
98: return $this;
99: }
100:
101: public function allowAdditionalProperties(): self
102: {
103: $this->additionalProperties = true;
104:
105: return $this;
106: }
107:
108: #[\Override]
109: public function toArray(): array
110: {
111: $schema = ['type' => 'object'];
112:
113: if (null !== $this->description) {
114: $schema['description'] = $this->description;
115: }
116:
117: if (null !== $this->title) {
118: $schema['title'] = $this->title;
119: }
120:
121: if ([] !== $this->properties) {
122: $schema['properties'] = $this->properties;
123: }
124:
125: if ([] !== $this->required) {
126: $schema['required'] = $this->required;
127: }
128:
129: $schema['additionalProperties'] = $this->additionalProperties;
130:
131: return $schema;
132: }
133: }
134: