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:
185
docs/mcp/README.md
Normal file
185
docs/mcp/README.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# MCP Server — Inventory
|
||||
|
||||
Serveur MCP (Model Context Protocol) pour l'application Inventory. Permet aux assistants IA (Claude, ChatGPT, Codex) de consulter et gérer l'inventaire industriel.
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Un profil actif avec rôle suffisant (ROLE_VIEWER pour lecture, ROLE_GESTIONNAIRE pour écriture)
|
||||
- Accès au tunnel pour les clients distants (Claude Desktop, ChatGPT Desktop)
|
||||
- Docker Compose démarré (`make start`)
|
||||
|
||||
## Configuration par client
|
||||
|
||||
### Claude Code (local, stdio)
|
||||
|
||||
Le fichier `.mcp.json` à la racine du projet est déjà configuré. Remplacez les placeholders :
|
||||
|
||||
```json
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Claude Desktop (HTTP via tunnel)
|
||||
|
||||
Dans `claude_desktop_config.json` :
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"inventory": {
|
||||
"url": "https://inventory.company-tunnel.com/_mcp",
|
||||
"headers": {
|
||||
"X-Profile-Id": "VOTRE_PROFILE_ID",
|
||||
"X-Profile-Password": "VOTRE_PASSWORD"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ChatGPT Desktop / Codex
|
||||
|
||||
Meme principe HTTP avec l'URL du tunnel + headers d'auth.
|
||||
|
||||
## Catalogue des Tools
|
||||
|
||||
### Tools de haut niveau
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `search_inventory` | Recherche globale (machines, pieces, composants, produits, sites, constructeurs) | VIEWER |
|
||||
| `get_machine_structure` | Hierarchie complete d'une machine | VIEWER |
|
||||
| `clone_machine` | Clone une machine avec toute sa structure | GESTIONNAIRE |
|
||||
| `get_dashboard_stats` | Statistiques globales | VIEWER |
|
||||
| `get_entity_history` | Historique d'audit d'une entite | VIEWER |
|
||||
| `get_activity_log` | Journal d'activite global | VIEWER |
|
||||
|
||||
### CRUD par entite
|
||||
|
||||
Pour chaque entite (Machine, Composant, Piece, Produit, Site, Constructeur) :
|
||||
|
||||
| Pattern | Exemple | Role |
|
||||
|---------|---------|------|
|
||||
| `list_{entite}s` | `list_machines` | VIEWER |
|
||||
| `get_{entite}` | `get_machine` | VIEWER |
|
||||
| `create_{entite}` | `create_machine` | GESTIONNAIRE |
|
||||
| `update_{entite}` | `update_machine` | GESTIONNAIRE |
|
||||
| `delete_{entite}` | `delete_machine` | GESTIONNAIRE |
|
||||
|
||||
### Slots
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `list_slots` | Lister les slots d'un composant ou piece | VIEWER |
|
||||
| `update_slots` | Remplir/vider les slots | GESTIONNAIRE |
|
||||
|
||||
### Machine Links
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `list_machine_links` | Liens composant/piece/produit d'une machine | VIEWER |
|
||||
| `add_machine_links` | Ajouter des liens | GESTIONNAIRE |
|
||||
| `update_machine_link` | Modifier un lien | GESTIONNAIRE |
|
||||
| `remove_machine_link` | Supprimer un lien | GESTIONNAIRE |
|
||||
|
||||
### Commentaires
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `list_comments` | Lister les commentaires d'une entite | VIEWER |
|
||||
| `create_comment` | Creer un commentaire | VIEWER |
|
||||
| `resolve_comment` | Resoudre un commentaire | GESTIONNAIRE |
|
||||
| `get_unresolved_comments_count` | Nombre de commentaires non resolus | VIEWER |
|
||||
|
||||
### Custom Fields
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `list_custom_field_values` | Valeurs de champs perso d'une entite | VIEWER |
|
||||
| `upsert_custom_field_values` | Creer/mettre a jour des valeurs | GESTIONNAIRE |
|
||||
| `delete_custom_field_value` | Supprimer une valeur | GESTIONNAIRE |
|
||||
|
||||
### Documents
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `list_documents` | Lister les documents d'une entite | VIEWER |
|
||||
| `delete_document` | Supprimer un document | GESTIONNAIRE |
|
||||
|
||||
> **Limitation :** L'upload de documents n'est pas supporte via MCP (protocole JSON uniquement). Utilisez l'API REST `/api/documents` (POST multipart).
|
||||
|
||||
### ModelTypes
|
||||
|
||||
| Tool | Description | Role |
|
||||
|------|-------------|------|
|
||||
| `list_model_types` | Lister par categorie | VIEWER |
|
||||
| `get_model_type` | Detail avec skeleton requirements | VIEWER |
|
||||
| `create_model_type` | Creer | GESTIONNAIRE |
|
||||
| `update_model_type` | Modifier | GESTIONNAIRE |
|
||||
| `delete_model_type` | Supprimer | GESTIONNAIRE |
|
||||
| `sync_model_type` | Preview/sync skeleton | GESTIONNAIRE |
|
||||
|
||||
## Workflows guides
|
||||
|
||||
### Creer un composant complet
|
||||
|
||||
```
|
||||
1. list_model_types(category: "composant") -> choisir le type
|
||||
2. get_model_type(modelTypeId: "...") -> voir le skeleton
|
||||
3. create_composant(name, reference, modelTypeId) -> cree + slots auto
|
||||
4. search_inventory(query: "Roulement", types: "piece") -> trouver pieces
|
||||
5. update_slots(slots: [{slotId, selectedPieceId}]) -> remplir
|
||||
6. upsert_custom_field_values(entityType: "composant", entityId, fields: [...])
|
||||
```
|
||||
|
||||
### Creer une machine complete (bottom-up)
|
||||
|
||||
```
|
||||
1. Creer les produits necessaires
|
||||
2. Creer les pieces (avec produits dans les slots)
|
||||
3. Creer les composants (avec pieces dans les slots)
|
||||
4. list_sites -> choisir le site
|
||||
5. create_machine(name, siteId)
|
||||
6. add_machine_links(machineId, links: [{type: "composant", entityId, quantity}])
|
||||
7. upsert_custom_field_values(entityType: "machine", machineId, fields: [...])
|
||||
```
|
||||
|
||||
## Resources MCP
|
||||
|
||||
| URI | Description |
|
||||
|-----|-------------|
|
||||
| `inventory://schema/entities` | Schema de toutes les entites |
|
||||
| `inventory://roles` | Hierarchie des roles et permissions |
|
||||
| `inventory://stats` | Statistiques globales |
|
||||
|
||||
## Roles & Permissions
|
||||
|
||||
```
|
||||
ROLE_ADMIN > ROLE_GESTIONNAIRE > ROLE_VIEWER > ROLE_USER
|
||||
```
|
||||
|
||||
- **VIEWER** : lecture, recherche, commentaires
|
||||
- **GESTIONNAIRE** : ecriture (CRUD, slots, links, clone)
|
||||
- **ADMIN** : gestion profils (via API REST uniquement)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Erreur | Cause | Solution |
|
||||
|--------|-------|----------|
|
||||
| `401 Unauthorized` | Credentials invalides | Verifier X-Profile-Id et X-Profile-Password |
|
||||
| `Permission denied: ROLE_GESTIONNAIRE required` | Role insuffisant | Utiliser un profil avec le bon role |
|
||||
| `Rate limited` | Trop de tentatives echouees | Attendre 1 minute |
|
||||
| `Tool not found` | Tool non enregistre | Verifier que le cache est a jour (`cache:clear`) |
|
||||
| `Error while executing tool` | Erreur interne | Verifier les logs et les parametres |
|
||||
1472
docs/superpowers/plans/2026-03-16-mcp-server.md
Normal file
1472
docs/superpowers/plans/2026-03-16-mcp-server.md
Normal file
File diff suppressed because it is too large
Load Diff
669
docs/superpowers/specs/2026-03-16-mcp-server-design.md
Normal file
669
docs/superpowers/specs/2026-03-16-mcp-server-design.md
Normal file
@@ -0,0 +1,669 @@
|
||||
# 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.
|
||||
|
||||
```yaml
|
||||
# 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
|
||||
|
||||
```yaml
|
||||
# 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_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
|
||||
|
||||
```json
|
||||
{
|
||||
"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 :
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
```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)
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
```yaml
|
||||
framework:
|
||||
rate_limiter:
|
||||
mcp_auth:
|
||||
policy: sliding_window
|
||||
limit: 5
|
||||
interval: '1 minute'
|
||||
```
|
||||
|
||||
### 11.4 Routes — config/routes.yaml (ajout)
|
||||
|
||||
```yaml
|
||||
mcp:
|
||||
resource: .
|
||||
type: mcp
|
||||
```
|
||||
|
||||
### 11.5 Logging — config/packages/monolog.yaml (ajout)
|
||||
|
||||
```yaml
|
||||
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 :
|
||||
|
||||
```json
|
||||
{
|
||||
"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` :
|
||||
|
||||
```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
|
||||
|
||||
```bash
|
||||
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.
|
||||
Reference in New Issue
Block a user