diff --git a/CHANGELOG.md b/CHANGELOG.md index eb0b7062..05c64549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,7 @@ All notable changes to `mcp/sdk` will be documented in this file. * Fix an `int` parameter receiving a wrong value: a number beyond the integer range wrapped around, also past the schema's `minimum` or `maximum`, a numeric string beyond it became `PHP_INT_MAX` or `PHP_INT_MIN`, and `"--5"` became `0`. They are refused now. * Fix a nullable parameter with a default (e.g. `?string $category = 'books'`) receiving the default when the client sends `null`. * Fix DocBlock types producing wrong schemas, so valid arguments were rejected with `-32602`: PHPStan/Psalm pseudo-types and literal types (`non-empty-string`, `positive-int`, `'asc'|'desc'`, `array`) get the JSON Schema type of their base type, nullable and union keys of an array shape (`array{nickname: string|null}`) and union elements of a generic (`array`) get their own types instead of `object`, and a `null` inside them, or in a class name, no longer makes the parameter nullable. + * Fix a variadic enum parameter (e.g. `Status ...$statuses`) declaring its items as `{"type": "object"}`, so every call was rejected with `-32602`: the items list the enum's values, as for a single enum parameter. * Fix tool name validation to follow the spec (1 to 128 characters of `A-Z`, `a-z`, `0-9`, `_`, `-` and `.`): names of 65 to 128 characters no longer log a warning, names with a `/` or a trailing newline do. * Fix `content`, `messages` and `contents` being sent as a JSON object when the handler returned a non-list array, e.g. from `array_filter()`, and named keys in nested mixed arrays overwriting each other. * Fix a scalar `structuredContent` on `2026-07-28` going out as raw text in `content`: `'sunny'` is sent as the JSON `"sunny"`, as SEP-2106 requires. diff --git a/src/Server/Capability/Discovery/SchemaGenerator.php b/src/Server/Capability/Discovery/SchemaGenerator.php index 4b26e7a1..3fbf9366 100644 --- a/src/Server/Capability/Discovery/SchemaGenerator.php +++ b/src/Server/Capability/Discovery/SchemaGenerator.php @@ -366,10 +366,14 @@ private function buildVariadicParameterSchema(array $paramInfo): array // If no items specified by Schema attribute, infer from type if (!isset($paramSchema['items'])) { + // Reflection gives a variadic its element type, so an enum constrains each item. + $enumItems = $this->applyEnumConstraints([], $paramInfo); $itemJsonTypes = $this->mapPhpTypeToJsonSchemaType($paramInfo['type_string']); $nonNullItemTypes = array_filter($itemJsonTypes, static fn ($t) => 'null' !== $t); - if (1 === \count($nonNullItemTypes)) { + if ([] !== $enumItems) { + $paramSchema['items'] = $enumItems; + } elseif (1 === \count($nonNullItemTypes)) { $paramSchema['items'] = ['type' => $nonNullItemTypes[0]]; } } diff --git a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php index 14f105c3..a6cefbf6 100644 --- a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php +++ b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php @@ -513,6 +513,14 @@ public function untypedVariadic(...$values): void { } + public function variadicBackedEnum(BackedStringEnum ...$options): void + { + } + + public function variadicUnitEnum(UnitEnum ...$answers): void + { + } + /** * Variadic with Schema constraints. * diff --git a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php index 90e57d14..0da6c12d 100644 --- a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php +++ b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php @@ -527,6 +527,16 @@ public function testRepresentsVariadicStringParametersAsArrayOfStrings(): void $this->assertArrayNotHasKey('required', $schema); } + public function testRepresentsVariadicEnumParametersAsArrayOfEnumValues(): void + { + $backed = $this->schemaGenerator->generate(new \ReflectionMethod(SchemaGeneratorFixture::class, 'variadicBackedEnum')); + $this->assertEquals(['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['A', 'B']]], $backed['properties']['options']); + $this->assertSame([], (new SchemaValidator())->validateAgainstJsonSchema(['options' => ['A', 'B']], $backed)); + + $unit = $this->schemaGenerator->generate(new \ReflectionMethod(SchemaGeneratorFixture::class, 'variadicUnitEnum')); + $this->assertEquals(['type' => 'array', 'items' => ['type' => 'string', 'enum' => ['Yes', 'No']]], $unit['properties']['answers']); + } + public function testAppliesItemConstraintsToVariadicParameters(): void { $method = new \ReflectionMethod(SchemaGeneratorFixture::class, 'variadicWithConstraints');