Files
Inventory/docs/superpowers/specs/2026-03-16-mcp-server-design.md
Matthieu f965affc94 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>
2026-03-16 15:49:00 +01:00

30 KiB

MCP Server — Inventory Project — Design Spec

Date : 2026-03-16 Version projet : 1.9.1 Statut : Draft (post-review v2)


1. Objectif

Exposer l'intégralité de l'API Inventory (machines, pièces, composants, produits, sites, constructeurs, custom fields, documents, commentaires, audit) via un serveur MCP (Model Context Protocol) intégré directement dans l'application Symfony.

Le serveur doit être compatible avec tous les clients MCP majeurs : Claude Code, Claude Desktop, ChatGPT Desktop, Codex, et tout client supportant le protocole MCP.

2. Contraintes

Contrainte Détail
Réseau Machine hébergée sur un réseau fermé d'entreprise. Les clients distants (Claude Desktop, ChatGPT, Codex) accèdent via un tunnel chiffré (Cloudflare/WireGuard/SSH)
Auth Pass-through : chaque client fournit ses propres credentials (profileId + password). Le serveur MCP charge le profil correspondant et applique ses rôles. Les actions sont traçables par utilisateur dans l'audit log
Transport Dual : stdio pour usage local (Claude Code sur la même machine) + HTTP Streamable/SSE pour clients distants via tunnel
Stack PHP / Symfony 8.0 — le serveur MCP vit dans l'application existante, pas de service séparé
Scope Lecture + écriture complète — les outils couvrent tout le CRUD + les opérations métier

3. Stack technique

Composant Choix
SDK MCP symfony/mcp-bundle v0.6.0 + mcp/sdk ^0.4 (officiel Symfony + PHP Foundation + Anthropic)
Transport stdio bin/console mcp:server (dans le container Docker)
Transport HTTP Endpoint /_mcp sur le même port que l'API (8081)
Auth HTTP Custom Symfony Authenticator (McpHeaderAuthenticator) intégré au firewall Symfony
Auth stdio Token synthétique chargé depuis $_ENV au boot
Rate limiting symfony/rate-limiter sur les tentatives d'auth échouées
Accès données Repositories Doctrine directs (pas de hop HTTP interne)

Note : Le bundle est expérimental et non couvert par la BC Promise de Symfony. L'implémentation inclut un spike/PoC initial (étape 1 du plan) pour valider la compatibilité de l'API réelle du bundle avec ce design.

4. Architecture

