diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2524ecf066..04acb81158 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -375,6 +375,7 @@ jobs: - api-platform/openapi - api-platform/graphql - api-platform/http-cache + - api-platform/mcp - api-platform/ramsey-uuid - api-platform/serializer - api-platform/state diff --git a/src/Mcp/State/ToolProvider.php b/src/Mcp/State/ToolProvider.php index dff2e8874e..8ed7f761d1 100644 --- a/src/Mcp/State/ToolProvider.php +++ b/src/Mcp/State/ToolProvider.php @@ -30,7 +30,7 @@ public function __construct(private readonly ObjectMapperInterface $objectMapper public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null { - if (!isset($context['mcp_request'])) { + if (!isset($context['mcp_request'], $context['mcp_data'])) { return null; } diff --git a/src/Mcp/Tests/State/ToolProviderTest.php b/src/Mcp/Tests/State/ToolProviderTest.php new file mode 100644 index 0000000000..15308a6d1e --- /dev/null +++ b/src/Mcp/Tests/State/ToolProviderTest.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace ApiPlatform\Mcp\Tests\State; + +use ApiPlatform\Mcp\State\ToolProvider; +use ApiPlatform\Metadata\McpResource; +use Mcp\Schema\Request\ReadResourceRequest; +use PHPUnit\Framework\TestCase; +use Symfony\Component\ObjectMapper\ObjectMapperInterface; + +class ToolProviderTest extends TestCase +{ + /** + * The handler installs this provider on every MCP operation that declares none, + * MCP resources included, but it only fills `mcp_data` for a tool call: reading + * a resource must not be mapped from a payload that does not exist. + */ + public function testProvideReturnsNullWhenTheRequestCarriesNoToolPayload(): void + { + $objectMapper = $this->createMock(ObjectMapperInterface::class); + $objectMapper->expects($this->never())->method('map'); + + $provider = new ToolProvider($objectMapper); + + $operation = new McpResource(uri: 'dummy://docs', name: 'docs', class: \stdClass::class); + + $this->assertNull($provider->provide($operation, [], [ + 'mcp_request' => new ReadResourceRequest('dummy://docs'), + ])); + } +} diff --git a/src/Mcp/composer.json b/src/Mcp/composer.json index 22fc743f32..24bab2d78c 100644 --- a/src/Mcp/composer.json +++ b/src/Mcp/composer.json @@ -30,12 +30,17 @@ "php": ">=8.2", "api-platform/metadata": "^5.0@alpha", "api-platform/json-schema": "^5.0@alpha", + "api-platform/state": "^5.0@alpha", "mcp/sdk": "^0.8", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/http-foundation": "^7.4 || ^8.0", "symfony/object-mapper": "^7.4 || ^8.0", - "symfony/polyfill-php85": "^1.32" + "symfony/polyfill-php85": "^1.32", + "symfony/serializer": "^7.4 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^11.5 || ^12.2" + "phpunit/phpunit": "^11.5 || ^12.2", + "symfony/expression-language": "^7.4 || ^8.0" }, "autoload": { "psr-4": { @@ -60,6 +65,12 @@ "url": "https://github.com/api-platform/api-platform" } }, + "suggest": { + "symfony/expression-language": "To use the operation-level \"security\" expressions." + }, + "scripts": { + "test": "./vendor/bin/phpunit" + }, "minimum-stability": "beta", "prefer-stable": true }