Skip to main content
Glama
README.md
<div align="center">

# 🔌 odoo-mcp

**Pilotez n'importe quelle base Odoo en langage naturel — sans jamais perdre le contrîle.**

Un serveur [MCP](https://modelcontextprotocol.io) qui connecte votre assistant IA
(Claude Code, Antigravity, Gemini CLI, Claude Desktop, Cursor
) à Odoo via XML-RPC.
Lecture instantanée, écriture gardée, import/export Excel, journal d'audit et
rapports prĂ©sentables au client — le tout depuis une simple conversation.

<br/>

![Version](https://img.shields.io/badge/version-1.10.0-1f6feb?style=flat-square)
![Python](https://img.shields.io/badge/python-3.10+-3776AB?style=flat-square&logo=python&logoColor=white)
![MCP](https://img.shields.io/badge/MCP-compatible-8A2BE2?style=flat-square)
![Outils](https://img.shields.io/badge/outils-37-fb8500?style=flat-square)
![Licence](https://img.shields.io/badge/licence-MIT-2ea043?style=flat-square)

</div>

---

> **En une phrase.** On parle Ă  sa base Odoo comme Ă  un collĂšgue — *« combien de commandes
> en cours ce mois-ci ? »*, *« importe ce catalogue de 1 500 articles »*, *« prépare une
> maquette pour ce prospect »* — et l'outil traduit, exĂ©cute, et **rend des comptes** :
> rien ne s'écrit sans garde-fou, et tout ce qui s'écrit est tracé.

## 📑 Sommaire

- [✹ Pourquoi odoo-mcp](#-pourquoi-odoo-mcp)
- [đŸ—ïž Architecture](#-architecture)
- [🔒 Le modĂšle de sĂ©curitĂ©](#-le-modĂšle-de-sĂ©curitĂ©)
- [🚀 Installation](#-installation)
- [💬 Utilisation](#-utilisation)
- [🧰 Les 37 outils](#-les-37-outils)
- [📋 Le rapport d'intervention](#-le-rapport-dintervention)
- [🎬 Le guide de prĂ©sentation](#-le-guide-de-prĂ©sentation)
- [đŸ“„ Importer un fichier](#-importer-un-fichier)
- [🎭 PrĂ©parer une dĂ©monstration](#-prĂ©parer-une-dĂ©monstration)
- [📊 GĂ©nĂ©rer un tableau de bord](#-gĂ©nĂ©rer-un-tableau-de-bord)
- [⚡ Économie de contexte](#-Ă©conomie-de-contexte)
- [🧭 Notes de terrain](#-notes-de-terrain)
- [📄 Licence](#-licence)

## ✹ Pourquoi odoo-mcp

Ce n'est pas un connecteur « boßte noire » de plus. Chaque décision de conception vise le
mĂȘme objectif : donner de la puissance Ă  l'assistant **sans lui donner les pleins pouvoirs**.

| | |
|---|---|
| 🔐 **Aucun identifiant stockĂ©** | L'assistant demande l'URL, le login et la clĂ© API dans la conversation (`odoo_connect`). Ils ne vivent qu'en mĂ©moire, le temps de la session. |
| đŸš« **Écriture bloquĂ©e par dĂ©faut** | Elle s'active par un outil dĂ©diĂ© (`odoo_enable_write`), que l'assistant n'appelle qu'aprĂšs accord explicite. Sinon, seules les mĂ©thodes de **lecture** sont autorisĂ©es (liste blanche) — y compris via l'appel brut `odoo_execute`. |
| đŸ‘ïž **Modifications de masse prĂ©visualisĂ©es** | `odoo_update_where` montre d'abord combien d'enregistrements sont visĂ©s, avec un Ă©chantillon avant/aprĂšs, et n'Ă©crit qu'aprĂšs confirmation. |
| 📊 **Import/export Excel natif** | Le serveur tourne en local : il lit et Ă©crit les fichiers directement. Un catalogue de 1 500 lignes s'importe sans passer par la conversation. |
| ♻ **Écritures rejouables** | `odoo_upsert` et l'import par External ID mettent Ă  jour au lieu de dupliquer. |
| đŸ§Ÿ **Tout est tracĂ©** | Chaque Ă©criture est journalisĂ©e avec son Ă©tat avant/aprĂšs ; `odoo_journal_report` produit un rapport d'intervention prĂ©sentable au client. Secrets masquĂ©s, binaires rĂ©sumĂ©s, journal lisible par le seul utilisateur (`0600`). |
| 📈 **Tableaux de bord rĂ©els** | `odoo_dashboard_create` produit de vrais tableaux de bord Odoo dont les graphiques sont recalculĂ©s en direct — pas des captures d'Ă©cran. |
| 🎭 **Maquettes de dĂ©mo sĂ»res** | Un questionnaire de qualification cadre l'avant-vente, et le mode dĂ©monstration neutralise **toute** adresse e-mail Ă©crite : aucune fausse facture ne peut partir chez une vraie entreprise. |

## đŸ—ïž Architecture

Un seul processus local fait le pont entre l'assistant et Odoo. Rien n'est hébergé ailleurs,
aucune donnée ne transite par un tiers.

```mermaid
flowchart LR
    A["đŸ€– Assistant IA<br/>Claude · Gemini · Cursor
"]
    B["🔌 odoo-mcp<br/>serveur local"]
    C[("đŸ—„ïž Base Odoo")]
    D["📁 Fichiers locaux<br/>Excel · rapports · cache"]

    A -->|"protocole MCP"| B
    B -->|"XML-RPC"| C
    B -->|"lit / écrit"| D

    classDef ai fill:#eef2ff,stroke:#6366f1,color:#1e1b4b;
    classDef srv fill:#ecfeff,stroke:#06b6d4,color:#083344;
    classDef odoo fill:#f3e8ff,stroke:#a855f7,color:#3b0764;
    classDef file fill:#fff7ed,stroke:#f97316,color:#431407;
    class A ai; class B srv; class C odoo; class D file;
```

## 🔒 Le modĂšle de sĂ©curitĂ©

Le serveur est le **point de passage obligé** de toute opération. C'est là que sont posés
les garde-fous — donc ils tiennent quel que soit l'outil appelĂ© (crĂ©ation, modification,
upsert, import de fichier, ou appel brut `odoo_execute`).

```mermaid
flowchart TD
    Q["Appel d'un outil d'Ă©criture"] --> R{"Écriture activĂ©e ?"}
    R -->|Non| RO["⛔ RefusĂ©<br/>lecture seule (liste blanche)"]
    R -->|Oui| M{"Modification de masse ?"}
    M -->|Oui| P["🔎 PrĂ©visualisation<br/>volume + Ă©chantillon avant/aprĂšs"] --> CF{"ConfirmĂ© ?"}
    CF -->|Non| STOP["đŸš« AnnulĂ© — rien n'est Ă©crit"]
    CF -->|Oui| EX["✍ Écriture"]
    M -->|Non| EX
    EX --> J[("📓 Journal 0600<br/>secrets masquĂ©s · avant→aprĂšs")]

    classDef gate fill:#fef2f2,stroke:#ef4444,color:#450a0a;
    classDef ok fill:#f0fdf4,stroke:#22c55e,color:#052e16;
    classDef log fill:#f8fafc,stroke:#64748b,color:#0f172a;
    class R,M,CF gate; class EX,P ok; class J log;
```

## 🚀 Installation

> **PrĂ©requis :** [uv](https://docs.astral.sh/uv/getting-started/installation/) —
> `winget install astral-sh.uv` (Windows) · `brew install uv` (macOS) ·
> `curl -LsSf https://astral.sh/uv/install.sh | sh` (Linux).

### En un clic

[![Installer dans Cursor](https://img.shields.io/badge/Cursor-Installer%20en%201%20clic-0098FF?style=for-the-badge&logo=cursor)](cursor://anysphere.cursor-deeplink/mcp/install?name=odoo&config=eyJjb21tYW5kIjogInV2eCIsICJhcmdzIjogWyItLXJlZnJlc2giLCAiLS1mcm9tIiwgImdpdCtodHRwczovL2dpdGh1Yi5jb20vSmFtaVRoZVMvb2Rvby1tY3AiLCAib2Rvby1tY3AiXX0=)
[![Installer dans VS Code](https://img.shields.io/badge/VS%20Code-Installer%20en%201%20clic-007ACC?style=for-the-badge&logo=visualstudiocode)](https://vscode.dev/redirect/mcp/install?name=odoo&config=%7B%22name%22%3A%20%22odoo%22%2C%20%22command%22%3A%20%22uvx%22%2C%20%22args%22%3A%20%5B%22--refresh%22%2C%20%22--from%22%2C%20%22git%2Bhttps%3A//github.com/JamiTheS/odoo-mcp%22%2C%20%22odoo-mcp%22%5D%7D)

Clique le bouton de ton IDE → il ouvre l'Ă©diteur et propose d'ajouter le serveur `odoo`.
Accepte, c'est installé. *(Le bouton Cursor n'ouvre l'IDE que si Cursor est installé.)*

### Par prompt — Claude Code · Gemini · Antigravity

Pas de bouton pour ces clients ? **Copie-colle ce prompt dans le chat de ton assistant** :
il installe le serveur lui-mĂȘme.

```text
Installe le serveur MCP « odoo » pour moi, puis confirme.

Il se lance avec la commande `uvx` et les arguments :
--refresh --from git+https://github.com/JamiTheS/odoo-mcp odoo-mcp

ProcĂšde selon l'outil dans lequel tu tournes :
- Claude Code : exécute
  claude mcp add --scope user odoo -- uvx --refresh --from git+https://github.com/JamiTheS/odoo-mcp odoo-mcp
- Gemini CLI : ajoute une entrée « odoo » sous « mcpServers » dans ~/.gemini/settings.json
- Antigravity ou autre client : ajoute la mĂȘme entrĂ©e « mcpServers.odoo » dans son fichier
  de config MCP.

L'entrée JSON à utiliser (sauf pour la commande Claude Code) :
  "odoo": {
    "command": "uvx",
    "args": ["--refresh", "--from", "git+https://github.com/JamiTheS/odoo-mcp", "odoo-mcp"]
  }

Ne mets aucune clé API ni URL Odoo en dur. Quand c'est fait, dis-moi de redémarrer le
client pour que le serveur « odoo » soit chargé.
```

### En manuel

<details>
<summary><b>Claude Code</b> — une seule commande</summary>

```bash
claude mcp add --scope user odoo -- uvx --refresh --from git+https://github.com/JamiTheS/odoo-mcp odoo-mcp
```
</details>

<details>
<summary><b>Antigravity / Gemini CLI</b> — bloc JSON à coller</summary>

Antigravity : panneau **MCP Servers** → *Manage MCP config*. Gemini CLI : `~/.gemini/settings.json`.

```json
{
  "mcpServers": {
    "odoo": {
      "command": "uvx",
      "args": ["--refresh", "--from", "git+https://github.com/JamiTheS/odoo-mcp", "odoo-mcp"]
    }
  }
}
```
</details>

<details>
<summary><b>Claude Desktop</b></summary>

ParamĂštres → DĂ©veloppeur → `claude_desktop_config.json` : mĂȘme bloc JSON que ci-dessus.
</details>

C'est tout : `uvx` télécharge, installe et lance le serveur tout seul au premier démarrage.

> [!IMPORTANT]
> **🔄 Mises Ă  jour automatiques.** Le `--refresh` prĂ©sent dans toutes les configs est la
> clé : à **chaque démarrage du client**, `uvx` revérifie la derniÚre version publiée sur
> GitHub et la récupÚre si du nouveau code a été poussé. Autrement dit, dÚs qu'un `git push`
> est fait, les utilisateurs sont à jour **au prochain lancement de leur IDE** — sans rien
> réinstaller.
>
> *Contrepartie :* quelques secondes de plus au démarrage et une connexion réseau requise à
> ce moment-lĂ . Pour figer une version, retirer `--refresh` des `args` : la mise Ă  jour
> redevient manuelle.

## 💬 Utilisation

### Au démarrage : deux modes

À l'ouverture d'une session, le serveur se prĂ©sente (via le champ `instructions` du protocole
MCP) et l'assistant propose de choisir un mode de travail :

- **🧭 Consultant** — intervention sur une base **rĂ©elle**. L'assistant ouvre un journal
  d'audit avant d'écrire, garde l'écriture bloquée jusqu'à ton accord, et propose un rapport
  d'intervention en fin de travail.
- **🎭 Avant-vente (Sales)** — construire une **maquette de dĂ©monstration**. L'assistant part
  du questionnaire de qualification, active le filet e-mail (`odoo_demo_mode`), puis peuple la
  base avec `odoo_demo_generate`.

> [!NOTE]
> La présentation dépend du client MCP : la plupart injectent ces `instructions` dans le
> contexte de l'assistant, qui t'accueille alors dĂšs ton premier message. L'assistant ne
> choisit pas le mode à ta place — il te pose la question.

### Connexion

Au premier échange, l'assistant demande trois informations :

1. **URL de la base** — ex. `https://acme.odoo.com`
2. **Login** — l'e-mail de connexion
3. **ClĂ© API** — dans Odoo : avatar → *Mon profil* → *SĂ©curitĂ© du compte* → *Nouvelle clĂ©
   API*, en **laissant le champ « Scope » vide** (une clé de scope « MCP » est refusée en
   XML-RPC)

Puis on parle à sa base en langage naturel : *« combien de commandes en cours ? »*,
*« montre les champs de res.partner »*, *« corrige le téléphone de ce contact »*.

### Une session type

Un exemple de bout en bout : importer un catalogue puis préparer une démo. Remarquez
l'Ă©tape oĂč l'assistant **s'arrĂȘte pour demander l'autorisation** avant la moindre Ă©criture.

```mermaid
sequenceDiagram
    autonumber
    actor U as Vous
    participant A as Assistant IA
    participant M as odoo-mcp
    participant O as Odoo
    U->>A: « importe ce catalogue et prépare une démo »
    A->>M: odoo_connect (URL · login · clé)
    M->>O: authentification XML-RPC
    A->>M: odoo_import_file (mode check)
    M->>O: validation champ par champ
    M-->>A: 0 erreur — prĂȘt Ă  Ă©crire
    A->>U: « je peux activer l'écriture ? »
    U->>A: oui, vas-y
    A->>M: odoo_enable_write + import (mode run)
    M->>O: écriture par lots + journalisation
    M-->>A: 40 articles créés
    A->>U: rapport d'intervention HTML
```

<details>
<summary><b>Base fixe (optionnel)</b></summary>

Pour se connecter automatiquement à une base donnée, ajouter à la déclaration du serveur
(la clĂ© est alors en clair dans le fichier de config du client — Ă  rĂ©server aux bases de test) :

```json
"env": {
  "ODOO_URL": "https://acme.odoo.com",
  "ODOO_USERNAME": "vous@acme.com",
  "ODOO_API_KEY": "votre-clé"
}
```
</details>

## 🧰 Les 37 outils

Huit familles, une progression logique : se connecter → lire → explorer → Ă©crire (sous
garde) → Ă©changer des fichiers → prĂ©parer une dĂ©mo → visualiser → rendre des comptes.

```mermaid
flowchart LR
    R(["🔌 odoo-mcp<br/>37 outils"])
    R --> A["🔌 Connexion · 3"]
    R --> B["📖 Lecture · 7"]
    R --> C["🔎 SchĂ©ma · 4"]
    R --> D["✍ Écriture · 6"]
    R --> E["📁 Fichiers · 3"]
    R --> F["🎭 DĂ©monstration · 4"]
    R --> G["📊 Tableaux de bord · 4"]
    R --> H["đŸ§Ÿ TraçabilitĂ© · 6"]

    classDef root fill:#0f2b46,stroke:#0f2b46,color:#fff;
    classDef cat fill:#eef2ff,stroke:#6366f1,color:#1e1b4b;
    class R root; class A,B,C,D,E,F,G,H cat;
```

<table>
<tr><td valign="top" width="50%">

**🔌 Connexion**

| Outil | RĂŽle |
|---|---|
| `odoo_connect` | Se connecter (URL + login + clĂ©) — en mĂ©moire |
| `odoo_status` | État connexion, version serveur, mode |
| `odoo_enable_write` | Activer/couper l'écriture |

**📖 Lecture**

| Outil | RĂŽle |
|---|---|
| `odoo_models` | Lister les modĂšles |
| `odoo_fields` | Décrire les champs (`writable_only`) |
| `odoo_search` | Recherche + lecture (domaine, tri, page) |
| `odoo_count` | Comptage |
| `odoo_read` | Lecture par identifiants |
| `odoo_name_find` | Retrouver un enreg. par son nom |
| `odoo_aggregate` | Sommes/comptes par groupe, par date |

**🔎 Exploration du schĂ©ma** *(cache disque)*

| Outil | RĂŽle |
|---|---|
| `odoo_find_models` | Chercher un modÚle par mot-clé |
| `odoo_find_fields` | Chercher un champ par mot-clé |
| `odoo_fields_batch` | Champs de plusieurs modĂšles d'un coup |
| `odoo_schema_status` | État du cache ; `refresh=true` |

**📁 Fichiers**

| Outil | RĂŽle |
|---|---|
| `odoo_import_file` | Importer .xlsx/.csv (`inspect`/`check`/`run`) |
| `odoo_export_file` | Exporter vers .xlsx/.csv (`ecraser=True`) |
| `odoo_get_attachment` | Télécharger une piÚce jointe |

</td><td valign="top" width="50%">

**✍ Écriture** *(exige `odoo_enable_write`)*

| Outil | RĂŽle |
|---|---|
| `odoo_create` | Création |
| `odoo_write` | Modification par identifiants |
| `odoo_update_where` | Modif. de masse **prévisualisée** |
| `odoo_unlink` | Suppression (confirmation > 50) |
| `odoo_upsert` | Créer-ou-mettre-à-jour par External ID |
| `odoo_execute` | Appel brut `execute_kw` (liste blanche) |

**🎭 DĂ©monstration & avant-vente**

| Outil | RĂŽle |
|---|---|
| `odoo_demo_questionnaire` | Trame de qualification avant démo |
| `odoo_demo_generate` | Générer un jeu de démo depuis une recette compacte |
| `odoo_demo_mode` | Filet : neutralise toute adresse e-mail |
| `odoo_demo_check` | Audite et corrige les adresses réelles |

**📊 Tableaux de bord**

| Outil | RĂŽle |
|---|---|
| `odoo_dashboard_list` | Inventaire des tableaux de bord |
| `odoo_dashboard_inspect` | Décrire en clair un tableau de bord |
| `odoo_dashboard_create` | Créer un tableau de graphiques live |
| `odoo_saved_analysis` | Enregistrer une analyse réutilisable |

**đŸ§Ÿ TraçabilitĂ© & reporting**

| Outil | RĂŽle |
|---|---|
| `odoo_journal_start` | Ouvrir un journal (titre + objectif) |
| `odoo_journal_chapter` | Ouvrir une étape + sa justification |
| `odoo_journal_note` | Consigner décision / observation / alerte |
| `odoo_journal_report` | Générer le rapport (HTML / Markdown) |
| `odoo_presentation_guide` | Générer le déroulé de démo client |
| `odoo_recent_changes` | Ce qui a bougé (audit natif Odoo) |

</td></tr>
</table>

## 📋 Le rapport d'intervention

Quand c'est l'assistant qui construit un flux entier, retrouver aprÚs coup ce qui a été fait
— et l'expliquer à un client — devient vite impossible à partir du seul historique de
conversation. Le serveur étant le point de passage obligé de toute écriture, il journalise
tout automatiquement.

```
odoo_journal_start("Maquette de démonstration", "Traduire le flux affaires dans Odoo")
odoo_journal_chapter("Référentiel articles", "Aucun catalogue n'existait : prérequis
                                              pour bloquer les achats hors contrat")
   → les Ă©critures suivantes sont tracĂ©es, avec leur Ă©tat avant/aprĂšs
odoo_journal_note("Le stock reste hors périmÚtre (décision actée en réunion)", "decision")
odoo_journal_report(format="both")
```

Le rapport HTML est autonome (aucune ressource externe), présentable tel quel ou imprimable
en PDF. Il contient la synthÚse chiffrée, les volumes par type d'information, le déroulé
chronologique par Ă©tape, et pour chaque modification le dĂ©tail `avant → aprĂšs`. Les
suppressions y apparaissent avec **le nom de ce qui a disparu** et un marquage
« irréversible ».

> [!TIP]
> **Le rapport parle français, pas Odoo.** Les noms techniques sont traduits en langage
> courant — `res.partner` devient « Contacts (clients, fournisseurs) », `sale.order` devient
> « Devis et commandes clients » — et chaque type d'information est accompagnĂ© de l'endroit oĂč
> le trouver dans l'interface. Un dirigeant qui n'a jamais ouvert Odoo comprend le document.

### À quoi ressemble un rapport

> *Extrait reconstitué et anonymisé d'un rapport réel. Le document livré est une page HTML
> autonome et mise en forme (en-tĂȘte, cartes de synthĂšse, code couleur) — voici son contenu.*

<table>
<tr><td>

**RAPPORT D'INTERVENTION ODOO**
### Mise en place d'un jeu de démonstration
Alimenter la base avec un jeu de données de démonstration complet et varié.
`Base ‱‱‱‱‱‱` · `Utilisateur ‱‱‱‱‱‱` · le 27/07/2026

</td></tr>
</table>

**Ce qui a été fait**

| Enregistrements touchés | Opérations | Import de données | Création |
|:---:|:---:|:---:|:---:|
| **325** | **9** | **323** | **2** |

| Ce qui a Ă©tĂ© touchĂ© | OĂč le trouver | DĂ©tail | Total |
|---|---|---|---:|
| Contacts (clients, fournisseurs) | Contacts | import de données | **40** |
| SalariĂ©s | EmployĂ©s → EmployĂ©s | import de donnĂ©es | **40** |
| Articles | Ventes → Articles → Articles | import de donnĂ©es | **40** |
| Planning des Ă©quipes | Planning → Planning | import de donnĂ©es | **195** |
| Taxes | ComptabilitĂ© → Configuration → Taxes | crĂ©ation | **1** |

**Déroulé détaillé** *(extrait)*

> `10:36` · **Import de donnĂ©es** — 40 · *Contacts (clients, fournisseurs)*
> *Pourquoi : ajout de personnes physiques avec leurs coordonnées professionnelles (poste, email, téléphone).*
>
> `10:37` · **Import de donnĂ©es** — 40 · *SalariĂ©s*
> *Pourquoi : alimenter le module Planning avec les employés, les rÎles métier et un planning complet sur 5 jours.*
>
> `10:48` · **CrĂ©ation** — 1 · *Taxes*
> *Pourquoi : appliquer la TVA réduite sur le catalogue et enrichir les descriptions de vente.*

Chaque ligne est passĂ©e par le connecteur MCP et a Ă©tĂ© journalisĂ©e automatiquement — rien
n'est saisi Ă  la main dans le rapport.

## 🎬 Le guide de prĂ©sentation

`odoo_presentation_guide` produit le **déroulé à suivre en réunion client**, écran par écran,
déduit de ce qui a réellement été fait : seules les étapes correspondant aux données mises
en place apparaissent, dans l'ordre naturel du mĂ©tier (contacts → catalogue → devis →
livraison → facture → rentabilitĂ©).

Chaque étape donne le chemin de menu exact, les clics à faire sous forme de **cases à
cocher**, les enregistrements précis à ouvrir, et une phrase d'accroche à dire au client.
Le fichier HTML s'ouvre pendant la réunion : on coche au fur et à mesure, on n'oublie
aucune étape, et on garde le fil du discours.

```
### Étape 3 — Du devis à la commande client

OĂč aller : Ventes → Commandes → Devis

[ ] Ouvrir un devis de la démonstration
[ ] Montrer les lignes : articles, quantités, prix
[ ] Expliquer le bouton « Confirmer » : le devis devient une commande ferme

> À dire : C'est le point de bascule — un clic sur « Confirmer », et le reste
  de la chaĂźne se met en route tout seul.
```

Les journaux sont écrits en JSONL dans `~/odoo-mcp-journaux/` (une ligne par opération,
lisible et diffable), et un rapport peut ĂȘtre regĂ©nĂ©rĂ© plus tard Ă  partir d'un journal
ancien via `journal_path`.

`odoo_recent_changes` complĂšte le dispositif : il interroge les champs d'audit d'Odoo
(`write_date`, `write_uid`), donc il voit aussi les modifications faites directement dans
l'interface par d'autres personnes.

## đŸ“„ Importer un fichier

Trois modes à enchaßner, qui évitent d'écrire n'importe quoi dans la base :

```mermaid
flowchart LR
    F["📄 Fichier<br/>.xlsx / .csv"]
    I["🔍 inspect<br/>structure et doublons<br/>hors ligne"]
    C["✅ check<br/>validation champ par champ<br/>sans Ă©crire"]
    R["🚀 run<br/>import par lots<br/>via load()"]
    O[("đŸ—„ïž Odoo")]
    F --> I --> C --> R --> O

    classDef step fill:#f0f9ff,stroke:#0ea5e9,color:#082f49;
    classDef odoo fill:#f3e8ff,stroke:#a855f7,color:#3b0764;
    class F,I,C,R step; class O odoo;
```

1. **`inspect`** — structure du fichier : colonnes, taux de remplissage, valeurs distinctes,
   doublons d'identifiant. Aucune connexion nécessaire.
2. **`check`** — construit les lignes et vĂ©rifie chaque champ contre le modĂšle, sans rien
   écrire.
3. **`run`** — importe par lots via `load()`, l'import natif d'Odoo.

Le `mapping` relie les colonnes du fichier aux champs Odoo. Il est indispensable : les
en-tĂȘtes des fichiers exportĂ©s depuis Odoo sont des libellĂ©s d'interface (`Name*`,
`Sales Price`), jamais des noms de champs.

```json
{
  "_columns": {
    "Code":  "id",
    "Nom":   "name",
    "Pays":  "country_id/id",
    "Notes": null
  },
  "_constants": { "is_company": "True" },
  "_replace":   { "type": { "Goods": "consu" } }
}
```

Mapper une colonne sur `id` (External ID) rend l'import **rejouable** : une seconde exécution
met Ă  jour au lieu de dupliquer. Et `load()` rejette un lot entier en cas d'erreur — un Ă©chec
ne laisse jamais de données à moitié écrites.

## 🎭 PrĂ©parer une dĂ©monstration

Deux usages coexistent dans cet outil. Le **consultant** intervient sur des données
rĂ©elles : il lui faut de la traçabilitĂ©, d'oĂč le journal et le rapport. L'**avant-vente**
construit des données fictives pour un prospect : il lui faut de la vraisemblance, vite.

Pour ce second cas, `odoo_demo_questionnaire` fournit une trame de qualification en huit
sections — le mĂ©tier, ce qu'il vend, ses achats, son pilotage, sa facturation, **les
spécificités venant de ses propres clients**, son vocabulaire maison, et le problÚme qu'il
cherche à résoudre. C'est ce dernier point qui fait la différence entre une démonstration
gĂ©nĂ©rique et une dĂ©monstration oĂč le prospect se reconnaĂźt.

La composition de la maquette n'est pas codée dans le serveur : c'est l'assistant qui
l'écrit à partir des réponses. Aucun catalogue figé ne produira des noms d'articles et un
vocabulaire aussi justes qu'un modùle de langage — et surtout, cela fonctionne pour un
métier qu'on n'avait pas prévu.

### Peupler sans exploser les tokens

Écrire 40 fiches complĂštes coĂ»te cher : Ă  chaque ligne, le modĂšle réécrit l'e-mail, la
rĂ©fĂ©rence, la TVA
 des champs identiques et sans intĂ©rĂȘt mĂ©tier. `odoo_demo_generate` sĂ©pare
les deux : **le modÚle fournit le vocabulaire, le serveur déroule la plomberie.**

```json
{
  "model": "product.template",
  "gabarit":  { "type": "consu" },
  "sequence": { "default_code": "ART-{i:03}" },
  "id_prefixe": "art",
  "lignes": [
    { "name": "Article vitrine A", "list_price": 2.90 },
    { "name": "Coffret découverte", "list_price": 15.90 }
  ]
}
```

Le modÚle n'écrit que `lignes` (le vocabulaire, spécifique au métier) ; le serveur ajoute les
constantes (`gabarit`), les champs séquentiels (`{i}`, `{i:03}`, `{autre_champ}`), un e-mail
dérivé du nom (`email_depuis`) et un External ID rejouable (`id_prefixe`), puis importe par
lots via `load()`. **~20 lignes de vocabulaire au lieu de ~200 lignes de fiches**, sans perdre
en pertinence — la base reste taillĂ©e pour *ce* mĂ©tier.

> [!NOTE]
> Ce n'est **pas** de la génération de contenu figée : le serveur ne multiplie que les champs
> répétitifs. Deux modes, comme l'import : `check` (valide la recette sans écrire) puis `run`
> (importe ; exige `odoo_enable_write`). Le filet e-mail et le journal s'appliquent
> automatiquement, puisque tout passe par `load()`.

### Le filet e-mail, non négociable

> [!WARNING]
> Une base de démonstration n'est presque jamais neutralisée : Odoo y envoie de vrais
> courriels dÚs qu'on confirme une commande ou une facture. **Une adresse réelle dans un jeu
> fictif, et une vraie entreprise reçoit une fausse facture.**

```
odoo_demo_mode(actif=true)
```

Une fois activĂ©, **toute** adresse Ă©crite est réécrite vers `example.com` — domaine rĂ©servĂ©
par la RFC 2606, qui ne peut appartenir à personne. La garantie est posée au seul endroit
par lequel passent toutes les écritures, elle tient donc quel que soit l'outil utilisé :
crĂ©ation, modification, upsert, import de fichier, ou mĂȘme appel brut `odoo_execute`. Elle
couvre aussi les lignes imbriquées (commandes one2many/many2many) et les duplications
(`copy`), et **chaque** adresse d'un champ multi-destinataires est vérifiée, pas seulement
la derniÚre. Le domaine de remplacement n'est pas paramétrable : seuls les domaines réservés
(`example.com` & co.) sont acceptés. La partie gauche de l'adresse est conservée, donc
`jean.dupont@example.com` reste lisible à l'écran pendant la démonstration.

`odoo_demo_check` balaie une base reprise de quelqu'un d'autre et signale — ou corrige — les
adresses qui pourraient encore recevoir du courrier : contacts, employés, pistes
(`crm.lead`) et utilisateurs.

## 📊 GĂ©nĂ©rer un tableau de bord

Les graphiques produits sont **recalculĂ©s par Odoo Ă  chaque ouverture** — ce ne sont ni des
images ni des valeurs figées. Chacun porte sa source, son regroupement, sa mesure et son
filtre :

```json
[
  {"titre": "Chiffre d'affaires par mois", "model": "sale.order",
   "groupby": ["date_order:month"], "mesure": "amount_untaxed",
   "type": "line", "domaine": [["state","=","sale"]], "pleine_largeur": true},
  {"titre": "Commandes par vendeur", "model": "sale.order",
   "groupby": ["user_id"], "mesure": "__count", "type": "bar"}
]
```

Trois types : `bar` (comparer), `line` (suivre dans le temps), `pie` (répartition). Sur un
champ date, la granularitĂ© s'Ă©crit `champ:month` — aussi `day`, `week`, `quarter`, `year`.
`__count` compte au lieu de sommer.

Le modÚle, les champs de regroupement et le type de la mesure sont **vérifiés avant
écriture**, et la vérification fonctionne en lecture seule : on peut valider une maquette
avant de demander l'autorisation d'écrire. C'est important, car un tableau de bord qui
référence un champ inexistant s'ouvre vide, sans message d'erreur.

> [!NOTE]
> **[SPREADSHEETS.md](SPREADSHEETS.md) explique en détail** comment fonctionnent les
> tableurs Odoo : le format o-spreadsheet, les quatre façons de connecter des données et leur
> robustesse respective, le choix de la bonne source (`sale.report` plutît que `sale.order`
),
> et ce que ce connecteur ne génÚre volontairement pas.

## ⚡ Économie de contexte

Le serveur rĂ©gule lui-mĂȘme ce qu'il renvoie, sans perdre l'accĂšs Ă  la donnĂ©e.

**Il rogne, il ne refuse jamais.** Une réponse trop volumineuse est réduite progressivement ;
les lignes rendues restent complÚtes, les métadonnées sont préservées, et la réponse indique
toujours combien de lignes sur combien et l'`offset` pour la suite. Une troncature silencieuse
mĂšnerait Ă  des conclusions fausses — c'est pire qu'une rĂ©ponse longue.

**Il dépose l'intégralité sur disque.** Quand un résultat dépasse le plafond, le jeu complet
est écrit dans `~/odoo-mcp-resultats/` et le chemin est renvoyé. L'assistant le relit avec
son propre outil de lecture, sans repasser par Odoo : quelques dizaines de tokens au lieu de
dizaines de milliers. Ces fichiers sont purgés automatiquement (au-delà de 7 jours ou de
50 fichiers).

**Il se resserre Ă  mesure que la session avance.**

| Palier | Déclenché à | Plafond | Champs par défaut | Limite par défaut |
|---|---|---|---|---|
| confort | départ | 60 k car | 12 | 50 |
| économie | 200 k rendus | 30 k | 8 | 30 |
| strict | 500 k | 15 k | 6 | 20 |

**Les défauts sont sobres.** `odoo_search` sans `fields` choisissait autrefois *tous* les
champs — 169 000 tokens pour dix contacts. Il retient dĂ©sormais une douzaine de champs utiles
et écarte les types lourds. `odoo_fields` renvoie une forme compacte
(`"partner_id":"many2one>res.partner*"`) au lieu de 20 000 tokens de détail.

**Le schéma est mémorisé sur disque.** La premiÚre exploration d'une base (liste des modÚles,
`fields_get` modÚle par modÚle) est ce qui coûte le plus cher à chaque session. ModÚles et
champs sont donc conservés dans `~/odoo-mcp-cache/`, par base, et remplis à l'usage : un
modÚle déjà consulté n'est jamais redemandé à Odoo. Le cache est vérifié une fois par session
(version serveur + modules installés) et invalidé tout seul s'il ne correspond plus ;
`odoo_schema_status(refresh=true)` force le réexamen aprÚs une (dés)installation de modules.
Pour chercher sans tout lister : `odoo_find_models` et `odoo_find_fields` filtrent cÎté
serveur, et `odoo_fields_batch` décrit plusieurs modÚles en un seul aller-retour.

L'état de consommation est visible à tout moment dans `odoo_status`. Si le coût permanent des
dĂ©finitions d'outils gĂȘne, le levier restant est de dĂ©sactiver dans votre client MCP ceux
dont vous ne vous servez pas.

## 🧭 Notes de terrain

- Inspecter les champs (`odoo_fields`) **avant** d'écrire : les noms changent entre versions
  d'Odoo (en 19, `is_company` existe mais pas `company_type` ; `groups_id` est devenu
  `group_ids` ; le contenu d'une piÚce jointe est passé de `datas` à `raw`).
- Préférer `odoo_upsert` à `odoo_create` pour toute donnée de maquette ou d'import.
- Un échec d'écriture sur un champ non fourni (ex. `credit_limit` en créant un contact) est un
  problÚme de **droits Odoo**, pas de données.
- Les relations one2many (ex. `seller_ids`) **s'accumulent** à chaque écriture au lieu de se
  remplacer — purger avant de rejouer un chargement.
- Archiver (`{"active": false}`) est presque toujours préférable à supprimer : Odoo n'a pas de
  corbeille.

## 📄 Licence

PubliĂ© sous licence **MIT** — voir [LICENSE](LICENSE). Auteur : Damien Dechamps.

TDQS

B3.3/5.0

Scored across 32 tools

Disambiguation4/5

Most tools have clearly distinct purposes: search, read, count, aggregate, name_find, create, write, unlink, upsert, etc. are all separate operations. The main ambiguity is odoo_execute, which is a generic method caller that could overlap with many others, and odoo_write vs odoo_update_where, though the descriptions clarify the difference (by IDs vs by domain).

Naming Consistency4/5

All tools share the odoo_ prefix, which creates a strong brand, but the second part mixes nouns (models, fields, status), verbs (search, read, create), and verb-noun compounds (enable_write, update_where, name_find, get_attachment). Some names like odoo_name_find are oddly ordered, but overall the pattern is predictable and readable.

Tool Count3/5

With 32 tools, the server is on the heavier side, but the scope is broad: it covers ORM operations, imports/exports, attachments, journaling, demo features, and dashboards. Each tool appears to serve a distinct function, so the count feels justifiable for an Odoo integration, even if it exceeds the typical well-scoped range.

Completeness5/5

The tool surface is remarkably complete: it includes connection management, model/field introspection, search/read/count/aggregate, create/write/unlink/upsert, bulk updates, file import/export, attachment retrieval, a journaling system, demo preparation, and dashboard creation/inspection. There are no obvious dead ends for common Odoo workflows, and odoo_execute covers any missing long-tail operations.

Maintenance

ActivitySlowing
ResponsivenessNo issues