From 6390130cfb3e14d5fc00887ac24018d9c062dab1 Mon Sep 17 00:00:00 2001 From: Andrei Fedoseev Date: Wed, 11 Nov 2026 12:16:44 +0100 Subject: [PATCH] [Server] Fix iterable parameters being declared as objects --- CHANGELOG.md | 1 + src/Server/Capability/Discovery/SchemaGenerator.php | 2 +- .../Capability/Discovery/SchemaGeneratorFixture.php | 4 ++++ .../Server/Capability/Discovery/SchemaGeneratorTest.php | 8 ++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb0b7062..21340de5 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. + * [BC Break] Generate `{"type": "array"}` for an `iterable` parameter, as for `array` and `iterable`, instead of `{"type": "object"}`, which refused a JSON array with `-32602` although the handler accepts one. A JSON object is refused instead, as for `array`. * 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..04dc994a 100644 --- a/src/Server/Capability/Discovery/SchemaGenerator.php +++ b/src/Server/Capability/Discovery/SchemaGenerator.php @@ -843,7 +843,7 @@ private function mapPhpTypeToJsonSchemaType(string $phpTypeString): array 'int', 'integer' => ['integer'], 'float', 'double', 'number' => ['number'], 'bool', 'boolean' => ['boolean'], - 'array' => ['array'], + 'array', 'iterable' => ['array'], 'object', 'stdclass' => ['object'], 'null' => ['null'], 'resource', 'callable' => ['object'], diff --git a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php index 14f105c3..5a474760 100644 --- a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php +++ b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorFixture.php @@ -37,6 +37,10 @@ public function typeHintsOnly(string $name, int $age, bool $active, array $tags, { } + public function iterableParameters(iterable $items, ?iterable $optional = null): void + { + } + /** * DocBlock types only - no PHP type hints, no Schema attributes. * diff --git a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php index 90e57d14..a16cf81e 100644 --- a/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php +++ b/tests/Unit/Server/Capability/Discovery/SchemaGeneratorTest.php @@ -51,6 +51,14 @@ public function testInfersBasicTypesFromPhpTypeHints(): void $this->assertEqualsCanonicalizing(['name', 'age', 'active', 'tags'], $schema['required']); } + public function testRepresentsIterableParametersAsArrays(): void + { + $schema = $this->schemaGenerator->generate(new \ReflectionMethod(SchemaGeneratorFixture::class, 'iterableParameters')); + $this->assertEquals(['type' => 'array', 'items' => new \stdClass()], $schema['properties']['items']); + $this->assertEquals(['type' => ['array', 'null'], 'default' => null, 'items' => new \stdClass()], $schema['properties']['optional']); + $this->assertSame([], (new SchemaValidator())->validateAgainstJsonSchema(['items' => [1, 2]], $schema)); + } + public function testInfersTypesAndDescriptionsFromDocBlockTags(): void { $method = new \ReflectionMethod(SchemaGeneratorFixture::class, 'docBlockOnly');