From 51ce453d1a2d725fe61184fcb87a95b813e6d241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Sun, 26 Jul 2026 18:45:50 +0200 Subject: [PATCH] Removed MCP Schema Factory decorator as fix was merge and the upstream version actually contained more fixes --- src/Mcp/JsonSchema/FixedSchemaFactory.php | 162 ---------------------- 1 file changed, 162 deletions(-) delete mode 100644 src/Mcp/JsonSchema/FixedSchemaFactory.php diff --git a/src/Mcp/JsonSchema/FixedSchemaFactory.php b/src/Mcp/JsonSchema/FixedSchemaFactory.php deleted file mode 100644 index c851c394..00000000 --- a/src/Mcp/JsonSchema/FixedSchemaFactory.php +++ /dev/null @@ -1,162 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace App\Mcp\JsonSchema; - -use ApiPlatform\JsonSchema\Schema; -use ApiPlatform\JsonSchema\SchemaFactoryInterface; -use ApiPlatform\Metadata\Operation; -use Symfony\Component\DependencyInjection\Attribute\AsAlias; -use Symfony\Component\DependencyInjection\Attribute\AsDecorator; -use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated; - -/** - * Overwrite the default JSON Schema factory to resolve $ref and allOf into a flat schema. - * This is a workaround until https://github.com/api-platform/core/pull/7962 is merged - */ -#[AsAlias('api_platform.mcp.json_schema.schema_factory')] -readonly class FixedSchemaFactory implements SchemaFactoryInterface -{ - public function __construct( - private readonly SchemaFactoryInterface $decorated, - ) { - } - - public function buildSchema(string $className, string $format = 'json', string $type = Schema::TYPE_OUTPUT, ?Operation $operation = null, ?Schema $schema = null, ?array $serializerContext = null, bool $forceCollection = false): Schema - { - $schema = $this->decorated->buildSchema($className, $format, $type, $operation, $schema, $serializerContext, $forceCollection); - - $definitions = []; - foreach ($schema->getDefinitions() as $key => $definition) { - $definitions[$key] = $definition instanceof \ArrayObject ? $definition->getArrayCopy() : (array) $definition; - } - - $rootKey = $schema->getRootDefinitionKey(); - if (null !== $rootKey) { - $root = $definitions[$rootKey] ?? []; - } else { - // Collection schemas (and others) put allOf/type directly on the root - $root = $schema->getArrayCopy(false); - } - - $flat = self::resolveNode($root, $definitions); - - $flatSchema = new Schema(Schema::VERSION_JSON_SCHEMA); - unset($flatSchema['$schema']); - foreach ($flat as $key => $value) { - $flatSchema[$key] = $value; - } - - return $flatSchema; - } - - /** - * Recursively resolve $ref, allOf, and nested structures into a flat schema node. - * - * @param array $resolving Tracks the current $ref resolution chain to detect circular references - */ - public static function resolveNode(array|\ArrayObject $node, array $definitions, array &$resolving = []): array - { - if ($node instanceof \ArrayObject) { - $node = $node->getArrayCopy(); - } - - if (isset($node['$ref'])) { - $refKey = str_replace('#/definitions/', '', $node['$ref']); - if (!isset($definitions[$refKey]) || isset($resolving[$refKey])) { - return ['type' => 'object']; - } - $resolving[$refKey] = true; - $resolved = self::resolveNode($definitions[$refKey], $definitions, $resolving); - unset($resolving[$refKey]); - - return $resolved; - } - - if (isset($node['allOf'])) { - $merged = ['type' => 'object', 'properties' => []]; - $requiredSets = []; - foreach ($node['allOf'] as $entry) { - $resolved = self::resolveNode($entry, $definitions, $resolving); - if (isset($resolved['properties'])) { - foreach ($resolved['properties'] as $k => $v) { - $merged['properties'][$k] = $v; - } - } - if (isset($resolved['required'])) { - $requiredSets[] = $resolved['required']; - } - } - - if ($requiredSets) { - $merged['required'] = array_merge(...$requiredSets); - } - if ([] === $merged['properties']) { - unset($merged['properties']); - } - if (isset($node['description'])) { - $merged['description'] = $node['description']; - } - - return self::resolveDeep($merged, $definitions, $resolving); - } - - // oneOf/anyOf nodes must not receive a type fallback — their type is expressed - // through the sub-schemas. Adding 'type: object' here would break schemas like - // HydraItemBaseSchema's @context, which is oneOf: [string, object]. - if (isset($node['oneOf']) || isset($node['anyOf'])) { - return self::resolveDeep($node, $definitions, $resolving); - } - - if (!isset($node['type'])) { - $node['type'] = 'object'; - } - - return self::resolveDeep($node, $definitions, $resolving); - } - - /** - * Recursively resolve nested properties and array items. - */ - private static function resolveDeep(array $node, array $definitions, array &$resolving): array - { - if (isset($node['items'])) { - $node['items'] = self::resolveNode( - $node['items'] instanceof \ArrayObject ? $node['items']->getArrayCopy() : $node['items'], - $definitions, - $resolving, - ); - } - - if (isset($node['properties']) && \is_array($node['properties'])) { - foreach ($node['properties'] as $propName => $propSchema) { - $node['properties'][$propName] = self::resolveNode( - $propSchema instanceof \ArrayObject ? $propSchema->getArrayCopy() : $propSchema, - $definitions, - $resolving, - ); - } - } - - return $node; - } -}