Module 5 — Basculer en mode sécurisé
Ce module bascule la stack du Module 2 du mode YOLO vers le mode sécurisé décrit au Module 4. Toutes les commandes ont été réellement exécutées contre le module mcp_server obtenu depuis sa source officielle — jamais copié dans ce dépôt (voir l’avertissement de licence du Module 4).
1. Monter votre copie du module (local uniquement)
Section titled “1. Monter votre copie du module (local uniquement)”Créez un docker-compose.override.yml (déjà dans .gitignore du projet) à partir de l’exemple fourni :
cp docker-compose.override.example.yml docker-compose.override.ymlAdaptez-y le chemin vers votre propre copie du module :
services: odoo: volumes: - "/chemin/vers/votre/copie/mcp_server:/mnt/extra-addons/mcp_server:ro"Puis recréez le conteneur Odoo pour qu’il prenne en compte ce nouveau chemin d’extension :
docker compose up -d odoo2. Installer le module
Section titled “2. Installer le module”L’image officielle odoo:16 place déjà /mnt/extra-addons dans addons_path par défaut. On installe le module directement en ligne de commande, dans la base créée au Module 2 :
docker exec mcp-odoo-toolkit-odoo-1 \ odoo --db_host=db --db_port=5432 --db_user=odoo --db_password=odoo \ -d mcp_demo -i mcp_server --stop-after-initSortie réelle (extrait) :
odoo.modules.loading: Loading module mcp_server (18/24)odoo.modules.registry: module mcp_server: creating or updating database tablesodoo.modules.loading: loading mcp_server/security/security.xmlodoo.modules.loading: loading mcp_server/security/ir.model.access.csvodoo.modules.loading: loading mcp_server/views/mcp_enabled_models_views.xmlodoo.modules.loading: loading mcp_server/views/res_config_settings_views.xmlodoo.modules.loading: Module mcp_server loaded in 3.62s, 391 queries (+391 other)...odoo.modules.loading: 24 modules loaded in 33.13s, 7683 queries (+7683 extra)Aucune erreur : la seule dépendance Python externe du module (defusedxml) était déjà présente dans l’image officielle.
3. Activer MCP et whitelister un modèle
Section titled “3. Activer MCP et whitelister un modèle”Deux réglages sont nécessaires : le commutateur global (mcp_server.enabled) et l’activation du modèle res.partner en lecture seule uniquement, via l’ORM (ici en ligne de commande, l’équivalent de ce que fait l’interface Paramètres > MCP Server) :
res = models.execute_kw(db, uid, password, "ir.config_parameter", "set_param", ["mcp_server.enabled", "True"])
partner_model_id = models.execute_kw(db, uid, password, "ir.model", "search", [[["model", "=", "res.partner"]]])
models.execute_kw(db, uid, password, "mcp.enabled.model", "create", [{ "model_id": partner_model_id[0], "allow_read": True, "allow_create": False, "allow_write": False, "allow_unlink": False,}])Vérification via l’endpoint REST du module lui-même (/mcp/health et /mcp/system/info, sans dépendre encore du serveur MCP) :
curl -s http://localhost:8069/mcp/health{"success": true, "data": {"status": "ok", "mcp_server_version": "16.0.1.2.0"}}4. Générer une clé d’API
Section titled “4. Générer une clé d’API”En interface graphique, cela se fait depuis Mon profil > Comptes développeur > Nouvelle clé API. Pour rester scriptable, on peut appeler directement la méthode native d’Odoo (res.users.apikeys._generate) via odoo shell, en se plaçant dans le contexte de l’utilisateur admin :
ApiKeys = env["res.users.apikeys"].with_user(2) # 2 = adminkey_value = ApiKeys._generate(None, "mcp-odoo-toolkit-test")print(key_value)env.cr.commit()odoo.addons.base.models.res_users: Users API Keys generated: scope: <None> for 'admin' (#2) from n/a022aa9fc2e714fabe5eeeac09da4470ef0efc0a7Confirmation, toujours via l’endpoint REST du module, avec la clé fraîchement générée :
curl -s http://localhost:8069/mcp/system/info -H "X-API-Key: 022aa9fc2e714fabe5eeeac09da4470ef0efc0a7"{"success": true, "data": {"db_name": "mcp_demo", "enabled_mcp_models": 1, "mcp_server_version": "16.0.1.2.0"}}enabled_mcp_models: 1 confirme que seul res.partner est whitelisté.
5. Reconfigurer le serveur MCP
Section titled “5. Reconfigurer le serveur MCP”Basculez .env du mode YOLO vers la clé d’API :
ODOO_DB=mcp_demoODOO_API_KEY=022aa9fc2e714fabe5eeeac09da4470ef0efc0a7ODOO_YOLO=falsedocker compose up -d mcp-serverLes logs du conteneur montrent un comportement radicalement différent du Module 2 :
odoo_connection - INFO - Authenticating in standard MCP mode for database 'mcp_demo'odoo_connection - INFO - Attempting MCP API key authenticationodoo_connection - INFO - Successfully authenticated with MCP API key (UID: 2)access_control - INFO - Initialized AccessController for http://odoo:8069 (API key auth)odoo_connection - ERROR - XML-RPC fault during read on res.users: <Fault 403: "Access denied by MCP for model 'res.users' method 'read'.">Plus aucune mention de « YOLO » : le serveur s’authentifie désormais par clé d’API — et tente même, dès le démarrage, de lire res.users pour construire son contexte utilisateur, tentative que le module rejette immédiatement puisque res.users n’est pas whitelisté. C’est le contrôle d’accès en action, dès la première seconde.
6. Rejouer le client du Module 3
Section titled “6. Rejouer le client du Module 3”Sans changer une ligne du client Python, on relance exactement le même script :
python examples/demo.py{ "models": [ { "model": "res.partner", "name": "Contact", "operations": { "read": true, "write": false, "create": false, "unlink": false } } ], "yolo_mode": null, "total": 1, "total_available": 1}search_records sur res.partner continue de fonctionner (lecture autorisée) et retourne les mêmes 36 contacts qu’au Module 3. En revanche, une tentative d’écriture est rejetée :
python examples/test_hardened_denial.pyError executing tool create_record: Access denied: Operation 'create' not allowed on model 'res.partner'7. Vérifier le journal d’audit
Section titled “7. Vérifier le journal d’audit”logs = env["mcp.log"].sudo().search_read( [], ["event_type", "model_name", "operation"], limit=5, order="id desc"){'event_type': 'model_access', 'model_name': 'res.partner', 'operation': 'read'}{'event_type': 'auth_success', 'model_name': False, 'operation': False}{'event_type': 'model_access', 'model_name': 'res.partner', 'operation': 'fields_get'}{'event_type': 'model_access', 'model_name': 'res.partner', 'operation': 'search_count'}{'event_type': 'model_access', 'model_name': 'res.partner', 'operation': 'search'}Chaque appel XML-RPC réellement transmis à Odoo est journalisé. Une nuance observée en testant : le refus de create_record vu à l’étape 6 est décidé côté serveur MCP lui-même (à partir de la liste des modèles et opérations autorisés qu’il a récupérée), avant même d’émettre un appel XML-RPC — il n’apparaît donc pas dans mcp.log, qui journalise les appels reçus côté Odoo. Le refus de lecture de res.users à l’étape 5, lui, provient bien d’un appel XML-RPC réellement reçu et rejeté par Odoo, et apparaît donc dans le journal avec l’événement permission_denied.
Ce que cette bascule a changé, concrètement
Section titled “Ce que cette bascule a changé, concrètement”| Mode YOLO (Module 3) | Mode sécurisé (ce module) | |
|---|---|---|
Modèles visibles par list_models |
55 | 1 (res.partner) |
Opérations sur res.partner |
toutes | lecture seule |
| Authentification | mot de passe Odoo | clé d’API dédiée |
res.users accessible ? |
oui | non — rejeté et journalisé |
| Trace des accès | aucune | mcp.log, par appel |
Prochaine étape
Section titled “Prochaine étape”Le Module 6 branche ce même serveur MCP (en mode sécurisé ou YOLO, au choix) sur une interface de chat complète : LibreChat.