fix(mcp) : return CallToolResult to prevent structuredContent serialization issue

Tools now return CallToolResult directly instead of Content arrays,
preventing the MCP SDK from auto-generating structuredContent as a
JSON array (which Claude Code rejects — expects a JSON object/record).
Also adds Accept header to test helpers and SSE response parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-03-16 17:24:04 +01:00
parent f965affc94
commit add3a9a21f
60 changed files with 156 additions and 84 deletions

View File

@@ -1,14 +1,12 @@
{ {
"mcpServers": { "mcpServers": {
"inventory": { "inventory": {
"command": "docker", "type": "http",
"args": [ "url": "http://inventory.malio-dev.fr/_mcp",
"exec", "-i", "headers": {
"-e", "MCP_PROFILE_ID=REPLACE_WITH_YOUR_PROFILE_ID", "X-Profile-Id": "admin-default-profile",
"-e", "MCP_PROFILE_PASSWORD=REPLACE_WITH_YOUR_PASSWORD", "X-Profile-Password": "A123"
"php-inventory-apache", }
"php", "bin/console", "mcp:server" }
]
} }
}
} }

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool;
use App\Repository\AuditLogRepository; use App\Repository\AuditLogRepository;
use DateTimeInterface; use DateTimeInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -22,7 +23,7 @@ class ActivityLogTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $entityType = '', string $action = ''): array public function __invoke(int $page = 1, int $limit = 30, string $entityType = '', string $action = ''): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_VIEWER'); $this->requireRole($this->security, 'ROLE_VIEWER');

View File

@@ -9,6 +9,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ProfileRepository; use App\Repository\ProfileRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -30,7 +31,7 @@ class CreateCommentTool
string $entityType, string $entityType,
string $entityId, string $entityId,
string $entityName = '', string $entityName = '',
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_VIEWER'); $this->requireRole($this->security, 'ROLE_VIEWER');
$content = trim($content); $content = trim($content);

View File

