feat(mcp) : add MCP resources, documentation, and .mcp.json config
- 3 MCP resources: schema, roles, stats - docs/mcp/README.md with full user guide (config, tools catalogue, workflows) - .mcp.json for Claude Code stdio transport - Design spec and implementation plan Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
35
src/Mcp/Resource/RolesResource.php
Normal file
35
src/Mcp/Resource/RolesResource.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Resource;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
use Mcp\Schema\Content\TextContent;
|
||||
|
||||
#[McpResource(
|
||||
uri: 'inventory://roles',
|
||||
name: 'Roles & Permissions',
|
||||
description: 'Role hierarchy and permissions for MCP tools.',
|
||||
mimeType: 'application/json'
|
||||
)]
|
||||
class RolesResource
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
$roles = [
|
||||
'hierarchy' => [
|
||||
'ROLE_ADMIN' => 'Inherits ROLE_GESTIONNAIRE. Can manage profiles.',
|
||||
'ROLE_GESTIONNAIRE' => 'Inherits ROLE_VIEWER. Can create, update, delete all entities.',
|
||||
'ROLE_VIEWER' => 'Inherits ROLE_USER. Can read all entities, create comments, search.',
|
||||
'ROLE_USER' => 'Base role. Authenticated but minimal access.',
|
||||
],
|
||||
'tool_permissions' => [
|
||||
'ROLE_VIEWER' => 'list_*, get_*, search_inventory, get_dashboard_stats, get_entity_history, get_activity_log, list_comments, create_comment, get_unresolved_comments_count, list_custom_field_values, list_documents, list_slots',
|
||||
'ROLE_GESTIONNAIRE' => 'All VIEWER tools + create_*, update_*, delete_*, clone_machine, update_slots, add_machine_links, remove_machine_link, resolve_comment, upsert_custom_field_values, sync_model_type',
|
||||
],
|
||||
];
|
||||
|
||||
return [new TextContent(text: json_encode($roles, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))];
|
||||
}
|
||||
}
|
||||
53
src/Mcp/Resource/SchemaResource.php
Normal file
53
src/Mcp/Resource/SchemaResource.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Resource;
|
||||
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
use Mcp\Schema\Content\TextContent;
|
||||
|
||||
#[McpResource(
|
||||
uri: 'inventory://schema/entities',
|
||||
name: 'Entity Schema',
|
||||
description: 'Complete schema of all inventory entities with their fields, types, and relationships.',
|
||||
mimeType: 'application/json'
|
||||
)]
|
||||
class SchemaResource
|
||||
{
|
||||
public function __invoke(): array
|
||||
{
|
||||
$schema = [
|
||||
'Machine' => [
|
||||
'fields' => ['id (string)', 'name (string, unique)', 'reference (string?)', 'prix (string?)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['site (Site, required)', 'constructeurs (Constructeur[])', 'componentLinks (MachineComponentLink[])', 'pieceLinks (MachinePieceLink[])', 'productLinks (MachineProductLink[])', 'customFields (CustomField[])', 'customFieldValues (CustomFieldValue[])'],
|
||||
],
|
||||
'Composant' => [
|
||||
'fields' => ['id (string)', 'name (string, unique)', 'reference (string?)', 'description (text?)', 'prix (string?)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['typeComposant (ModelType?)', 'constructeurs (Constructeur[])', 'pieceSlots (ComposantPieceSlot[])', 'productSlots (ComposantProductSlot[])', 'subcomponentSlots (ComposantSubcomponentSlot[])', 'customFieldValues (CustomFieldValue[])'],
|
||||
],
|
||||
'Piece' => [
|
||||
'fields' => ['id (string)', 'name (string)', 'reference (string?, unique)', 'description (text?)', 'prix (string?)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['typePiece (ModelType?)', 'product (Product?)', 'constructeurs (Constructeur[])', 'productSlots (PieceProductSlot[])', 'customFieldValues (CustomFieldValue[])'],
|
||||
],
|
||||
'Product' => [
|
||||
'fields' => ['id (string)', 'name (string, unique)', 'reference (string?)', 'supplierPrice (string?)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['typeProduct (ModelType?)', 'constructeurs (Constructeur[])'],
|
||||
],
|
||||
'Site' => [
|
||||
'fields' => ['id (string)', 'name (string)', 'contactName (string)', 'contactPhone (string)', 'contactAddress (string)', 'contactPostalCode (string)', 'contactCity (string)', 'color (string)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['machines (Machine[])'],
|
||||
],
|
||||
'Constructeur' => [
|
||||
'fields' => ['id (string)', 'name (string, unique)', 'email (string?)', 'phone (string?)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['machines (Machine[])', 'composants (Composant[])', 'pieces (Piece[])', 'products (Product[])'],
|
||||
],
|
||||
'ModelType' => [
|
||||
'fields' => ['id (string)', 'name (string)', 'category (machine|composant|piece|product)', 'code (string?)', 'createdAt', 'updatedAt'],
|
||||
'relationships' => ['skeletonPieceRequirements[]', 'skeletonProductRequirements[]', 'skeletonSubcomponentRequirements[]'],
|
||||
],
|
||||
];
|
||||
|
||||
return [new TextContent(text: json_encode($schema, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))];
|
||||
}
|
||||
}
|
||||
48
src/Mcp/Resource/StatsResource.php
Normal file
48
src/Mcp/Resource/StatsResource.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mcp\Resource;
|
||||
|
||||
use App\Repository\ComposantRepository;
|
||||
use App\Repository\MachineRepository;
|
||||
use App\Repository\PieceRepository;
|
||||
use App\Repository\ProductRepository;
|
||||
use App\Repository\SiteRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mcp\Capability\Attribute\McpResource;
|
||||
use Mcp\Schema\Content\TextContent;
|
||||
|
||||
#[McpResource(
|
||||
uri: 'inventory://stats',
|
||||
name: 'Inventory Statistics',
|
||||
description: 'Global counters: machines, pieces, composants, products, sites, unresolved comments.',
|
||||
mimeType: 'application/json'
|
||||
)]
|
||||
class StatsResource
|
||||
{
|
||||
public function __construct(
|
||||
private readonly MachineRepository $machines,
|
||||
private readonly PieceRepository $pieces,
|
||||
private readonly ComposantRepository $composants,
|
||||
private readonly ProductRepository $products,
|
||||
private readonly SiteRepository $sites,
|
||||
private readonly EntityManagerInterface $em,
|
||||
) {}
|
||||
|
||||
public function __invoke(): array
|
||||
{
|
||||
$unresolvedComments = (int) $this->em->createQuery(
|
||||
"SELECT COUNT(c.id) FROM App\\Entity\\Comment c WHERE c.status = 'open'"
|
||||
)->getSingleScalarResult();
|
||||
|
||||
return [new TextContent(text: json_encode([
|
||||
'machines' => $this->machines->count([]),
|
||||
'pieces' => $this->pieces->count([]),
|
||||
'composants' => $this->composants->count([]),
|
||||
'products' => $this->products->count([]),
|
||||
'sites' => $this->sites->count([]),
|
||||
'unresolvedComments' => $unresolvedComments,
|
||||
], JSON_THROW_ON_ERROR))];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user