Skip to content

Commit 951ec4a

Browse files
feat: Enhance Schema attribute with comprehensive JSON Schema support
1 parent 72f85f5 commit 951ec4a

19 files changed

+1211
-1214
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"friendsofphp/php-cs-fixer": "^3.75",
3030
"mockery/mockery": "^1.6",
3131
"pestphp/pest": "^2.36.0|^3.5.0",
32+
"pestphp/pest-plugin-drift": "^3.0",
3233
"react/async": "^4.0",
3334
"react/child-process": "^0.6.6",
3435
"symfony/var-dumper": "^6.4.11|^7.1.5"

src/Attributes/Schema.php

Lines changed: 144 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -5,138 +5,174 @@
55
namespace PhpMcp\Server\Attributes;
66

77
use Attribute;
8-
use PhpMcp\Server\Attributes\Schema\ArrayItems;
9-
use PhpMcp\Server\Attributes\Schema\Property;
108

11-
#[Attribute(Attribute::TARGET_PARAMETER)]
9+
/**
10+
* Defines a JSON Schema for a method's input or an individual parameter.
11+
*
12+
* When used at the method level, it describes an object schema where properties
13+
* correspond to the method's parameters.
14+
*
15+
* When used at the parameter level, it describes the schema for that specific parameter.
16+
* If 'type' is omitted at the parameter level, it will be inferred.
17+
*/
18+
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_PARAMETER)]
1219
class Schema
1320
{
14-
/** @var Property[] */
15-
protected array $properties = [];
21+
/**
22+
* The complete JSON schema array.
23+
* If provided, it takes precedence over individual properties like $type, $properties, etc.
24+
*/
25+
public ?array $definition = null;
26+
27+
/**
28+
* Alternatively, provide individual top-level schema keywords.
29+
* These are used if $definition is null.
30+
*/
31+
public ?string $type = null;
32+
public ?string $description = null;
33+
public mixed $default = null;
34+
public ?array $enum = null; // list of allowed values
35+
public ?string $format = null; // e.g., 'email', 'date-time'
36+
37+
// Constraints for string
38+
public ?int $minLength = null;
39+
public ?int $maxLength = null;
40+
public ?string $pattern = null;
41+
42+
// Constraints for number/integer
43+
public int|float|null $minimum = null;
44+
public int|float|null $maximum = null;
45+
public ?bool $exclusiveMinimum = null;
46+
public ?bool $exclusiveMaximum = null;
47+
public int|float|null $multipleOf = null;
48+
49+
// Constraints for array
50+
public ?array $items = null; // JSON schema for array items
51+
public ?int $minItems = null;
52+
public ?int $maxItems = null;
53+
public ?bool $uniqueItems = null;
54+
55+
// Constraints for object (primarily used when Schema is on a method or an object-typed parameter)
56+
public ?array $properties = null; // [propertyName => [schema array], ...]
57+
public ?array $required = null; // [propertyName, ...]
58+
public bool|array|null $additionalProperties = null; // true, false, or a schema array
1659

1760
/**
18-
* @param string|null $format String format (email, date-time, uri, etc.)
19-
* @param int|null $minLength Minimum string length
20-
* @param int|null $maxLength Maximum string length
21-
* @param string|null $pattern Regular expression pattern
22-
* @param int|float|null $minimum Minimum numeric value
23-
* @param int|float|null $maximum Maximum numeric value
24-
* @param bool|null $exclusiveMinimum Whether minimum is exclusive
25-
* @param bool|null $exclusiveMaximum Whether maximum is exclusive
26-
* @param int|float|null $multipleOf Value must be multiple of this number
27-
* @param ArrayItems|null $items Schema for array items
28-
* @param int|null $minItems Minimum array items
29-
* @param int|null $maxItems Maximum array items
30-
* @param bool|null $uniqueItems Whether array items must be unique
31-
* @param Property[] $properties Properties for object validation
32-
* @param string[]|null $required Required properties for objects
33-
* @param bool|Schema|null $additionalProperties Whether additional properties are allowed
34-
* @param mixed|null $enum List of allowed values
35-
* @param mixed|null $default Default value
61+
* @param array|null $definition A complete JSON schema array. If provided, other parameters are ignored.
62+
* @param Type|null $type The JSON schema type.
63+
* @param string|null $description Description of the element.
64+
* @param array|null $enum Allowed enum values.
65+
* @param string|null $format String format (e.g., 'date-time', 'email').
66+
* @param int|null $minLength Minimum length for strings.
67+
* @param int|null $maxLength Maximum length for strings.
68+
* @param string|null $pattern Regex pattern for strings.
69+
* @param int|float|null $minimum Minimum value for numbers/integers.
70+
* @param int|float|null $maximum Maximum value for numbers/integers.
71+
* @param bool|null $exclusiveMinimum Exclusive minimum.
72+
* @param bool|null $exclusiveMaximum Exclusive maximum.
73+
* @param int|float|null $multipleOf Must be a multiple of this value.
74+
* @param array|null $items JSON Schema for items if type is 'array'.
75+
* @param int|null $minItems Minimum items for an array.
76+
* @param int|null $maxItems Maximum items for an array.
77+
* @param bool|null $uniqueItems Whether array items must be unique.
78+
* @param array|null $properties Property definitions if type is 'object'. [name => schema_array].
79+
* @param array|null $required List of required properties for an object.
80+
* @param bool|array|null $additionalProperties Policy for additional properties in an object.
3681
*/
3782
public function __construct(
38-
public ?string $format = null,
39-
public ?int $minLength = null,
40-
public ?int $maxLength = null,
41-
public ?string $pattern = null,
42-
public int|float|null $minimum = null,
43-
public int|float|null $maximum = null,
44-
public ?bool $exclusiveMinimum = null,
45-
public ?bool $exclusiveMaximum = null,
46-
public int|float|null $multipleOf = null,
47-
public ?ArrayItems $items = null,
48-
public ?int $minItems = null,
49-
public ?int $maxItems = null,
50-
public ?bool $uniqueItems = null,
51-
array $properties = [],
52-
public ?array $required = null,
53-
public bool|Schema|null $additionalProperties = null,
54-
public mixed $enum = null,
55-
public mixed $default = null,
83+
?array $definition = null,
84+
?string $type = null,
85+
?string $description = null,
86+
?array $enum = null,
87+
?string $format = null,
88+
?int $minLength = null,
89+
?int $maxLength = null,
90+
?string $pattern = null,
91+
int|float|null $minimum = null,
92+
int|float|null $maximum = null,
93+
?bool $exclusiveMinimum = null,
94+
?bool $exclusiveMaximum = null,
95+
int|float|null $multipleOf = null,
96+
?array $items = null,
97+
?int $minItems = null,
98+
?int $maxItems = null,
99+
?bool $uniqueItems = null,
100+
?array $properties = null,
101+
?array $required = null,
102+
bool|array|null $additionalProperties = null
56103
) {
57-
$this->properties = $properties;
104+
if ($definition !== null) {
105+
$this->definition = $definition;
106+
} else {
107+
$this->type = $type;
108+
$this->description = $description;
109+
$this->enum = $enum;
110+
$this->format = $format;
111+
$this->minLength = $minLength;
112+
$this->maxLength = $maxLength;
113+
$this->pattern = $pattern;
114+
$this->minimum = $minimum;
115+
$this->maximum = $maximum;
116+
$this->exclusiveMinimum = $exclusiveMinimum;
117+
$this->exclusiveMaximum = $exclusiveMaximum;
118+
$this->multipleOf = $multipleOf;
119+
$this->items = $items;
120+
$this->minItems = $minItems;
121+
$this->maxItems = $maxItems;
122+
$this->uniqueItems = $uniqueItems;
123+
$this->properties = $properties;
124+
$this->required = $required;
125+
$this->additionalProperties = $additionalProperties;
126+
}
58127
}
59128

60129
/**
61-
* Convert to JSON Schema array
130+
* Converts the attribute's definition to a JSON schema array.
62131
*/
63132
public function toArray(): array
64133
{
65-
$schema = [];
66-
67-
// String constraints
68-
if ($this->format !== null) {
69-
$schema['format'] = $this->format;
70-
}
71-
if ($this->minLength !== null) {
72-
$schema['minLength'] = $this->minLength;
73-
}
74-
if ($this->maxLength !== null) {
75-
$schema['maxLength'] = $this->maxLength;
76-
}
77-
if ($this->pattern !== null) {
78-
$schema['pattern'] = $this->pattern;
134+
if ($this->definition !== null) {
135+
return [
136+
'definition' => $this->definition,
137+
];
79138
}
80139

81-
// Numeric constraints
82-
if ($this->minimum !== null) {
83-
$schema['minimum'] = $this->minimum;
84-
}
85-
if ($this->maximum !== null) {
86-
$schema['maximum'] = $this->maximum;
87-
}
88-
if ($this->exclusiveMinimum !== null) {
89-
$schema['exclusiveMinimum'] = $this->exclusiveMinimum;
90-
}
91-
if ($this->exclusiveMaximum !== null) {
92-
$schema['exclusiveMaximum'] = $this->exclusiveMaximum;
93-
}
94-
if ($this->multipleOf !== null) {
95-
$schema['multipleOf'] = $this->multipleOf;
96-
}
97-
98-
// Array constraints
99-
if ($this->items !== null) {
100-
$schema['items'] = $this->items->toArray();
140+
$schema = [];
141+
if ($this->type !== null) {
142+
$schema['type'] = $this->type;
101143
}
102-
if ($this->minItems !== null) {
103-
$schema['minItems'] = $this->minItems;
144+
if ($this->description !== null) {
145+
$schema['description'] = $this->description;
104146
}
105-
if ($this->maxItems !== null) {
106-
$schema['maxItems'] = $this->maxItems;
147+
if ($this->enum !== null) {
148+
$schema['enum'] = $this->enum;
107149
}
108-
if ($this->uniqueItems !== null) {
109-
$schema['uniqueItems'] = $this->uniqueItems;
150+
if ($this->format !== null) {
151+
$schema['format'] = $this->format;
110152
}
111153

112-
// Object constraints
113-
if (!empty($this->properties)) {
114-
$props = [];
115-
foreach ($this->properties as $property) {
116-
$props[$property->name] = $property->toArray();
117-
}
118-
$schema['properties'] = $props;
119-
}
154+
// String
155+
if ($this->minLength !== null) $schema['minLength'] = $this->minLength;
156+
if ($this->maxLength !== null) $schema['maxLength'] = $this->maxLength;
157+
if ($this->pattern !== null) $schema['pattern'] = $this->pattern;
120158

121-
if ($this->required !== null) {
122-
$schema['required'] = $this->required;
123-
}
159+
// Numeric
160+
if ($this->minimum !== null) $schema['minimum'] = $this->minimum;
161+
if ($this->maximum !== null) $schema['maximum'] = $this->maximum;
162+
if ($this->exclusiveMinimum !== null) $schema['exclusiveMinimum'] = $this->exclusiveMinimum;
163+
if ($this->exclusiveMaximum !== null) $schema['exclusiveMaximum'] = $this->exclusiveMaximum;
164+
if ($this->multipleOf !== null) $schema['multipleOf'] = $this->multipleOf;
124165

125-
if ($this->additionalProperties !== null) {
126-
if ($this->additionalProperties instanceof self) {
127-
$schema['additionalProperties'] = $this->additionalProperties->toArray();
128-
} else {
129-
$schema['additionalProperties'] = $this->additionalProperties;
130-
}
131-
}
166+
// Array
167+
if ($this->items !== null) $schema['items'] = $this->items;
168+
if ($this->minItems !== null) $schema['minItems'] = $this->minItems;
169+
if ($this->maxItems !== null) $schema['maxItems'] = $this->maxItems;
170+
if ($this->uniqueItems !== null) $schema['uniqueItems'] = $this->uniqueItems;
132171

133-
// General constraints
134-
if ($this->enum !== null) {
135-
$schema['enum'] = $this->enum;
136-
}
137-
if ($this->default !== null) {
138-
$schema['default'] = $this->default;
139-
}
172+
// Object
173+
if ($this->properties !== null) $schema['properties'] = $this->properties;
174+
if ($this->required !== null) $schema['required'] = $this->required;
175+
if ($this->additionalProperties !== null) $schema['additionalProperties'] = $this->additionalProperties;
140176

141177
return $schema;
142178
}

src/Attributes/Schema/ArrayItems.php

Lines changed: 0 additions & 58 deletions
This file was deleted.

src/Attributes/Schema/Format.php

Lines changed: 0 additions & 32 deletions
This file was deleted.

0 commit comments

Comments
 (0)