[Schema] Stop rejecting elicitation schemas the specification allows (#437)

* [Schema] Stop rejecting elicitation schemas the specification allows

The specification makes `title` optional on every elicitation schema
definition. AbstractSchemaDefinition required it, so a client reading a
conformant server's elicitation/create refused the request - and a server
building one could not omit it either.

`title` is now nullable on all seven definitions, omitted from the
serialized form when absent, and rejected only when present and not a
string.

* Fix stale title docblocks; allow omitting title where safe

* Give Number's title param a default too

* Apply suggestions from code review

Co-authored-by: Christopher Hertel <mail@christopher-hertel.de>
This commit is contained in:
Christopher Hertel 2026-08-18 19:49:15 +02:00 • committed by GitHub
commit cf2c4d4ead
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 144 additions and 65 deletions

View file

@ -21,13 +21,13 @@ use Mcp\Exception\InvalidArgumentException;
abstract class AbstractSchemaDefinition implements \JsonSerializable abstract class AbstractSchemaDefinition implements \JsonSerializable
{ {
public function __construct( public function __construct(
public readonly string $title, public readonly ?string $title = null,
public readonly ?string $description = null, public readonly ?string $description = null,
) { ) {
} }
/** /**
* Validate that title exists and is a string in the data array. * Reject a title that is present but not a string.
* *
* @param array<string, mixed> $data * @param array<string, mixed> $data
* *
@ -35,22 +35,23 @@ abstract class AbstractSchemaDefinition implements \JsonSerializable
*/ */
protected static function validateTitle(array $data, string $schemaType): void protected static function validateTitle(array $data, string $schemaType): void
{ {
if (!isset($data['title']) || !\is_string($data['title'])) { if (\array_key_exists('title', $data) && null !== $data['title'] && !\is_string($data['title'])) {
throw new InvalidArgumentException(\sprintf('Missing or invalid "title" for %s schema definition.', $schemaType)); throw new InvalidArgumentException(\sprintf('Invalid "title" for %s schema definition.', $schemaType));
} }
} }
/** /**
* Build the base JSON structure with type, title, and optional description. * Build the base JSON structure with type, optional title and description.
* *
* @return array<string, mixed> * @return array<string, mixed>
*/ */
protected function buildBaseJson(string $type): array protected function buildBaseJson(string $type): array
{ {
$data = [ $data = ['type' => $type];
'type' => $type,
'title' => $this->title, if (null !== $this->title) {
]; $data['title'] = $this->title;
}
if (null !== $this->description) { if (null !== $this->description) {
$data['description'] = $this->description; $data['description'] = $this->description;

View file

@ -19,12 +19,12 @@ namespace Mcp\Schema\Elicitation;
final class BooleanSchemaDefinition extends AbstractSchemaDefinition final class BooleanSchemaDefinition extends AbstractSchemaDefinition
{ {
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param bool|null $default Optional default value * @param bool|null $default Optional default value
*/ */
public function __construct( public function __construct(
string $title, ?string $title = null,
?string $description = null, ?string $description = null,
public readonly ?bool $default = null, public readonly ?bool $default = null,
) { ) {
@ -33,7 +33,7 @@ final class BooleanSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @param array{ * @param array{
* title: string, * title?: string,
* description?: string, * description?: string,
* default?: bool, * default?: bool,
* } $data * } $data
@ -43,7 +43,7 @@ final class BooleanSchemaDefinition extends AbstractSchemaDefinition
self::validateTitle($data, 'boolean'); self::validateTitle($data, 'boolean');
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: isset($data['default']) ? (bool) $data['default'] : null, default: isset($data['default']) ? (bool) $data['default'] : null,
); );
@ -52,7 +52,7 @@ final class BooleanSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @return array{ * @return array{
* type: string, * type: string,
* title: string, * title?: string,
* description?: string, * description?: string,
* default?: bool, * default?: bool,
* } * }

View file

@ -23,14 +23,14 @@ use Mcp\Exception\InvalidArgumentException;
final class EnumSchemaDefinition extends AbstractSchemaDefinition final class EnumSchemaDefinition extends AbstractSchemaDefinition
{ {
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param string[] $enum Array of allowed string values * @param string[] $enum Array of allowed string values
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param string|null $default Optional default value (must be in enum) * @param string|null $default Optional default value (must be in enum)
* @param string[]|null $enumNames Optional human-readable labels for each enum value * @param string[]|null $enumNames Optional human-readable labels for each enum value
*/ */
public function __construct( public function __construct(
string $title, ?string $title,
public readonly array $enum, public readonly array $enum,
?string $description = null, ?string $description = null,
public readonly ?string $default = null, public readonly ?string $default = null,
@ -59,7 +59,7 @@ final class EnumSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @param array{ * @param array{
* title: string, * title?: string,
* enum: string[], * enum: string[],
* description?: string, * description?: string,
* default?: string, * default?: string,
@ -75,7 +75,7 @@ final class EnumSchemaDefinition extends AbstractSchemaDefinition
} }
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
enum: $data['enum'], enum: $data['enum'],
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: $data['default'] ?? null, default: $data['default'] ?? null,
@ -86,7 +86,7 @@ final class EnumSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @return array{ * @return array{
* type: string, * type: string,
* title: string, * title?: string,
* enum: string[], * enum: string[],
* description?: string, * description?: string,
* default?: string, * default?: string,
@ -95,11 +95,13 @@ final class EnumSchemaDefinition extends AbstractSchemaDefinition
*/ */
public function jsonSerialize(): array public function jsonSerialize(): array
{ {
$data = [ $data = ['type' => 'string'];
'type' => 'string',
'title' => $this->title, if (null !== $this->title) {
'enum' => $this->enum, $data['title'] = $this->title;
]; }
$data['enum'] = $this->enum;
if (null !== $this->description) { if (null !== $this->description) {
$data['description'] = $this->description; $data['description'] = $this->description;

View file

@ -23,7 +23,7 @@ use Mcp\Exception\InvalidArgumentException;
final class MultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition final class MultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition
{ {
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param string[] $enum Array of allowed string values * @param string[] $enum Array of allowed string values
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param string[]|null $default Optional default selected values (must be subset of enum) * @param string[]|null $default Optional default selected values (must be subset of enum)
@ -31,7 +31,7 @@ final class MultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition
* @param int|null $maxItems Optional maximum number of selections * @param int|null $maxItems Optional maximum number of selections
*/ */
public function __construct( public function __construct(
string $title, ?string $title,
public readonly array $enum, public readonly array $enum,
?string $description = null, ?string $description = null,
public readonly ?array $default = null, public readonly ?array $default = null,
@ -73,7 +73,7 @@ final class MultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @param array{ * @param array{
* title: string, * title?: string,
* items: array{type: string, enum: string[]}, * items: array{type: string, enum: string[]},
* description?: string, * description?: string,
* default?: string[], * default?: string[],
@ -90,7 +90,7 @@ final class MultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition
} }
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
enum: $data['items']['enum'], enum: $data['items']['enum'],
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: $data['default'] ?? null, default: $data['default'] ?? null,

View file

@ -23,7 +23,7 @@ use Mcp\Exception\InvalidArgumentException;
final class NumberSchemaDefinition extends AbstractSchemaDefinition final class NumberSchemaDefinition extends AbstractSchemaDefinition
{ {
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param bool $integerOnly Whether to restrict to integer values only * @param bool $integerOnly Whether to restrict to integer values only
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param int|float|null $default Optional default value * @param int|float|null $default Optional default value
@ -31,7 +31,7 @@ final class NumberSchemaDefinition extends AbstractSchemaDefinition
* @param int|float|null $maximum Optional maximum value (inclusive) * @param int|float|null $maximum Optional maximum value (inclusive)
*/ */
public function __construct( public function __construct(
string $title, ?string $title = null,
public readonly bool $integerOnly = false, public readonly bool $integerOnly = false,
?string $description = null, ?string $description = null,
public readonly int|float|null $default = null, public readonly int|float|null $default = null,
@ -60,7 +60,7 @@ final class NumberSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @param array{ * @param array{
* type: string, * type: string,
* title: string, * title?: string,
* description?: string, * description?: string,
* default?: int|float, * default?: int|float,
* minimum?: int|float, * minimum?: int|float,
@ -75,7 +75,7 @@ final class NumberSchemaDefinition extends AbstractSchemaDefinition
$integerOnly = 'integer' === $type; $integerOnly = 'integer' === $type;
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
integerOnly: $integerOnly, integerOnly: $integerOnly,
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: $data['default'] ?? null, default: $data['default'] ?? null,
@ -87,7 +87,7 @@ final class NumberSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @return array{ * @return array{
* type: string, * type: string,
* title: string, * title?: string,
* description?: string, * description?: string,
* default?: int|float, * default?: int|float,
* minimum?: int|float, * minimum?: int|float,

View file

@ -25,7 +25,7 @@ final class StringSchemaDefinition extends AbstractSchemaDefinition
private const VALID_FORMATS = ['date', 'date-time', 'email', 'uri']; private const VALID_FORMATS = ['date', 'date-time', 'email', 'uri'];
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param string|null $default Optional default value * @param string|null $default Optional default value
* @param string|null $format Optional format constraint (date, date-time, email, uri) * @param string|null $format Optional format constraint (date, date-time, email, uri)
@ -33,7 +33,7 @@ final class StringSchemaDefinition extends AbstractSchemaDefinition
* @param int|null $maxLength Optional maximum string length * @param int|null $maxLength Optional maximum string length
*/ */
public function __construct( public function __construct(
string $title, ?string $title = null,
?string $description = null, ?string $description = null,
public readonly ?string $default = null, public readonly ?string $default = null,
public readonly ?string $format = null, public readonly ?string $format = null,
@ -61,7 +61,7 @@ final class StringSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @param array{ * @param array{
* title: string, * title?: string,
* description?: string, * description?: string,
* default?: string, * default?: string,
* format?: string, * format?: string,
@ -74,7 +74,7 @@ final class StringSchemaDefinition extends AbstractSchemaDefinition
self::validateTitle($data, 'string'); self::validateTitle($data, 'string');
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: $data['default'] ?? null, default: $data['default'] ?? null,
format: $data['format'] ?? null, format: $data['format'] ?? null,
@ -86,7 +86,7 @@ final class StringSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @return array{ * @return array{
* type: string, * type: string,
* title: string, * title?: string,
* description?: string, * description?: string,
* default?: string, * default?: string,
* format?: string, * format?: string,

View file

@ -24,13 +24,13 @@ use Mcp\Exception\InvalidArgumentException;
final class TitledEnumSchemaDefinition extends AbstractSchemaDefinition final class TitledEnumSchemaDefinition extends AbstractSchemaDefinition
{ {
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param list<array{const: string, title: string}> $oneOf Array of const/title pairs * @param list<array{const: string, title: string}> $oneOf Array of const/title pairs
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param string|null $default Optional default value (must match a const) * @param string|null $default Optional default value (must match a const)
*/ */
public function __construct( public function __construct(
string $title, ?string $title,
public readonly array $oneOf, public readonly array $oneOf,
?string $description = null, ?string $description = null,
public readonly ?string $default = null, public readonly ?string $default = null,
@ -59,7 +59,7 @@ final class TitledEnumSchemaDefinition extends AbstractSchemaDefinition
/** /**
* @param array{ * @param array{
* title: string, * title?: string,
* oneOf: list<array{const: string, title: string}>, * oneOf: list<array{const: string, title: string}>,
* description?: string, * description?: string,
* default?: string, * default?: string,
@ -74,7 +74,7 @@ final class TitledEnumSchemaDefinition extends AbstractSchemaDefinition
} }
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
oneOf: $data['oneOf'], oneOf: $data['oneOf'],
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: $data['default'] ?? null, default: $data['default'] ?? null,

View file

@ -23,7 +23,7 @@ use Mcp\Exception\InvalidArgumentException;
final class TitledMultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition final class TitledMultiSelectEnumSchemaDefinition extends AbstractSchemaDefinition
{ {
/** /**
* @param string $title Human-readable title for the field * @param ?string $title Optional human-readable title for the field
* @param list<array{const: string, title: string}> $anyOf Array of const/title pairs * @param list<array{const: string, title: string}> $anyOf Array of const/title pairs
* @param string|null $description Optional description/help text * @param string|null $description Optional description/help text
* @param string[]|null $default Optional default selected values (must be subset of anyOf consts) * @param string[]|null $default Optional default selected values (must be subset of anyOf consts)
@ -31,7 +31,7 @@ final class TitledMultiSelectEnumSchemaDefinition extends AbstractSchemaDefiniti
* @param int|null $maxItems Optional maximum number of selections * @param int|null $maxItems Optional maximum number of selections
*/ */
public function __construct( public function __construct(
string $title, ?string $title,
public readonly array $anyOf, public readonly array $anyOf,
?string $description = null, ?string $description = null,
public readonly ?array $default = null, public readonly ?array $default = null,
@ -78,7 +78,7 @@ final class TitledMultiSelectEnumSchemaDefinition extends AbstractSchemaDefiniti
/** /**
* @param array{ * @param array{
* title: string, * title?: string,
* items: array{anyOf: list<array{const: string, title: string}>}, * items: array{anyOf: list<array{const: string, title: string}>},
* description?: string, * description?: string,
* default?: string[], * default?: string[],
@ -95,7 +95,7 @@ final class TitledMultiSelectEnumSchemaDefinition extends AbstractSchemaDefiniti
} }
return new self( return new self(
title: $data['title'], title: $data['title'] ?? null,
anyOf: $data['items']['anyOf'], anyOf: $data['items']['anyOf'],
description: $data['description'] ?? null, description: $data['description'] ?? null,
default: $data['default'] ?? null, default: $data['default'] ?? null,

View file

@ -71,13 +71,23 @@ final class BooleanSchemaDefinitionTest extends TestCase
$this->assertTrue($schema->default); $this->assertTrue($schema->default);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = BooleanSchemaDefinition::fromArray([]);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for boolean schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
BooleanSchemaDefinition::fromArray([]); BooleanSchemaDefinition::fromArray(['title' => 42]);
} }
public function testJsonSerializeWithMinimalParams(): void public function testJsonSerializeWithMinimalParams(): void

View file

@ -114,13 +114,23 @@ final class EnumSchemaDefinitionTest extends TestCase
$this->assertSame(['Poor', 'Fair', 'Good'], $schema->enumNames); $this->assertSame(['Poor', 'Fair', 'Good'], $schema->enumNames);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = EnumSchemaDefinition::fromArray(['enum' => ['a', 'b']]);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for enum schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
EnumSchemaDefinition::fromArray(['enum' => ['a', 'b']]); EnumSchemaDefinition::fromArray(['title' => 42]);
} }
public function testFromArrayWithMissingEnum(): void public function testFromArrayWithMissingEnum(): void

View file

@ -137,13 +137,26 @@ final class MultiSelectEnumSchemaDefinitionTest extends TestCase
$this->assertSame(3, $schema->maxItems); $this->assertSame(3, $schema->maxItems);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = MultiSelectEnumSchemaDefinition::fromArray([
'items' => ['type' => 'string', 'enum' => ['a']],
]);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for multi-select enum schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
MultiSelectEnumSchemaDefinition::fromArray([ MultiSelectEnumSchemaDefinition::fromArray([
'title' => 42,
'items' => ['type' => 'string', 'enum' => ['a']], 'items' => ['type' => 'string', 'enum' => ['a']],
]); ]);
} }

View file

@ -133,13 +133,23 @@ final class NumberSchemaDefinitionTest extends TestCase
$this->assertSame(10, $schema->maximum); $this->assertSame(10, $schema->maximum);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = NumberSchemaDefinition::fromArray(['type' => 'integer']);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for number schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
NumberSchemaDefinition::fromArray(['type' => 'integer']); NumberSchemaDefinition::fromArray(['title' => 42]);
} }
public function testJsonSerializeAsInteger(): void public function testJsonSerializeAsInteger(): void

View file

@ -114,13 +114,23 @@ final class StringSchemaDefinitionTest extends TestCase
$this->assertSame(100, $schema->maxLength); $this->assertSame(100, $schema->maxLength);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = StringSchemaDefinition::fromArray([]);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for string schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
StringSchemaDefinition::fromArray([]); StringSchemaDefinition::fromArray(['title' => 42]);
} }
public function testJsonSerializeWithMinimalParams(): void public function testJsonSerializeWithMinimalParams(): void

View file

@ -124,13 +124,23 @@ final class TitledEnumSchemaDefinitionTest extends TestCase
$this->assertSame('b', $schema->default); $this->assertSame('b', $schema->default);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = TitledEnumSchemaDefinition::fromArray(['oneOf' => [['const' => 'a', 'title' => 'A']]]);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for titled enum schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
TitledEnumSchemaDefinition::fromArray(['oneOf' => [['const' => 'a', 'title' => 'A']]]); TitledEnumSchemaDefinition::fromArray(['title' => 42]);
} }
public function testFromArrayWithMissingOneOf(): void public function testFromArrayWithMissingOneOf(): void

View file

@ -166,13 +166,26 @@ final class TitledMultiSelectEnumSchemaDefinitionTest extends TestCase
$this->assertSame(2, $schema->maxItems); $this->assertSame(2, $schema->maxItems);
} }
public function testFromArrayWithMissingTitle(): void public function testFromArrayWithoutTitleIsAccepted(): void
{
// `title` is optional in the specification, so a server that omits it
// must still be readable.
$schema = TitledMultiSelectEnumSchemaDefinition::fromArray([
'items' => ['anyOf' => [['const' => 'a', 'title' => 'A']]],
]);
$this->assertNull($schema->title);
$this->assertArrayNotHasKey('title', $schema->jsonSerialize());
}
public function testFromArrayRejectsNonStringTitle(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Missing or invalid "title"'); $this->expectExceptionMessage('Invalid "title" for titled multi-select enum schema definition.');
/* @phpstan-ignore argument.type */ /* @phpstan-ignore argument.type */
TitledMultiSelectEnumSchemaDefinition::fromArray([ TitledMultiSelectEnumSchemaDefinition::fromArray([
'title' => 42,
'items' => ['anyOf' => [['const' => 'a', 'title' => 'A']]], 'items' => ['anyOf' => [['const' => 'a', 'title' => 'A']]],
]); ]);
} }