Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/Mcp/Capability/Registry/Loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,22 @@ public function load(RegistryInterface $registry): void
foreach ($resource->getMcp() ?? [] as $mcp) {
if ($mcp instanceof McpTool) {
$inputClass = $mcp->getInput()['class'] ?? $mcp->getClass();
$schema = $this->schemaFactory->buildSchema($inputClass, 'json', Schema::TYPE_INPUT, $mcp, null, [SchemaFactory::FORCE_SUBSCHEMA => true]);
$outputSchema = $this->schemaFactory->buildSchema($inputClass, 'json', Schema::TYPE_OUTPUT, $mcp, null, [SchemaFactory::FORCE_SUBSCHEMA => true]);
$inputFormat = array_first($mcp->getInputFormats() ?? ['json']);
$inputSchema = $this->schemaFactory->buildSchema($inputClass, $inputFormat, Schema::TYPE_INPUT, $mcp, null, [SchemaFactory::FORCE_SUBSCHEMA => true]);

$outputClass = $mcp->getOutput()['class'] ?? $mcp->getClass();
$outputFormat = array_first($mcp->getOutputFormats() ?? ['jsonld']);
$outputSchema = $this->schemaFactory->buildSchema($outputClass, $outputFormat, Schema::TYPE_OUTPUT, $mcp, null, [SchemaFactory::FORCE_SUBSCHEMA => true]);

$registry->registerTool(
new Tool(
name: $mcp->getName(),
inputSchema: $schema->getDefinitions()[$schema->getRootDefinitionKey()]->getArrayCopy(),
inputSchema: $inputSchema->getDefinitions()[$inputSchema->getRootDefinitionKey()]->getArrayCopy(),
description: $mcp->getDescription(),
annotations: $mcp->getAnnotations() ? ToolAnnotations::fromArray($mcp->getAnnotations()) : null,
icons: $mcp->getIcons(),
meta: $mcp->getMeta(),
outputSchema: $outputSchema->getDefinitions()[$outputSchema->getRootDefinitionKey()]->getArrayCopy(),
outputSchema: $outputSchema->getArrayCopy(),
),
self::HANDLER,
true,
Expand Down
2 changes: 1 addition & 1 deletion src/Mcp/State/StructuredContentProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public function process(mixed $data, Operation $operation, array $uriVariables =
'operation' => $operation,
]);
$serializerContext['uri_variables'] = $uriVariables;
$format = $request->getRequestFormat('') ?: 'json';
$format = $request->getRequestFormat('') ?: 'jsonld';
$structuredContent = $this->serializer->normalize($result, $format, $serializerContext);
$result = $this->serializer->encode($structuredContent, $format, $serializerContext);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Mcp/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"php": ">=8.2",
"api-platform/metadata": "^4.2",
"api-platform/json-schema": "^4.2",
"mcp/sdk": "^0.3.0"
"mcp/sdk": "^0.3.0",
"symfony/polyfill-php85": "^1.32"
},
"autoload": {
"psr-4": {
Expand Down
2 changes: 1 addition & 1 deletion src/Metadata/McpTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use Symfony\Component\WebLink\Link as WebLink;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class McpTool extends HttpOperation
class McpTool extends HttpOperation
{
/**
* @param string|null $name The name of the tool (defaults to the method name)
Expand Down
19 changes: 19 additions & 0 deletions src/Metadata/McpToolCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Metadata;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
final class McpToolCollection extends McpTool implements CollectionOperationInterface
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public function create(string $resourceClass): ResourceMetadataCollection
$resourceMetadata = $resourceMetadata->withGraphQlOperations($this->getTransformedOperations($resourceMetadata->getGraphQlOperations(), $resourceMetadata));
}

if ($resourceMetadata->getMcp()) {
$resourceMetadata = $resourceMetadata->withMcp($this->getTransformedOperations($resourceMetadata->getMcp(), $resourceMetadata));
}

$resourceMetadataCollection[$key] = $resourceMetadata;
}

Expand Down
37 changes: 37 additions & 0 deletions tests/Fixtures/TestBundle/Dto/McpBookOutputDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Fixtures\TestBundle\Dto;

use ApiPlatform\Tests\Fixtures\TestBundle\Entity\McpBook as McpBookEntity;
use Symfony\Component\ObjectMapper\Attribute\Map;

#[Map(source: McpBookEntity::class)]
final class McpBookOutputDto
{
public int $id;

public string $name;

public string $isbn;

public static function fromMcpBook(McpBookEntity $mcpBook): self
{
$mcpBookOutputDto = new self();
$mcpBookOutputDto->id = $mcpBook->getId();
$mcpBookOutputDto->name = $mcpBook->getTitle();
$mcpBookOutputDto->isbn = $mcpBook->getIsbn();

return $mcpBookOutputDto;
}
}
19 changes: 19 additions & 0 deletions tests/Fixtures/TestBundle/Dto/SearchDto.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Fixtures\TestBundle\Dto;

final class SearchDto
{
public string $search;
}
18 changes: 18 additions & 0 deletions tests/Fixtures/TestBundle/Entity/McpBook.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@

use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\McpTool;
use ApiPlatform\Metadata\McpToolCollection;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\McpBookOutputDto;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\SearchDto;
use ApiPlatform\Tests\Fixtures\TestBundle\State\McpBookListDtoProcessor;
use ApiPlatform\Tests\Fixtures\TestBundle\State\McpBookListProcessor;
use Doctrine\ORM\Mapping as ORM;

#[ApiResource(
Expand All @@ -27,6 +32,19 @@
'update_book_status' => new McpTool(
processor: [self::class, 'process']
),
'list_books' => new McpToolCollection(
description: 'List Books',
input: SearchDto::class,
processor: McpBookListProcessor::class,
structuredContent: true,
),
'list_books_dto' => new McpTool(
description: 'List Books and return a DTO',
input: SearchDto::class,
output: McpBookOutputDto::class,
processor: McpBookListDtoProcessor::class,
structuredContent: true,
),
]
)]
#[ORM\Entity]
Expand Down
47 changes: 47 additions & 0 deletions tests/Fixtures/TestBundle/State/McpBookListDtoProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Fixtures\TestBundle\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Tests\Fixtures\TestBundle\Dto\McpBookOutputDto;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\McpBook;
use Doctrine\Persistence\ManagerRegistry;

class McpBookListDtoProcessor implements ProcessorInterface
{
public function __construct(private readonly ManagerRegistry $managerRegistry)
{
}

public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ?McpBookOutputDto
{
$search = $context['data']->search ?? null;

$mcpBookRepository = $this->managerRegistry->getRepository(McpBook::class);

$queryBuilder = $mcpBookRepository->createQueryBuilder('b');
$queryBuilder
->where($queryBuilder->expr()->like('b.title', ':title'))
->setParameter(':title', '%'.$search.'%');

$book = $queryBuilder->getQuery()->getOneOrNullResult();

if ($book instanceof McpBook) {
return McpBookOutputDto::fromMcpBook($book);
}

return null;
}
}
31 changes: 31 additions & 0 deletions tests/Fixtures/TestBundle/State/McpBookListProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Tests\Fixtures\TestBundle\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\McpBook;
use Doctrine\Persistence\ManagerRegistry;

class McpBookListProcessor implements ProcessorInterface
{
public function __construct(private readonly ManagerRegistry $managerRegistry)
{
}

public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ?iterable
{
return $this->managerRegistry->getRepository(McpBook::class)->findAll();
}
}
11 changes: 11 additions & 0 deletions tests/Fixtures/app/config/config_common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ services:
tags:
- name: 'api_platform.state_processor'

ApiPlatform\Tests\Fixtures\TestBundle\State\McpBookListProcessor:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\McpBookListProcessor'
arguments: [ '@doctrine' ]
tags:
- name: 'api_platform.state_processor'

ApiPlatform\Tests\Fixtures\TestBundle\State\McpBookListDtoProcessor:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\McpBookListDtoProcessor'
arguments: [ '@doctrine' ]
tags:
- name: 'api_platform.state_processor'

ApiPlatform\Tests\Fixtures\TestBundle\State\ContainNonResourceProvider:
class: 'ApiPlatform\Tests\Fixtures\TestBundle\State\ContainNonResourceProvider'
Expand Down
Loading
Loading