[Server] Always emit items for array tool parameter schemas (#378)

Array tool parameters without an inferable element type generated `{"type":"array"}` with no `items`, which strict MCP clients (VS Code / Copilot) reject. Always emit `items` (`{}` when the element type is unknown) and recover the element type for nullable typed arrays such as `string[]|null`.

Fixes #151
This commit is contained in:
Valeriu Gutu 2026-07-15 00:56:50 +03:00 • committed by GitHub
commit 8e18bfbdf1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -223,7 +223,7 @@ final class SchemaGenerator implements SchemaGeneratorInterface
private function buildParameterSchema(array $paramInfo, ?array $methodLevelParamSchema): array
{
if ($paramInfo['is_variadic']) {
return $this->buildVariadicParameterSchema($paramInfo);
return $this->ensureArrayItems($this->buildVariadicParameterSchema($paramInfo));
}
$inferredSchema = $this->buildInferredParameterSchema($paramInfo);
@ -240,7 +240,34 @@ final class SchemaGenerator implements SchemaGeneratorInterface
$mergedSchema = array_merge($mergedSchema, $parameterLevelSchema);
}
return $mergedSchema;
// Run after all merges so that when a Schema attribute reshapes the parameter
// (e.g. to `object`), the array `items` invariant is only enforced on what is
// genuinely still an array.
return $this->ensureArrayItems($mergedSchema);
}
/**
* Guarantees an array-typed schema always declares `items`.
*
* `items` is optional in JSON Schema, but some strict clients reject an array schema
* without it. When no element type could be inferred, default to the empty schema `{}`
* (matches anything) — represented as `new \stdClass()` so it serializes to `{}` rather
* than `[]`.
*
* @param array<string, mixed> $schema
*
* @return array<string, mixed>
*/
private function ensureArrayItems(array $schema): array
{
$type = $schema['type'] ?? null;
$isArray = 'array' === $type || (\is_array($type) && \in_array('array', $type, true));
if ($isArray && !isset($schema['items'])) {
$schema['items'] = new \stdClass();
}
return $schema;
}
/**
@ -693,6 +720,11 @@ final class SchemaGenerator implements SchemaGeneratorInterface
{
$normalizedType = trim($phpTypeString);
// Strip a top-level nullable union (e.g. `string[]|null`, `null|int[]`) so the
// element type is still recovered; array nullability is handled separately via
// `allows_null`. Internal unions such as `array<int|string>` are left untouched.
$normalizedType = trim((string) preg_replace('/^null\s*\|\s*|\s*\|\s*null$/i', '', $normalizedType));
// Case 1: Simple T[] syntax (e.g., string[], int[], bool[], etc.)
if (preg_match('/^(\\??)([\w\\\\]+)\\s*\\[\\]$/i', $normalizedType, $matches)) {
$itemType = strtolower($matches[2]);