┌─────────────────────────────────────────────────────┐
│  Docker Compose (réseau fermé entreprise)            │
│                                                     │
│  ┌─────────────────────────────────────────────┐    │
│  │  php-inventory-apache (Symfony 8)            │    │
│  │                                              │    │
│  │  /api/*          ← API REST existante        │    │
│  │  /_mcp           ← Endpoint MCP HTTP (SSE)   │    │
│  │  bin/console mcp:server  ← Transport stdio   │    │
│  │                                              │    │
│  │  Firewall Symfony :                          │    │
│  │    ^/api  → SessionProfileAuthenticator      │    │
│  │    ^/_mcp → McpHeaderAuthenticator           │    │
│  │                                              │    │
│  │  src/Mcp/Tool/       ← Tools MCP             │    │
│  │  src/Mcp/Resource/   ← Resources MCP         │    │
│  │  src/Mcp/Security/   ← Authenticator + Guard │    │
│  └──────────┬───────────────────────────────────┘    │
│             │ réseau Docker interne                   │
│  ┌──────────▼──────────┐                             │
│  │  PostgreSQL 16       │                             │
│  └─────────────────────┘                             │
└──────────────────┬──────────────────────────────────┘
                   │ tunnel (chiffré)
    ┌──────────────▼──────────────────┐
    │  Postes utilisateurs            │
    │  - Claude Desktop → HTTP/SSE    │
    │  - ChatGPT Desktop → HTTP/SSE   │
    │  - Codex → HTTP/SSE             │
    │  - Claude Code local → stdio    │
    └─────────────────────────────────┘

Le serveur MCP accède directement aux repositories Doctrine et aux services Symfony existants. Pas de double sérialisation — les tools appellent les mêmes repositories/services que les controllers REST.

5. Authentification pass-through

5.1 Firewall Symfony — intégration sécurité

Un firewall dédié pour /_mcp avec un authenticator custom. Cela garantit que $security->getUser() retourne le bon Profile, que la hiérarchie des rôles fonctionne via is_granted(), et que l'audit log trace le bon acteur.

# config/packages/security.yaml (ajout)
security:
    firewalls:
        mcp:
            pattern: ^/_mcp
            stateless: true
            custom_authenticators:
                - App\Mcp\Security\McpHeaderAuthenticator

Le McpHeaderAuthenticator implémente AuthenticatorInterface :

  1. Extrait X-Profile-Id et X-Profile-Password des headers
  2. Charge le profil via ProfileRepository
  3. Vérifie le password hash via UserPasswordHasherInterface
  4. Retourne un Passport avec le Profile comme User
  5. Symfony gère le reste (token, rôles, hiérarchie)

Cela permet à AbstractAuditSubscriber.resolveActorProfileId() de résoudre l'acteur via $security->getUser() sans aucune modification du code existant.

5.2 Transport stdio — token synthétique

Pour le transport stdio (pas de requête HTTP), un EventSubscriber sur console.command (quand la commande est mcp:server) :

  1. Lit MCP_PROFILE_ID et MCP_PROFILE_PASSWORD depuis $_ENV
  2. Valide les credentials
  3. Injecte un UsernamePasswordToken synthétique dans le TokenStorage avec le Profile

5.3 Rate limiting — protection brute-force

# config/packages/rate_limiter.yaml
framework:
    rate_limiter:
        mcp_auth:
            policy: sliding_window
            limit: 5
            interval: '1 minute'

Le McpHeaderAuthenticator consomme le rate limiter sur chaque tentative échouée (clé = IP). Après 5 échecs en 1 minute, toute tentative est rejetée avec une erreur MCP 429 Too Many Requests.

5.4 Vérification des rôles

Chaque tool déclare un rôle minimum. L'authenticator Symfony gère la hiérarchie :

Rôle Droits MCP
ROLE_VIEWER Tous les tools de lecture (list, get, search, history)
ROLE_GESTIONNAIRE Lecture + écriture (create, update, delete, slots, clone)
ROLE_ADMIN Tout + gestion profils

Les tools utilisent $this->security->isGranted('ROLE_XXX') pour vérifier, bénéficiant de la hiérarchie Symfony standard.

6. Catalogue des Tools MCP

6.1 Tools de haut niveau (métier)

Tool Description Paramètres principaux Rôle min
search_inventory Recherche globale dans toutes les entités (machines, pièces, composants, produits, sites, constructeurs) query: string, types?: string[], limit?: int VIEWER
get_machine_structure Hiérarchie complète d'une machine : composants, pièces, produits, custom fields, slots machineId: string VIEWER
clone_machine Clone une machine avec sa structure complète machineId: string, name: string, siteId: string, reference?: string GESTIONNAIRE
get_entity_history Historique d'audit d'une entité entityType: string, entityId: string VIEWER
get_activity_log Journal d'activité global page?: int, limit?: int, entityType?: string, action?: string VIEWER
get_dashboard_stats Compteurs globaux (machines, pièces, composants, produits, commentaires ouverts) aucun VIEWER
sync_model_type Preview ou exécution de la synchronisation skeleton d'un ModelType modelTypeId: string, action: "preview"|"sync", structure?: object GESTIONNAIRE

6.2 Tools CRUD — Machines

Tool Description Rôle min
list_machines Lister les machines avec filtres (nom, référence, site) et pagination VIEWER
get_machine Détail d'une machine par ID VIEWER
create_machine Créer une machine (nom, référence, siteId, constructeurs) GESTIONNAIRE
update_machine Mise à jour partielle d'une machine GESTIONNAIRE
delete_machine Supprimer une machine GESTIONNAIRE

6.3 Tools CRUD — Composants

Tool Description Rôle min
list_composants Lister les composants avec filtres et pagination VIEWER
get_composant Détail d'un composant par ID (incluant ses slots) VIEWER
create_composant Créer un composant (nom, référence, modelTypeId, constructeurs). Retourne l'ID + les slots vides auto-générés GESTIONNAIRE
update_composant Mise à jour partielle GESTIONNAIRE
delete_composant Supprimer un composant GESTIONNAIRE

6.4 Tools CRUD — Pièces

Tool Description Rôle min
list_pieces Lister les pièces avec filtres et pagination VIEWER
get_piece Détail d'une pièce par ID (incluant ses product-slots) VIEWER
create_piece Créer une pièce (nom, référence, modelTypeId, constructeurs). Retourne l'ID + product-slots auto-générés GESTIONNAIRE
update_piece Mise à jour partielle GESTIONNAIRE
delete_piece Supprimer une pièce GESTIONNAIRE

6.5 Tools CRUD — Produits

Tool Description Rôle min
list_products Lister les produits avec filtres et pagination VIEWER
get_product Détail d'un produit par ID VIEWER
create_product Créer un produit (nom, référence, modelTypeId, prix (string), constructeurs) GESTIONNAIRE
update_product Mise à jour partielle GESTIONNAIRE
delete_product Supprimer un produit GESTIONNAIRE

6.6 Tools CRUD — Sites

Tool Description Rôle min
list_sites Lister les sites VIEWER
get_site Détail d'un site par ID VIEWER
create_site Créer un site GESTIONNAIRE
update_site Mise à jour partielle GESTIONNAIRE
delete_site Supprimer un site GESTIONNAIRE

6.7 Tools CRUD — Constructeurs

Tool Description Rôle min
list_constructeurs Lister les constructeurs/fournisseurs VIEWER
get_constructeur Détail d'un constructeur par ID VIEWER
create_constructeur Créer un constructeur GESTIONNAIRE
update_constructeur Mise à jour partielle GESTIONNAIRE
delete_constructeur Supprimer un constructeur GESTIONNAIRE

6.8 Tools — Commentaires (splittés)

Tool Description Rôle min
list_comments Lister les commentaires d'une entité VIEWER
create_comment Créer un commentaire sur une entité VIEWER
resolve_comment Marquer un commentaire comme résolu GESTIONNAIRE
get_unresolved_comments_count Nombre de commentaires non résolus VIEWER

6.9 Tools — Custom Fields (splittés)

Tool Description Rôle min
list_custom_field_values Lister les custom field values d'une entité VIEWER
upsert_custom_field_values Créer ou mettre à jour des custom field values GESTIONNAIRE
delete_custom_field_value Supprimer une custom field value GESTIONNAIRE

6.10 Tools — Documents (splittés)

Tool Description Rôle min
list_documents Lister les documents d'une entité VIEWER
delete_document Supprimer un document GESTIONNAIRE

Limitation connue : L'upload de documents n'est pas supporté via MCP. Le protocole MCP échange du JSON — l'upload de fichiers binaires (multipart/form-data) n'est pas compatible. Les uploads doivent se faire via l'API REST /api/documents (POST multipart). Cette limitation pourra être réévaluée si le protocole MCP ajoute un support binaire.

Tool Description Rôle min
list_machine_links Lister les liens composant/pièce/produit d'une machine VIEWER
add_machine_links Ajouter des liens machine↔composant/pièce/produit GESTIONNAIRE
update_machine_link Modifier un lien (quantité, overrides) GESTIONNAIRE
remove_machine_link Supprimer un lien GESTIONNAIRE

6.12 Tools — Slots

Tool Description Rôle min
list_slots Lister les slots d'un composant ou pièce avec état (rempli/vide, requirement). Paramètre entityType: "composant"|"piece" + entityId VIEWER
update_slots Remplir un ou plusieurs slots. Paramètre slots: [{slotId, selectedPieceId?|selectedProductId?|selectedComposantId?}] GESTIONNAIRE

Note : Un seul tool list_slots et un seul update_slots — ils acceptent un paramètre entityType pour dispatcher vers composant ou pièce. Un seul fichier d'implémentation par tool.

6.13 Tools — ModelTypes

Tool Description Rôle min
list_model_types Lister les ModelTypes par catégorie avec skeleton requirements VIEWER
get_model_type Détail complet d'un ModelType (requirements + custom fields) VIEWER
create_model_type Créer un ModelType GESTIONNAIRE
update_model_type Modifier un ModelType GESTIONNAIRE
delete_model_type Supprimer un ModelType GESTIONNAIRE

Total : ~55 tools (splittés pour des schémas JSON non-ambigus, meilleure compatibilité LLM)

Note : Les tools d'administration des profils (list_profiles, create_profile, etc.) ne sont pas inclus — la gestion des profils reste exclusivement via l'API REST /api/admin/profiles (ROLE_ADMIN). Cela évite d'exposer la gestion des comptes/mots de passe via MCP.

7. Resources MCP

URI Description Contenu
inventory://schema/entities Schéma de toutes les entités Nom, champs (nom, type, nullable, description) pour chaque entité
inventory://model-types/{category} ModelTypes par catégorie Liste des ModelTypes avec leurs skeleton requirements et custom fields
inventory://roles Hiérarchie des rôles Rôles et permissions associées pour guider le LLM
inventory://stats Statistiques globales Compteurs de chaque entité, commentaires ouverts

8. Workflows de création guidés

8.1 Créer un Composant complet

1. list_model_types(category: "composant")
   → Choisir le type de composant

2. get_model_type(modelTypeId)
   → Voir les skeleton requirements : pièces, produits, sous-composants attendus
   → Voir les custom fields de chaque requirement

3. create_composant(name, reference, modelTypeId, constructeurs)
   → Reçoit: { id, slots: [{slotId, type, requirementName}, ...] }

4. search_inventory(query: "Roulement", types: ["piece"])
   → Trouver les pièces candidates pour chaque slot

5. update_slots([{slotId, selectedPieceId}, {slotId, selectedProductId}, ...])
   → Remplir les slots

6. upsert_custom_field_values(entityType: "composant", entityId,
     fields: [{name: "Tension", value: "220V"}, ...])
   → Remplir les custom fields

8.2 Créer une Pièce complète

1. list_model_types(category: "piece")
2. get_model_type(modelTypeId)
3. create_piece(name, reference, modelTypeId, constructeurs)
   → Reçoit: { id, productSlots: [{slotId, requirementName}, ...] }
4. search_inventory(query: "...", types: ["product"])
5. update_slots([{slotId, selectedProductId}, ...])
6. upsert_custom_field_values(...)

8.3 Créer un Produit

1. list_model_types(category: "product")
2. create_product(name, reference, modelTypeId, prix, constructeurs)
3. upsert_custom_field_values(...)

8.4 Créer une Machine complète (de bas en haut)

1. Créer les produits nécessaires (§8.3)
2. Créer les pièces avec les produits dans les slots (§8.2)
3. Créer les composants avec les pièces dans les slots (§8.1)
4. list_sites → choisir le site
5. create_machine(name, reference, siteId, constructeurs)
6. add_machine_links(machineId, links: [
     {type: "composant", entityId, quantity},
     {type: "piece", entityId, quantity},
     {type: "product", entityId}
   ])
7. upsert_custom_field_values(entityType: "machine", machineId, ...)

9. Pagination

Toutes les tools list_* utilisent un contrat de pagination uniforme :

Paramètres d'entrée

Paramètre Type Default Description
page int 1 Numéro de page (1-indexed)
limit int 30 Nombre d'items par page (max 100)

Format de réponse

{
  "items": [...],
  "total": 142,
  "page": 1,
  "limit": 30,
  "pageCount": 5
}

10. Format des erreurs

Toutes les erreurs MCP suivent un format uniforme via isError: true dans la réponse tool :

{
  "isError": true,
  "content": [{"type": "text", "text": "Permission denied: ROLE_GESTIONNAIRE required for create_machine"}]
}

Catégories d'erreurs

Code Description Exemple
auth_error Credentials invalides ou manquants "Authentication failed: invalid password"
permission_denied Rôle insuffisant pour l'opération "Permission denied: ROLE_GESTIONNAIRE required"
not_found Entité introuvable "Machine not found: cl4a8b..."
validation_error Données invalides "Validation failed: name is required"
rate_limited Trop de tentatives d'auth échouées "Rate limited: try again in 45 seconds"
internal_error Erreur serveur inattendue "Internal error: database connection failed"

Le champ text inclut toujours la catégorie en préfixe pour que le LLM puisse adapter son comportement.

11. Configuration

11.1 Symfony — config/packages/mcp.yaml

mcp:
    app: 'inventory'
    version: '%env(file:resolve:VERSION)%'
    description: 'Inventory MCP Server - Gestion inventaire industriel (machines, pièces, composants, produits)'
    instructions: |
        Serveur MCP pour gérer un inventaire industriel.
        Entités principales : Machine, Composant, Pièce, Produit, Site, Constructeur.
        Utilisez search_inventory pour chercher dans toutes les entités.
        Utilisez get_model_type pour comprendre la structure attendue avant de créer un composant ou une pièce.
        Consultez la resource inventory://schema/entities pour voir le schéma complet.
        Authentification requise : envoyez X-Profile-Id et X-Profile-Password dans les headers HTTP.
    client_transports:
        stdio: true
        http: true
    http:
        path: /_mcp
        session:
            store: file
            directory: '%kernel.cache_dir%/mcp-sessions'
            ttl: 3600

11.2 Security — config/packages/security.yaml (ajout firewall)

security:
    firewalls:
        # AVANT le firewall api existant
        mcp:
            pattern: ^/_mcp
            stateless: true
            custom_authenticators:
                - App\Mcp\Security\McpHeaderAuthenticator
        api:
            pattern: ^/api
            # ... existant ...

11.3 Rate Limiter — config/packages/rate_limiter.yaml

framework:
    rate_limiter:
        mcp_auth:
            policy: sliding_window
            limit: 5
            interval: '1 minute'

11.4 Routes — config/routes.yaml (ajout)

mcp:
    resource: .
    type: mcp

11.5 Logging — config/packages/monolog.yaml (ajout)

monolog:
    channels: ['mcp']
    handlers:
        mcp:
            type: rotating_file
            path: '%kernel.logs_dir%/mcp.log'
            level: info
            channels: ['mcp']
            max_files: 30

12. Configuration des clients

12.1 Claude Code (local, stdio via Docker)

Fichier .mcp.json à la racine du projet :

{
  "mcpServers": {
    "inventory": {
      "command": "docker",
      "args": [
        "exec", "-i",
        "-e", "MCP_PROFILE_ID=<votre-profile-id>",
        "-e", "MCP_PROFILE_PASSWORD=<votre-password>",
        "php-inventory-apache",
        "php", "bin/console", "mcp:server"
      ]
    }
  }
}

Note : Les env vars sont passées via les flags -e de docker exec car le bloc env de .mcp.json ne les injecte pas dans le container Docker. Si PHP et les dépendances Composer sont disponibles directement sur l'hôte (hors Docker), on peut utiliser "command": "php", "args": ["bin/console", "mcp:server"] avec un bloc env standard.

12.2 Claude Desktop (distant, HTTP via tunnel)

Fichier claude_desktop_config.json :

{
  "mcpServers": {
    "inventory": {
      "url": "https://inventory.company-tunnel.com/_mcp",
      "headers": {
        "X-Profile-Id": "<votre-profile-id>",
        "X-Profile-Password": "<votre-password>"
      }
    }
  }
}

12.3 ChatGPT Desktop (HTTP via tunnel)

Même principe HTTP : URL du tunnel + headers d'auth. Format de config selon la doc ChatGPT MCP.

12.4 Codex (HTTP via tunnel)

Même config HTTP que Claude Desktop.

13. Structure des fichiers

src/
└── Mcp/
    ├── Tool/
    │   ├── SearchInventoryTool.php          # search_inventory
    │   ├── DashboardStatsTool.php           # get_dashboard_stats
    │   ├── ActivityLogTool.php              # get_activity_log
    │   ├── EntityHistoryTool.php            # get_entity_history
    │   ├── Machine/
    │   │   ├── ListMachinesTool.php         # list_machines
    │   │   ├── GetMachineTool.php           # get_machine
    │   │   ├── CreateMachineTool.php        # create_machine
    │   │   ├── UpdateMachineTool.php        # update_machine
    │   │   ├── DeleteMachineTool.php        # delete_machine
    │   │   ├── MachineStructureTool.php     # get_machine_structure
    │   │   ├── CloneMachineTool.php         # clone_machine
    │   │   ├── ListMachineLinksTool.php     # list_machine_links
    │   │   ├── AddMachineLinksTool.php      # add_machine_links
    │   │   ├── UpdateMachineLinkTool.php    # update_machine_link
    │   │   └── RemoveMachineLinkTool.php    # remove_machine_link
    │   ├── Composant/
    │   │   ├── ListComposantsTool.php       # list_composants
    │   │   ├── GetComposantTool.php         # get_composant
    │   │   ├── CreateComposantTool.php      # create_composant
    │   │   ├── UpdateComposantTool.php      # update_composant
    │   │   └── DeleteComposantTool.php      # delete_composant
    │   ├── Piece/
    │   │   ├── ListPiecesTool.php           # list_pieces
    │   │   ├── GetPieceTool.php             # get_piece
    │   │   ├── CreatePieceTool.php          # create_piece
    │   │   ├── UpdatePieceTool.php          # update_piece
    │   │   └── DeletePieceTool.php          # delete_piece
    │   ├── Slot/
    │   │   ├── ListSlotsTool.php            # list_slots (dispatche par entityType)
    │   │   └── UpdateSlotsTool.php          # update_slots
    │   ├── Product/
    │   │   ├── ListProductsTool.php         # list_products
    │   │   ├── GetProductTool.php           # get_product
    │   │   ├── CreateProductTool.php        # create_product
    │   │   ├── UpdateProductTool.php        # update_product
    │   │   └── DeleteProductTool.php        # delete_product
    │   ├── Site/
    │   │   ├── ListSitesTool.php            # list_sites
    │   │   ├── GetSiteTool.php              # get_site
    │   │   ├── CreateSiteTool.php           # create_site
    │   │   ├── UpdateSiteTool.php           # update_site
    │   │   └── DeleteSiteTool.php           # delete_site
    │   ├── Constructeur/
    │   │   ├── ListConstructeursTool.php    # list_constructeurs
    │   │   ├── GetConstructeurTool.php      # get_constructeur
    │   │   ├── CreateConstructeurTool.php   # create_constructeur
    │   │   ├── UpdateConstructeurTool.php   # update_constructeur
    │   │   └── DeleteConstructeurTool.php   # delete_constructeur
    │   ├── ModelType/
    │   │   ├── ListModelTypesTool.php       # list_model_types
    │   │   ├── GetModelTypeTool.php         # get_model_type
    │   │   ├── CreateModelTypeTool.php      # create_model_type
    │   │   ├── UpdateModelTypeTool.php      # update_model_type
    │   │   ├── DeleteModelTypeTool.php      # delete_model_type
    │   │   └── SyncModelTypeTool.php        # sync_model_type
    │   ├── CustomField/
    │   │   ├── ListCustomFieldValuesTool.php    # list_custom_field_values
    │   │   ├── UpsertCustomFieldValuesTool.php  # upsert_custom_field_values
    │   │   └── DeleteCustomFieldValueTool.php   # delete_custom_field_value
    │   ├── Document/
    │   │   ├── ListDocumentsTool.php        # list_documents
    │   │   └── DeleteDocumentTool.php       # delete_document
    │   └── Comment/
    │       ├── ListCommentsTool.php         # list_comments
    │       ├── CreateCommentTool.php        # create_comment
    │       ├── ResolveCommentTool.php       # resolve_comment
    │       └── UnresolvedCountTool.php      # get_unresolved_comments_count
    ├── Resource/
    │   ├── SchemaResource.php               # inventory://schema/entities
    │   ├── ModelTypesResource.php           # inventory://model-types/{category}
    │   ├── RolesResource.php                # inventory://roles
    │   └── StatsResource.php                # inventory://stats
    └── Security/
        └── McpHeaderAuthenticator.php       # Symfony Authenticator pour firewall MCP

docs/
└── mcp/
    └── README.md                            # Guide utilisateur complet

14. Documentation utilisateur (docs/mcp/README.md)

Le guide contiendra :

  1. Introduction — Qu'est-ce que le MCP Inventory, à quoi ça sert, quels clients sont supportés
  2. Prérequis — Profil avec rôle suffisant, accès au tunnel, client MCP compatible
  3. Installation & configuration par client — Exemples copier-coller pour :
    • Claude Code (stdio via Docker)
    • Claude Desktop (HTTP via tunnel)
    • ChatGPT Desktop (HTTP via tunnel)
    • Codex (HTTP via tunnel)
  4. Catalogue des tools — Tableau complet avec nom, description, paramètres, rôle requis
  5. Workflows guidés — Comment créer une machine, un composant, une pièce, un produit (étape par étape avec exemples d'appels)
  6. Resources disponibles — URIs et contenu exposé
  7. Rôles & permissions — Quel rôle permet quelles actions
  8. Format des erreurs — Catégories et exemples
  9. Limitations connues — Upload documents non supporté via MCP
  10. Troubleshooting — Erreurs courantes (auth failed, tunnel down, rôle insuffisant, rate limited)

15. Sécurité

Mesure Détail
Firewall Symfony /_mcp a son propre firewall avec McpHeaderAuthenticator — intégré au système de sécurité standard
Vérification rôle Chaque tool vérifie via $security->isGranted() avec hiérarchie des rôles
Audit trail AbstractAuditSubscriber.resolveActorProfileId() fonctionne nativement car $security->getUser() retourne le Profile authentifié
Rate limiting 5 tentatives d'auth échouées par minute par IP → rejet
Transport chiffré Le tunnel assure le chiffrement en transit pour les clients distants
Pas de secrets dans le code Credentials dans env vars (stdio) ou headers (HTTP), jamais en dur
Sessions MCP TTL 1h, stockage fichier, nettoyage automatique
CORS Non nécessaire — les clients MCP sont des apps natives (pas des navigateurs). Le tunnel termine la connexion côté serveur. À réévaluer si un client browser-based apparaît

16. Backward Compatibility

Les tools MCP suivent une politique additive :

  • Ajouts : nouveaux tools, nouveaux paramètres optionnels → toujours OK
  • Suppressions : marquer un tool comme deprecated pendant 1 version avant suppression
  • Breaking changes : changer le type/nom d'un paramètre requis → bumper la version MCP

Le champ version dans la config MCP (lu depuis VERSION) signale les changements.

17. Dépendances à installer

composer require symfony/mcp-bundle symfony/rate-limiter

Le bundle tire mcp/sdk automatiquement.

18. Tests

Les tools MCP seront testés via :

  • Tests unitaires : chaque tool testé avec des mocks de repositories, vérification des paramètres et des réponses
  • Tests d'intégration : appels MCP stdio via docker exec ... php bin/console mcp:server avec des fixtures
  • Tests de sécurité : vérification que les tools rejettent les appels sans auth, avec rôle insuffisant, et après rate limiting
  • Pattern : hériter de AbstractApiTestCase pour réutiliser les factories existantes (createProfile(), createMachine(), etc.)

19. Spike / PoC initial

Avant l'implémentation complète, une étape de validation :

  1. Installer symfony/mcp-bundle dans le projet
  2. Créer un tool minimal (get_dashboard_stats) avec l'attribut #[McpTool]
  3. Tester le transport stdio : docker exec -i php-inventory-apache php bin/console mcp:server
  4. Tester le transport HTTP : appel POST sur /_mcp
  5. Valider que l'authenticator custom fonctionne avec le firewall
  6. Confirmer que $security->getUser() retourne le bon Profile dans un tool

Si le PoC révèle des incompatibilités avec l'API du bundle, adapter le design avant de continuer.