- 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>
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 :
- Extrait
X-Profile-IdetX-Profile-Passworddes headers - Charge le profil via
ProfileRepository - Vérifie le password hash via
UserPasswordHasherInterface - Retourne un
Passportavec le Profile comme User - 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) :
- Lit
MCP_PROFILE_IDetMCP_PROFILE_PASSWORDdepuis$_ENV - Valide les credentials
- Injecte un
UsernamePasswordTokensynthétique dans leTokenStorageavec 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.
6.11 Tools — Machine Links (splittés)
| 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_slotset un seulupdate_slots— ils acceptent un paramètreentityTypepour 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
-ededocker execcar le blocenvde.mcp.jsonne 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 blocenvstandard.
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 :
- Introduction — Qu'est-ce que le MCP Inventory, à quoi ça sert, quels clients sont supportés
- Prérequis — Profil avec rôle suffisant, accès au tunnel, client MCP compatible
- 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)
- Catalogue des tools — Tableau complet avec nom, description, paramètres, rôle requis
- Workflows guidés — Comment créer une machine, un composant, une pièce, un produit (étape par étape avec exemples d'appels)
- Resources disponibles — URIs et contenu exposé
- Rôles & permissions — Quel rôle permet quelles actions
- Format des erreurs — Catégories et exemples
- Limitations connues — Upload documents non supporté via MCP
- 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:serveravec 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
AbstractApiTestCasepour réutiliser les factories existantes (createProfile(),createMachine(), etc.)
19. Spike / PoC initial
Avant l'implémentation complète, une étape de validation :
- Installer
symfony/mcp-bundledans le projet - Créer un tool minimal (
get_dashboard_stats) avec l'attribut#[McpTool] - Tester le transport stdio :
docker exec -i php-inventory-apache php bin/console mcp:server - Tester le transport HTTP : appel POST sur
/_mcp - Valider que l'authenticator custom fonctionne avec le firewall
- 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.