@@ -8,6 +8,7 @@ use App\Entity\Comment;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_comments', name: 'list_comments',
@@ -21,7 +22,7 @@ class ListCommentsTool
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
) {} ) {}
public function __invoke(string $entityType, string $entityId, int $page = 1, int $limit = 30): array public function __invoke(string $entityType, string $entityId, int $page = 1, int $limit = 30): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -10,6 +10,7 @@ use App\Repository\ProfileRepository;
use DateTimeImmutable; use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -26,7 +27,7 @@ class ResolveCommentTool
private readonly ProfileRepository $profiles, private readonly ProfileRepository $profiles,
) {} ) {}
public function __invoke(string $commentId): array public function __invoke(string $commentId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -8,6 +8,7 @@ use App\Entity\Comment;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_unresolved_comments_count', name: 'get_unresolved_comments_count',
@@ -21,7 +22,7 @@ class UnresolvedCountTool
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
) {} ) {}
public function __invoke(): array public function __invoke(): CallToolResult
{ {
$count = (int) $this->em->getRepository(Comment::class) $count = (int) $this->em->getRepository(Comment::class)
->createQueryBuilder('c') ->createQueryBuilder('c')

View File

@@ -10,6 +10,7 @@ use App\Repository\ConstructeurRepository;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -37,7 +38,7 @@ class CreateComposantTool
string $prix = '', string $prix = '',
string $modelTypeId = '', string $modelTypeId = '',
array $constructeurIds = [], array $constructeurIds = [],
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$composant = new Composant(); $composant = new Composant();

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ComposantRepository; use App\Repository\ComposantRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteComposantTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $composantId): array public function __invoke(string $composantId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Composant;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ComposantRepository; use App\Repository\ComposantRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_composant', name: 'get_composant',
@@ -20,7 +21,7 @@ class GetComposantTool
private readonly ComposantRepository $composants, private readonly ComposantRepository $composants,
) {} ) {}
public function __invoke(string $composantId): array public function __invoke(string $composantId): CallToolResult
{ {
$composant = $this->composants->find($composantId); $composant = $this->composants->find($composantId);

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Composant;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ComposantRepository; use App\Repository\ComposantRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_composants', name: 'list_composants',
@@ -20,7 +21,7 @@ class ListComposantsTool
private readonly ComposantRepository $composants, private readonly ComposantRepository $composants,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $search = ''): array public function __invoke(int $page = 1, int $limit = 30, string $search = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -10,6 +10,7 @@ use App\Repository\ConstructeurRepository;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -39,7 +40,7 @@ class UpdateComposantTool
?string $prix = null, ?string $prix = null,
?string $modelTypeId = null, ?string $modelTypeId = null,
?array $constructeurIds = null, ?array $constructeurIds = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$composant = $this->composants->find($composantId); $composant = $this->composants->find($composantId);

View File

@@ -8,6 +8,7 @@ use App\Entity\Constructeur;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -27,7 +28,7 @@ class CreateConstructeurTool
string $name, string $name,
string $email = '', string $email = '',
string $phone = '', string $phone = '',
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$constructeur = new Constructeur(); $constructeur = new Constructeur();

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ConstructeurRepository; use App\Repository\ConstructeurRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteConstructeurTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $constructeurId): array public function __invoke(string $constructeurId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Constructeur;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ConstructeurRepository; use App\Repository\ConstructeurRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_constructeur', name: 'get_constructeur',
@@ -20,7 +21,7 @@ class GetConstructeurTool
private readonly ConstructeurRepository $constructeurs, private readonly ConstructeurRepository $constructeurs,
) {} ) {}
public function __invoke(string $constructeurId): array public function __invoke(string $constructeurId): CallToolResult
{ {
$constructeur = $this->constructeurs->find($constructeurId); $constructeur = $this->constructeurs->find($constructeurId);

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Constructeur;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ConstructeurRepository; use App\Repository\ConstructeurRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_constructeurs', name: 'list_constructeurs',
@@ -20,7 +21,7 @@ class ListConstructeursTool
private readonly ConstructeurRepository $constructeurs, private readonly ConstructeurRepository $constructeurs,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $search = ''): array public function __invoke(int $page = 1, int $limit = 30, string $search = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ConstructeurRepository; use App\Repository\ConstructeurRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -29,7 +30,7 @@ class UpdateConstructeurTool
?string $name = null, ?string $name = null,
?string $email = null, ?string $email = null,
?string $phone = null, ?string $phone = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$constructeur = $this->constructeurs->find($constructeurId); $constructeur = $this->constructeurs->find($constructeurId);

View File

@@ -8,6 +8,7 @@ use App\Entity\CustomFieldValue;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -23,7 +24,7 @@ class DeleteCustomFieldValueTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $customFieldValueId): array public function __invoke(string $customFieldValueId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -8,6 +8,7 @@ use App\Entity\CustomFieldValue;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_custom_field_values', name: 'list_custom_field_values',
@@ -23,7 +24,7 @@ class ListCustomFieldValuesTool
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
) {} ) {}
public function __invoke(string $entityType, string $entityId): array public function __invoke(string $entityType, string $entityId): CallToolResult
{ {
$entityType = strtolower($entityType); $entityType = strtolower($entityType);

View File

@@ -13,6 +13,7 @@ use App\Entity\Product;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -30,7 +31,7 @@ class UpsertCustomFieldValuesTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $entityType, string $entityId, array $fields): array public function __invoke(string $entityType, string $entityId, array $fields): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -12,6 +12,7 @@ use App\Repository\SiteRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Content\TextContent; use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_dashboard_stats', name: 'get_dashboard_stats',
@@ -28,14 +29,14 @@ class DashboardStatsTool
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
) {} ) {}
public function __invoke(): array public function __invoke(): CallToolResult
{ {
$unresolvedComments = (int) $this->em->createQuery( $unresolvedComments = (int) $this->em->createQuery(
"SELECT COUNT(c.id) FROM App\\Entity\\Comment c WHERE c.status = 'open'" "SELECT COUNT(c.id) FROM App\\Entity\\Comment c WHERE c.status = 'open'"
)->getSingleScalarResult(); )->getSingleScalarResult();
return [ return new CallToolResult(
new TextContent( content: [new TextContent(
text: json_encode([ text: json_encode([
'machines' => $this->machines->count([]), 'machines' => $this->machines->count([]),
'pieces' => $this->pieces->count([]), 'pieces' => $this->pieces->count([]),
@@ -44,7 +45,7 @@ class DashboardStatsTool
'sites' => $this->sites->count([]), 'sites' => $this->sites->count([]),
'unresolvedComments' => $unresolvedComments, 'unresolvedComments' => $unresolvedComments,
], JSON_THROW_ON_ERROR) ], JSON_THROW_ON_ERROR)
), )],
]; );
} }
} }

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\DocumentRepository; use App\Repository\DocumentRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteDocumentTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $documentId): array public function __invoke(string $documentId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Document;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\DocumentRepository; use App\Repository\DocumentRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_documents', name: 'list_documents',
@@ -28,7 +29,7 @@ class ListDocumentsTool
private readonly DocumentRepository $documents, private readonly DocumentRepository $documents,
) {} ) {}
public function __invoke(string $entityType, string $entityId): array public function __invoke(string $entityType, string $entityId): CallToolResult
{ {
if (!isset(self::ENTITY_FIELDS[$entityType])) { if (!isset(self::ENTITY_FIELDS[$entityType])) {
$this->mcpError('validation', "Invalid entityType '{$entityType}'. Must be one of: site, machine, composant, piece, product."); $this->mcpError('validation', "Invalid entityType '{$entityType}'. Must be one of: site, machine, composant, piece, product.");

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool;
use App\Repository\AuditLogRepository; use App\Repository\AuditLogRepository;
use DateTimeInterface; use DateTimeInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class EntityHistoryTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $entityType, string $entityId): array public function __invoke(string $entityType, string $entityId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_VIEWER'); $this->requireRole($this->security, 'ROLE_VIEWER');

View File

@@ -16,6 +16,7 @@ use App\Repository\PieceRepository;
use App\Repository\ProductRepository; use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -37,7 +38,7 @@ class AddMachineLinksTool
private readonly MachinePieceLinkRepository $pieceLinks, private readonly MachinePieceLinkRepository $pieceLinks,
) {} ) {}
public function __invoke(string $machineId, array $links): array public function __invoke(string $machineId, array $links): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -18,6 +18,7 @@ use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository; use App\Repository\MachineRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -42,7 +43,7 @@ class CloneMachineTool
string $name, string $name,
string $siteId, string $siteId,
string $reference = '', string $reference = '',
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$source = $this->machineRepository->find($machineId); $source = $this->machineRepository->find($machineId);

View File

@@ -10,6 +10,7 @@ use App\Repository\ConstructeurRepository;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -36,7 +37,7 @@ class CreateMachineTool
string $reference = '', string $reference = '',
string $prix = '', string $prix = '',
array $constructeurIds = [], array $constructeurIds = [],
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$site = $this->sites->find($siteId); $site = $this->sites->find($siteId);

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\MachineRepository; use App\Repository\MachineRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteMachineTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $machineId): array public function __invoke(string $machineId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Machine;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\MachineRepository; use App\Repository\MachineRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_machine', name: 'get_machine',
@@ -20,7 +21,7 @@ class GetMachineTool
private readonly MachineRepository $machines, private readonly MachineRepository $machines,
) {} ) {}
public function __invoke(string $machineId): array public function __invoke(string $machineId): CallToolResult
{ {
$machine = $this->machines->find($machineId); $machine = $this->machines->find($machineId);

View File

@@ -10,6 +10,7 @@ use App\Repository\MachinePieceLinkRepository;
use App\Repository\MachineProductLinkRepository; use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository; use App\Repository\MachineRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_machine_links', name: 'list_machine_links',
@@ -26,7 +27,7 @@ class ListMachineLinksTool
private readonly MachineProductLinkRepository $productLinks, private readonly MachineProductLinkRepository $productLinks,
) {} ) {}
public function __invoke(string $machineId): array public function __invoke(string $machineId): CallToolResult
{ {
$machine = $this->machines->find($machineId); $machine = $this->machines->find($machineId);
if (null === $machine) { if (null === $machine) {

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Machine;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\MachineRepository; use App\Repository\MachineRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_machines', name: 'list_machines',
@@ -20,7 +21,7 @@ class ListMachinesTool
private readonly MachineRepository $machines, private readonly MachineRepository $machines,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $search = ''): array public function __invoke(int $page = 1, int $limit = 30, string $search = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -21,6 +21,7 @@ use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository; use App\Repository\MachineRepository;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_machine_structure', name: 'get_machine_structure',
@@ -37,7 +38,7 @@ class MachineStructureTool
private readonly MachineProductLinkRepository $machineProductLinkRepository, private readonly MachineProductLinkRepository $machineProductLinkRepository,
) {} ) {}
public function __invoke(string $machineId): array public function __invoke(string $machineId): CallToolResult
{ {
$machine = $this->machineRepository->find($machineId); $machine = $this->machineRepository->find($machineId);

View File

@@ -10,6 +10,7 @@ use App\Repository\MachinePieceLinkRepository;
use App\Repository\MachineProductLinkRepository; use App\Repository\MachineProductLinkRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -28,7 +29,7 @@ class RemoveMachineLinkTool
private readonly MachineProductLinkRepository $productLinks, private readonly MachineProductLinkRepository $productLinks,
) {} ) {}
public function __invoke(string $linkId, string $linkType): array public function __invoke(string $linkId, string $linkType): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -11,6 +11,7 @@ use App\Repository\MachineComponentLinkRepository;
use App\Repository\MachinePieceLinkRepository; use App\Repository\MachinePieceLinkRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -35,7 +36,7 @@ class UpdateMachineLinkTool
?string $referenceOverride = null, ?string $referenceOverride = null,
?string $prixOverride = null, ?string $prixOverride = null,
?int $quantity = null, ?int $quantity = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
switch ($linkType) { switch ($linkType) {

View File

@@ -10,6 +10,7 @@ use App\Repository\MachineRepository;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -38,7 +39,7 @@ class UpdateMachineTool
?string $prix = null, ?string $prix = null,
?string $siteId = null, ?string $siteId = null,
?array $constructeurIds = null, ?array $constructeurIds = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$machine = $this->machines->find($machineId); $machine = $this->machines->find($machineId);

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Mcp\Tool; namespace App\Mcp\Tool;
use Mcp\Schema\Content\TextContent; use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Result\CallToolResult;
use RuntimeException; use RuntimeException;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
@@ -17,12 +18,11 @@ trait McpToolHelper
} }
} }
/** private function jsonResponse(array $data): CallToolResult
* @return array{TextContent}
*/
private function jsonResponse(array $data): array
{ {
return [new TextContent(text: json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE))]; return new CallToolResult(
content: [new TextContent(text: json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE))],
);
} }
private function mcpError(string $category, string $message): never private function mcpError(string $category, string $message): never
@@ -42,10 +42,7 @@ trait McpToolHelper
return ['page' => $page, 'limit' => $limit, 'offset' => $offset]; return ['page' => $page, 'limit' => $limit, 'offset' => $offset];
} }
/** private function paginatedResponse(array $items, int $total, int $page, int $limit): CallToolResult
* @return array{TextContent}
*/
private function paginatedResponse(array $items, int $total, int $page, int $limit): array
{ {
return $this->jsonResponse([ return $this->jsonResponse([
'items' => $items, 'items' => $items,

View File

@@ -9,6 +9,7 @@ use App\Enum\ModelCategory;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class CreateModelTypeTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $name, string $category, string $code = ''): array public function __invoke(string $name, string $category, string $code = ''): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteModelTypeTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $modelTypeId): array public function __invoke(string $modelTypeId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\ModelType;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_model_type', name: 'get_model_type',
@@ -20,7 +21,7 @@ class GetModelTypeTool
private readonly ModelTypeRepository $modelTypes, private readonly ModelTypeRepository $modelTypes,
) {} ) {}
public function __invoke(string $modelTypeId): array public function __invoke(string $modelTypeId): CallToolResult
{ {
$mt = $this->modelTypes->find($modelTypeId); $mt = $this->modelTypes->find($modelTypeId);

View File

@@ -8,6 +8,7 @@ use App\Enum\ModelCategory;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_model_types', name: 'list_model_types',
@@ -21,7 +22,7 @@ class ListModelTypesTool
private readonly ModelTypeRepository $modelTypes, private readonly ModelTypeRepository $modelTypes,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $category = ''): array public function __invoke(int $page = 1, int $limit = 30, string $category = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -10,6 +10,7 @@ use App\Repository\ModelTypeRepository;
use App\Service\ModelTypeSyncService; use App\Service\ModelTypeSyncService;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -33,7 +34,7 @@ class SyncModelTypeTool
?array $structure = null, ?array $structure = null,
bool $confirmDeletions = false, bool $confirmDeletions = false,
bool $confirmTypeChanges = false, bool $confirmTypeChanges = false,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
if (!in_array($action, ['preview', 'sync'], true)) { if (!in_array($action, ['preview', 'sync'], true)) {

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class UpdateModelTypeTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $modelTypeId, ?string $name = null, ?string $code = null): array public function __invoke(string $modelTypeId, ?string $name = null, ?string $code = null): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -10,6 +10,7 @@ use App\Repository\ConstructeurRepository;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -37,7 +38,7 @@ class CreatePieceTool
string $prix = '', string $prix = '',
string $modelTypeId = '', string $modelTypeId = '',
array $constructeurIds = [], array $constructeurIds = [],
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$piece = new Piece(); $piece = new Piece();

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\PieceRepository; use App\Repository\PieceRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeletePieceTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $pieceId): array public function __invoke(string $pieceId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Piece;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\PieceRepository; use App\Repository\PieceRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_piece', name: 'get_piece',
@@ -20,7 +21,7 @@ class GetPieceTool
private readonly PieceRepository $pieces, private readonly PieceRepository $pieces,
) {} ) {}
public function __invoke(string $pieceId): array public function __invoke(string $pieceId): CallToolResult
{ {
$piece = $this->pieces->find($pieceId); $piece = $this->pieces->find($pieceId);

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Piece;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\PieceRepository; use App\Repository\PieceRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_pieces', name: 'list_pieces',
@@ -20,7 +21,7 @@ class ListPiecesTool
private readonly PieceRepository $pieces, private readonly PieceRepository $pieces,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $search = ''): array public function __invoke(int $page = 1, int $limit = 30, string $search = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -10,6 +10,7 @@ use App\Repository\ModelTypeRepository;
use App\Repository\PieceRepository; use App\Repository\PieceRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -39,7 +40,7 @@ class UpdatePieceTool
?string $prix = null, ?string $prix = null,
?string $modelTypeId = null, ?string $modelTypeId = null,
?array $constructeurIds = null, ?array $constructeurIds = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$piece = $this->pieces->find($pieceId); $piece = $this->pieces->find($pieceId);

View File

@@ -10,6 +10,7 @@ use App\Repository\ConstructeurRepository;
use App\Repository\ModelTypeRepository; use App\Repository\ModelTypeRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -36,7 +37,7 @@ class CreateProductTool
string $supplierPrice = '', string $supplierPrice = '',
string $modelTypeId = '', string $modelTypeId = '',
array $constructeurIds = [], array $constructeurIds = [],
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$product = new Product(); $product = new Product();

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\ProductRepository; use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteProductTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $productId): array public function __invoke(string $productId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Product;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ProductRepository; use App\Repository\ProductRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_product', name: 'get_product',
@@ -20,7 +21,7 @@ class GetProductTool
private readonly ProductRepository $products, private readonly ProductRepository $products,
) {} ) {}
public function __invoke(string $productId): array public function __invoke(string $productId): CallToolResult
{ {
$product = $this->products->find($productId); $product = $this->products->find($productId);

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Product;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\ProductRepository; use App\Repository\ProductRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_products', name: 'list_products',
@@ -20,7 +21,7 @@ class ListProductsTool
private readonly ProductRepository $products, private readonly ProductRepository $products,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $search = ''): array public function __invoke(int $page = 1, int $limit = 30, string $search = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -10,6 +10,7 @@ use App\Repository\ModelTypeRepository;
use App\Repository\ProductRepository; use App\Repository\ProductRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -38,7 +39,7 @@ class UpdateProductTool
?string $supplierPrice = null, ?string $supplierPrice = null,
?string $modelTypeId = null, ?string $modelTypeId = null,
?array $constructeurIds = null, ?array $constructeurIds = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$product = $this->products->find($productId); $product = $this->products->find($productId);

View File

@@ -11,6 +11,7 @@ use App\Repository\PieceRepository;
use App\Repository\ProductRepository; use App\Repository\ProductRepository;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'search_inventory', name: 'search_inventory',
@@ -31,7 +32,7 @@ class SearchInventoryTool
private readonly ConstructeurRepository $constructeurs, private readonly ConstructeurRepository $constructeurs,
) {} ) {}
public function __invoke(string $query, string $types = '', int $limit = 20): array public function __invoke(string $query, string $types = '', int $limit = 20): CallToolResult
{ {
$query = trim($query); $query = trim($query);
if ('' === $query) { if ('' === $query) {

View File

@@ -8,6 +8,7 @@ use App\Entity\Site;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -31,7 +32,7 @@ class CreateSiteTool
string $contactPostalCode = '', string $contactPostalCode = '',
string $contactCity = '', string $contactCity = '',
string $color = '', string $color = '',
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$site = new Site(); $site = new Site();

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -24,7 +25,7 @@ class DeleteSiteTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(string $siteId): array public function __invoke(string $siteId): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Site;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'get_site', name: 'get_site',
@@ -20,7 +21,7 @@ class GetSiteTool
private readonly SiteRepository $sites, private readonly SiteRepository $sites,
) {} ) {}
public function __invoke(string $siteId): array public function __invoke(string $siteId): CallToolResult
{ {
$site = $this->sites->find($siteId); $site = $this->sites->find($siteId);

View File

@@ -7,6 +7,7 @@ namespace App\Mcp\Tool\Site;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_sites', name: 'list_sites',
@@ -20,7 +21,7 @@ class ListSitesTool
private readonly SiteRepository $sites, private readonly SiteRepository $sites,
) {} ) {}
public function __invoke(int $page = 1, int $limit = 30, string $search = ''): array public function __invoke(int $page = 1, int $limit = 30, string $search = ''): CallToolResult
{ {
$p = $this->paginationParams($page, $limit); $p = $this->paginationParams($page, $limit);

View File

@@ -8,6 +8,7 @@ use App\Mcp\Tool\McpToolHelper;
use App\Repository\SiteRepository; use App\Repository\SiteRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -33,7 +34,7 @@ class UpdateSiteTool
?string $contactPostalCode = null, ?string $contactPostalCode = null,
?string $contactCity = null, ?string $contactCity = null,
?string $color = null, ?string $color = null,
): array { ): CallToolResult {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');
$site = $this->sites->find($siteId); $site = $this->sites->find($siteId);

View File

@@ -11,6 +11,7 @@ use App\Entity\PieceProductSlot;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
#[McpTool( #[McpTool(
name: 'list_slots', name: 'list_slots',
@@ -24,7 +25,7 @@ class ListSlotsTool
private readonly EntityManagerInterface $em, private readonly EntityManagerInterface $em,
) {} ) {}
public function __invoke(string $entityType, string $entityId): array public function __invoke(string $entityType, string $entityId): CallToolResult
{ {
if ('composant' === $entityType) { if ('composant' === $entityType) {
return $this->listComposantSlots($entityId); return $this->listComposantSlots($entityId);
@@ -37,7 +38,7 @@ class ListSlotsTool
$this->mcpError('validation', "entityType must be 'composant' or 'piece', got '{$entityType}'."); $this->mcpError('validation', "entityType must be 'composant' or 'piece', got '{$entityType}'.");
} }
private function listComposantSlots(string $composantId): array private function listComposantSlots(string $composantId): CallToolResult
{ {
$pieceSlots = $this->em->createQueryBuilder() $pieceSlots = $this->em->createQueryBuilder()
->select( ->select(
@@ -108,7 +109,7 @@ class ListSlotsTool
]); ]);
} }
private function listPieceSlots(string $pieceId): array private function listPieceSlots(string $pieceId): CallToolResult
{ {
$slots = $this->em->createQueryBuilder() $slots = $this->em->createQueryBuilder()
->select( ->select(

View File

@@ -14,6 +14,7 @@ use App\Entity\Product;
use App\Mcp\Tool\McpToolHelper; use App\Mcp\Tool\McpToolHelper;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Mcp\Capability\Attribute\McpTool; use Mcp\Capability\Attribute\McpTool;
use Mcp\Schema\Result\CallToolResult;
use Symfony\Bundle\SecurityBundle\Security; use Symfony\Bundle\SecurityBundle\Security;
#[McpTool( #[McpTool(
@@ -29,7 +30,7 @@ class UpdateSlotsTool
private readonly Security $security, private readonly Security $security,
) {} ) {}
public function __invoke(array $slots): array public function __invoke(array $slots): CallToolResult
{ {
$this->requireRole($this->security, 'ROLE_GESTIONNAIRE'); $this->requireRole($this->security, 'ROLE_GESTIONNAIRE');

View File

@@ -108,6 +108,7 @@ abstract class AbstractApiTestCase extends ApiTestCase
$response = $client->request('POST', '/_mcp', [ $response = $client->request('POST', '/_mcp', [
'headers' => [ 'headers' => [
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'Accept' => 'application/json, text/event-stream',
'X-Profile-Id' => $profileId, 'X-Profile-Id' => $profileId,
'X-Profile-Password' => $password, 'X-Profile-Password' => $password,
], ],
@@ -128,6 +129,7 @@ abstract class AbstractApiTestCase extends ApiTestCase
$client->request('POST', '/_mcp', [ $client->request('POST', '/_mcp', [
'headers' => [ 'headers' => [
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'Accept' => 'application/json, text/event-stream',
'X-Profile-Id' => $profileId, 'X-Profile-Id' => $profileId,
'X-Profile-Password' => $password, 'X-Profile-Password' => $password,
'Mcp-Session-Id' => $sessionId, 'Mcp-Session-Id' => $sessionId,
@@ -149,6 +151,7 @@ abstract class AbstractApiTestCase extends ApiTestCase
$response = $session['client']->request('POST', '/_mcp', [ $response = $session['client']->request('POST', '/_mcp', [
'headers' => [ 'headers' => [
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'Accept' => 'application/json, text/event-stream',
'X-Profile-Id' => $session['profileId'], 'X-Profile-Id' => $session['profileId'],
'X-Profile-Password' => $session['password'], 'X-Profile-Password' => $session['password'],
'Mcp-Session-Id' => $session['sessionId'], 'Mcp-Session-Id' => $session['sessionId'],
@@ -164,7 +167,24 @@ abstract class AbstractApiTestCase extends ApiTestCase
]), ]),
]); ]);
$data = $response->toArray(false); $raw = $response->getContent(false);
$data = json_decode($raw, true);
if (null === $data) {
// SSE format: parse "data: {...}" lines
foreach (explode("\n", $raw) as $line) {
if (str_starts_with($line, 'data: ')) {
$parsed = json_decode(substr($line, 6), true);
if ($parsed && (isset($parsed['result']) || isset($parsed['error']))) {
$data = $parsed;
break;
}
}
}
}
$data ??= [];
if (isset($data['result']['content'][0]['text'])) { if (isset($data['result']['content'][0]['text'])) {
$data['_parsed'] = json_decode($data['result']['content'][0]['text'], true); $data['_parsed'] = json_decode($data['result']['content'][0]['text'], true);