From 5ef30598e04b8bc0d5459a243a0d6ad2fbd41734 Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Fri, 31 Jul 2026 01:13:21 +0000 Subject: [PATCH 1/3] fix(mcp): return resource read results for resources --- src/Mcp/State/StructuredContentProcessor.php | 10 +++++ .../State/StructuredContentProcessorTest.php | 42 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/Mcp/State/StructuredContentProcessor.php b/src/Mcp/State/StructuredContentProcessor.php index 1a92b43b51a..842304c9952 100644 --- a/src/Mcp/State/StructuredContentProcessor.php +++ b/src/Mcp/State/StructuredContentProcessor.php @@ -19,6 +19,7 @@ use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\SerializerContextBuilderInterface; use Mcp\Schema\Content\TextContent; +use Mcp\Schema\Content\TextResourceContents; use Mcp\Schema\JsonRpc\Response; use Mcp\Schema\Result\CallToolResult; use Mcp\Schema\Result\ReadResourceResult; @@ -71,6 +72,15 @@ public function process(mixed $data, Operation $operation, array $uriVariables = } } + if ($operation instanceof McpResource) { + return new Response( + $context['mcp_request']->getId(), + new ReadResourceResult([ + new TextResourceContents($operation->getUri(), $operation->getMimeType() ?? 'application/json', $result), + ]), + ); + } + return new Response( $context['mcp_request']->getId(), new CallToolResult( diff --git a/src/Mcp/Tests/State/StructuredContentProcessorTest.php b/src/Mcp/Tests/State/StructuredContentProcessorTest.php index 305a8f718f5..390bfc0b988 100644 --- a/src/Mcp/Tests/State/StructuredContentProcessorTest.php +++ b/src/Mcp/Tests/State/StructuredContentProcessorTest.php @@ -14,13 +14,16 @@ namespace ApiPlatform\Mcp\Tests\State; use ApiPlatform\Mcp\State\StructuredContentProcessor; +use ApiPlatform\Metadata\McpResource; use ApiPlatform\Metadata\McpTool; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\State\SerializerContextBuilderInterface; use Mcp\Schema\Content\TextContent; +use Mcp\Schema\Content\TextResourceContents; use Mcp\Schema\JsonRpc\Request; use Mcp\Schema\JsonRpc\Response; use Mcp\Schema\Result\CallToolResult; +use Mcp\Schema\Result\ReadResourceResult; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request as HttpRequest; use Symfony\Component\Serializer\Encoder\EncoderInterface; @@ -81,6 +84,45 @@ public function testTextContentIsPopulatedWhenStructuredContentIsDisabled(): voi $this->assertNotSame('{}', $textContent->text); $this->assertSame($expectedJson, $textContent->text); } + + public function testMcpResourceReturnsReadResourceResult(): void + { + $expectedJson = '{"name":"foo"}'; + $resourceUri = 'app://dummy'; + $resourceMimeType = 'application/json'; + + $decorated = $this->createMock(ProcessorInterface::class); + $decorated->method('process')->willReturn(new \stdClass()); + + $serializer = $this->createMock(SerializerEncoderNormalizer::class); + $serializer->method('normalize')->willReturn(['name' => 'foo']); + $serializer->method('encode')->willReturn($expectedJson); + + $contextBuilder = $this->createMock(SerializerContextBuilderInterface::class); + $contextBuilder->method('createFromRequest')->willReturn([]); + + $processor = new StructuredContentProcessor($serializer, $contextBuilder, $decorated); + + $operation = (new McpResource(uri: $resourceUri, mimeType: $resourceMimeType))->withClass(\stdClass::class); + + $mcpRequest = $this->createMock(Request::class); + $mcpRequest->method('getId')->willReturn('req-1'); + + /** @var Response $response */ + $response = $processor->process([], $operation, [], [ + 'mcp_request' => $mcpRequest, + 'request' => new HttpRequest(), + ]); + + $result = $response->result; + $this->assertInstanceOf(ReadResourceResult::class, $result); + + $resourceContents = $result->contents[0]; + $this->assertInstanceOf(TextResourceContents::class, $resourceContents); + $this->assertSame($resourceUri, $resourceContents->uri); + $this->assertSame($resourceMimeType, $resourceContents->mimeType); + $this->assertSame($expectedJson, $resourceContents->text); + } } /** From 4ea680245f26f36771453ba74e0e0961f2328e0b Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Fri, 31 Jul 2026 08:43:53 +0000 Subject: [PATCH 2/3] fix(mcp): allow current mcp sdk Signed-off-by: Ousama Ben Younes --- composer.json | 2 +- src/Mcp/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index e1b841d1530..4772ec8fc31 100644 --- a/composer.json +++ b/composer.json @@ -142,7 +142,7 @@ "jangregor/phpstan-prophecy": "^2.1.11", "justinrainbow/json-schema": "^6.5.2", "laravel/framework": "^11.0 || ^12.0 || ^13.0", - "mcp/sdk": "^0.6", + "mcp/sdk": "^0.7", "orchestra/testbench": "^10.9 || ^11.0", "phpspec/prophecy-phpunit": "^2.2", "phpstan/extension-installer": "^1.1", diff --git a/src/Mcp/composer.json b/src/Mcp/composer.json index a824d20b63d..608d3f2425b 100644 --- a/src/Mcp/composer.json +++ b/src/Mcp/composer.json @@ -30,7 +30,7 @@ "php": ">=8.2", "api-platform/metadata": "^5.0@alpha", "api-platform/json-schema": "^5.0@alpha", - "mcp/sdk": "^0.6", + "mcp/sdk": "^0.7", "symfony/object-mapper": "^7.4 || ^8.0", "symfony/polyfill-php85": "^1.32" }, From 6c18dc3645b88e280dd756174a8a1c74b5e8e741 Mon Sep 17 00:00:00 2001 From: Ousama Ben Younes Date: Fri, 31 Jul 2026 08:54:21 +0000 Subject: [PATCH 3/3] fix(mcp): adapt to current mcp bundle Signed-off-by: Ousama Ben Younes --- src/Laravel/ApiPlatformProvider.php | 4 +++- .../Factory/SerializerPropertyMetadataFactory.php | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/Laravel/ApiPlatformProvider.php b/src/Laravel/ApiPlatformProvider.php index 4cc6fd7aa1f..aaca8987c0f 100644 --- a/src/Laravel/ApiPlatformProvider.php +++ b/src/Laravel/ApiPlatformProvider.php @@ -193,6 +193,7 @@ use PHPStan\PhpDocParser\Parser\PhpDocParser; use Psr\Log\LoggerInterface; use Symfony\AI\McpBundle\Controller\McpController; +use Symfony\AI\McpBundle\Http\MiddlewareFactory; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory; use Symfony\Component\HttpFoundation\RequestStack; @@ -1342,7 +1343,8 @@ private function registerMcp(): void $psrHttpFactory, $httpFoundationFactory, $psr17Factory, - $psr17Factory + $psr17Factory, + new MiddlewareFactory() ); }); } diff --git a/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php b/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php index bb430c46f80..05b3c605180 100644 --- a/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php +++ b/src/Metadata/Property/Factory/SerializerPropertyMetadataFactory.php @@ -57,14 +57,6 @@ public function create(string $resourceClass, string $property, array $options = [$normalizationAttributes, $denormalizationAttributes] = $this->getEffectiveSerializerAttributes($options); - if ($normalizationAttributes && !\is_array($normalizationAttributes)) { - $normalizationAttributes = [$normalizationAttributes]; - } - - if ($denormalizationAttributes && !\is_array($denormalizationAttributes)) { - $denormalizationAttributes = [$denormalizationAttributes]; - } - $ignoredAttributes = $options['ignored_attributes'] ?? []; } catch (ResourceClassNotFoundException) { // TODO: for input/output classes, the serializer groups must be read from the actual resource class