91 outils, un seul serveur : comment nous utilisons MCP pour gérer nos ventes, la facturation et l'inventaire depuis une seule interface IA

Six modules. Quatre types de backends. Deux transports. Un seul binaire. Voici comment nous avons connecté toute notre stack opérationnelle à un serveur MCP unique.

Mar 13, 2026

91 outils, un seul serveur

Les outils de vente sont partout. Les outils de gestion de projet aussi. La facturation, les bases de données produits, l'analyse de relevés bancaires — tout est partout, tout est séparé, et chaque outil demande un onglet différent, une connexion différente, un modèle mental différent.

On en a eu assez de jongler entre les interfaces. Alors on a construit un serveur MCP qui les connecte tous — et maintenant nos assistants IA gèrent les mises à jour CRM, la génération de factures, la gestion de tâches et la recherche de produits depuis une seule conversation.

Cet article détaille comment nous l'avons structuré, ce que fait chaque module, et ce que nous avons appris en faisant tourner 91 outils sur 6 domaines en production.


Qu'est-ce que MCP et pourquoi on l'utilise

Le Model Context Protocol est un standard pour connecter les modèles d'IA à des outils et données externes. Au lieu de construire des intégrations sur mesure pour chaque client IA (Claude Desktop, Claude Code, un bot Telegram, un agent web), vous construisez un serveur MCP qui expose des outils — et n'importe quel client compatible MCP peut les appeler.

Voyez-le comme un adaptateur universel entre l'IA et vos systèmes métier. L'IA dit « cherche les contacts dont le statut est en discussion », le serveur MCP traduit ça en une requête PostgreSQL et renvoie des résultats structurés. L'IA ne touche jamais directement votre base de données. Votre base de données n'a pas besoin de savoir que l'IA existe.

Nous utilisons deux transports : stdio pour Claude Desktop (local, pas de réseau) et Streamable HTTP pour les clients distants comme Claude Code, Cursor, notre bot Telegram et notre service de classification. Même serveur, mêmes outils, modes d'accès différents.


L'architecture : des modules, pas un monolithe

Le serveur repose sur une abstraction simple : les modules. Chaque module possède un domaine — CRM, gestion de projet, facturation — et expose des outils pour ce domaine. Les modules sont indépendants. Ils ne communiquent pas entre eux. Ils ne partagent rien sauf l'interface qu'ils implémentent :

type Module interface {
    Name() string
    GetTools() []Tool
    CallTool(ctx context.Context, toolName string, arguments map[string]any) (*ToolResult, error)
    Start(ctx context.Context) error
    Stop(ctx context.Context) error
}

Au démarrage, le serveur charge les modules activés via des variables d'environnement. Un registre d'outils indexe chaque outil par nom à travers tous les modules. Quand un appel arrive, le routeur identifie le module concerné et lui transmet la requête. Le client IA voit une liste plate de 91 outils — il n'a pas besoin de connaître les modules.

C'est important parce que les modules ont des backends complètement différents. Certains appellent des API REST. D'autres interrogent PostgreSQL. D'autres encore chargent des fichiers XLSX en mémoire. La frontière du module absorbe cette complexité. Ajouter une nouvelle source de données revient à écrire un nouveau module, sans toucher au code existant.

Architecture overview

Claude Desktopstdio proxyClaude CodeHTTPCursorHTTPTelegram BotHTTPClassifierHTTPMCP ServerAuth MiddlewareTool RegistryRouterCRM43 toolsERP23 toolsSheets7 toolsLinear6 toolsProducts6 toolsBank6 toolsPostgreSQLREST APISheets APIGraphQLLocal XLSXLocal XLSX

Ce que fait chaque module

CRM — 43 outils

Le module le plus lourd. Connecté directement à la base PostgreSQL de notre CRM de prospection LinkedIn — la même base que l'application web lit et écrit. Contacts, interactions, opportunités, relances, workflows, cohortes, messages suggérés, coaching commercial.

C'est le module que notre bot Telegram et notre classifieur IA utilisent le plus. Un commercial demande au bot « comment ça se passe avec Marcelo ? » et le module CRM gère tout le cycle : recherche du contact par nom, récupération des interactions récentes, vérification des relances en attente, renvoi d'un résumé que l'IA formate en message.

Les outils d'écriture supportent un mode dry-run — l'IA peut prévisualiser ce qu'une création ou mise à jour produirait sans rien persister. Notre classifieur utilise ça pour détecter les opérations d'écriture et demander confirmation à l'utilisateur avant de valider. La réponse inclut l'objet complet qui serait créé, pour que l'utilisateur voie exactement ce qu'il approuve.

Décision de conception clé : l'authentification multi-tenant. Le transport HTTP valide les clés API contre la base CRM. Chaque clé correspond à un utilisateur et une organisation. Chaque requête est automatiquement restreinte à l'organisation de l'appelant — l'IA ne peut pas accidentellement accéder aux données d'un autre tenant, même si elle essaie.

Linear — 6 outils

Gestion de projets et de tâches via l'API GraphQL de Linear. Créer des tâches, lister les projets, assigner aux membres de l'équipe, suivre les sprints. On l'utilise pour le travail d'ingénierie — le même assistant IA qui met à jour les contacts CRM peut aussi créer un ticket de bug quand un commercial signale une fonctionnalité cassée.

Le module fait un health check au démarrage pour vérifier la connectivité. Si l'API Linear est injoignable, le serveur refuse de démarrer plutôt que de laisser les appels d'outils échouer silencieusement. Fail loud, fail early.

Connecteur ERP — 23 outils

L'ERP de gestion du temps et de facturation d'un client. Membres, abonnements, suivi de présence, et un cycle de facturation complet — génération de factures, ajout et suppression de lignes, transitions de statuts (brouillon vers envoyé vers payé), opérations par lot sur les comptes.

C'est le module qui nous a convaincus que l'approche multi-module était la bonne. Le domaine d'un ERP n'a rien à voir avec le CRM ou la gestion de projet. Mais du point de vue de l'IA, « génère les factures de janvier pour le programme de l'après-midi » est juste un autre appel d'outil. Le module traduit ça en la bonne séquence d'appels API — authentification, récupération des abonnements, calcul des montants, création de la facture avec ses lignes — et renvoie un résumé.

Trois stratégies d'authentification, choisies par configuration : clé API statique (préférée), transmission de clé par requête (pour les architectures avec proxy), ou identifiant et mot de passe. Le module choisit la bonne au démarrage sans que l'IA ait besoin de s'en soucier.

Google Sheets — 7 outils

Recherche dans un catalogue produit via un tableur Google Sheets. Recherche par producteur, catégorie, région, ou texte libre sur toutes les colonnes. Récupération de lignes spécifiques ou par lot.

Ce module était notre premier prototype — avant d'avoir une vraie base de données, toutes nos données de référence vivaient dans un tableur. Le module fonctionne avec ou sans clé API Google : avec une clé, il utilise l'API Sheets ; sans, il se rabat sur l'export CSV (fonctionne pour les tableurs publics). Option zéro infrastructure pour démarrer.

Base de données produits — 6 outils

Similaire à Google Sheets mais adossé à un fichier XLSX local — une base de référence sectorielle avec des dizaines de milliers d'entrées produits. Chargé en mémoire au démarrage pour des recherches en millisecondes. Recherche par producteur, région ou texte libre.

On a construit ce module séparément de Google Sheets parce que la source de données est fondamentalement différente (fichier local vs. API distante), même si l'interface des outils est quasi identique. La frontière du module rend ça invisible pour l'IA — les deux exposent des outils « recherche produits » avec la même structure de résultat.

Relevés bancaires — 6 outils

Analyse de relevés bancaires XLSX en local. Recherche de transactions par libellé, montant, date ou référence. Liste des catégories. Récupération de lignes spécifiques.

Lecture seule, pas d'API externe, pas d'authentification. Le module le plus simple — mais il permet à l'IA de répondre « combien a-t-on dépensé en abonnements logiciels le trimestre dernier ? » en cherchant dans des données bancaires réelles, pas en devinant.


Ce qu'on a appris en production

1. Les listes plates d'outils fonctionnent mieux que les hiérarchies

On a envisagé de préfixer les outils par module — crm.search_contacts, linear.create_task. On a abandonné l'idée. Les modèles IA gèrent mieux les listes plates d'outils bien nommés que les structures imbriquées. crm_search_contacts est suffisamment explicite. Le modèle choisit le bon outil parmi 91 options avec une précision quasi parfaite, à condition que chaque outil ait une description claire en une ligne et des paramètres bien typés.

2. Le dry-run change tout pour la confiance

Dry-run confirmation flow

1

AI calls tool

dry_run: true

2

Server previews

No data written

3

User reviews

Confirm or cancel

4

AI commits

dry_run: false

La plus grande friction avec les opérations d'écriture pilotées par l'IA, ce n'est pas la précision — c'est la confiance. Les utilisateurs ne veulent pas qu'une IA crée des contacts ou génère des factures sans voir ce qu'elle s'apprête à faire. Le mode dry-run résout ça complètement. L'IA appelle l'outil avec dry_run: true, montre la prévisualisation à l'utilisateur, et ne valide qu'après confirmation explicite. Notre bot Telegram propose un clavier inline : « Créer ce contact ? [Confirmer] [Annuler] » avec un timeout de 5 minutes.

3. Les health checks au démarrage évitent les échecs silencieux

Chaque module qui se connecte à un service externe (API Linear, API ERP) lance un health check avant que le serveur accepte les requêtes. Si le check échoue, le serveur log l'erreur avec un indice (« Vérifiez ERP_BASE_URL et ERP_API_KEY ») et s'arrête. Ça semble évident, mais l'alternative — des outils qui renvoient des erreurs 401 incompréhensibles en pleine conversation — est bien pire.

4. Une seule connexion à la base, plusieurs consommateurs

Le module CRM et le middleware d'authentification partagent le même pool de connexions PostgreSQL. On l'ouvre une seule fois dans une fonction d'initialisation partagée et on le passe aux deux. Ça évite les connexions en double et garantit que les migrations s'exécutent une seule fois. Le module gère le close lifecycle.

5. Les modules ne doivent jamais communiquer entre eux

Les appels entre modules créent des dépendances cachées et rendent les tests pénibles. Si l'IA a besoin de données du CRM et de Linear dans la même conversation, elle appelle les deux outils séparément et synthétise les résultats elle-même. L'IA est la couche d'intégration — pas le serveur.

6. Le même serveur sert des clients très différents

Notre transport stdio sert Claude Desktop — local, mono-utilisateur, pas d'authentification nécessaire. Notre transport HTTP sert Claude Code, Cursor, le bot Telegram et le classifieur — distant, multi-utilisateur, authentifié par clé API. Même binaire, mêmes modules, mêmes outils. Seule la couche transport change. Ça nous a évité de construire et maintenir deux serveurs d'outils séparés.


Les chiffres

ModuleOutilsBackendAuthentification
CRM43PostgreSQLClé API (multi-tenant)
Connecteur ERP23API RESTClé API / mot de passe
Google Sheets7API Sheets / CSVClé API (optionnelle)
Linear6API GraphQLClé API
Base produits6XLSX localAucune
Relevés bancaires6XLSX localAucune
Total91

Tool distribution by module

CRM
43
ERP Connector
23
Google Sheets
7
Linear
6
Product DB
6
Bank Statements
6

91 tools total across 6 modules

Six modules. Quatre types de backends différents (SQL, REST, GraphQL, fichier local). Trois stratégies d'authentification. Deux protocoles de transport. Un seul binaire.


Ajouter un nouveau module

L'interface de module est volontairement minimale. Un nouveau module nécessite :

  1. Un constructeur qui prend ses dépendances (logger, client, configuration)
  2. GetTools() renvoyant les définitions d'outils avec noms, descriptions et schémas JSON
  3. CallTool() qui aiguille chaque appel vers la bonne fonction selon le nom de l'outil
  4. Optionnellement Start()/Stop() pour la gestion du cycle de vie

L'enregistrement se fait en un seul appel de fonction dans modules.go. Le registre d'outils, le routeur et la couche transport le prennent en charge automatiquement. Aucune modification des modules existants.

On est passé de 1 module à 6 en un an. Chacun a pris un jour ou deux à construire, client du service inclus. L'architecture modulaire rend ça prévisible plutôt qu'effrayant.


Connecter les clients : Claude Code vs. Claude Desktop

Tous les clients MCP ne se connectent pas de la même façon. C'est important à comprendre si vous construisez votre propre serveur, car ça affecte la gestion de l'authentification et du transport.

Claude Code et Cursor — HTTP direct

Claude Code et Cursor supportent Streamable HTTP nativement. Ils se connectent directement à l'endpoint HTTP de votre serveur — pas de proxy, pas de sous-processus. C'est la configuration la plus simple :

{
  "mcpServers": {
    "crm": {
      "type": "streamable-http",
      "url": "https://votre-serveur-mcp.example.com/mcp",
      "headers": {
        "Authorization": "Bearer votre_cle_api"
      }
    }
  }
}

Pour Claude Code, ça va dans .claude/settings.json (global) ou .mcp.json (par projet). Pour Cursor, c'est dans .cursor/mcp.json. La différence clé avec Claude Desktop : le client gère HTTP directement, donc le header Authorization voyage avec chaque requête. Votre serveur le valide, résout l'utilisateur et l'organisation, et restreint toutes les requêtes en conséquence.

Claude Desktop — proxy stdio

Claude Desktop ne parle que stdio — il lance un sous-processus et communique via stdin/stdout. Si votre serveur tourne à distance sur HTTP, vous avez besoin d'un petit binaire proxy qui fait le pont :

{
  "mcpServers": {
    "crm": {
      "command": "/chemin/vers/mcp-proxy",
      "env": {
        "CRM_URL": "https://votre-serveur-mcp.example.com",
        "CRM_API_KEY": "votre_cle_api"
      }
    }
  }
}

Le proxy lit du JSON-RPC depuis stdin, le transmet à votre endpoint HTTP avec la clé API, et écrit la réponse sur stdout. C'est ~100 lignes de Go. Sur macOS, il faut retirer le flag de quarantaine après le téléchargement : xattr -d com.apple.quarantine mcp-proxy.

Ce fichier va dans ~/Library/Application Support/Claude/claude_desktop_config.json sur macOS.

Ce que ça implique en pratique

La différence de transport est invisible pour vos modules — ils ne voient jamais comment la requête est arrivée. Mais ça compte pour le déploiement :

  • Clients HTTP (Claude Code, Cursor, bot Telegram) se connectent directement. L'authentification est un header. Vous pouvez ajouter de nouveaux clients sans toucher au serveur.
  • Clients stdio (Claude Desktop) ont besoin d'un binaire proxy par plateforme. Vous le compilez une fois, vous livrez les binaires pour macOS/Linux/Windows, et vous n'y pensez plus.

On livre des binaires proxy pré-compilés pour darwin-arm64, darwin-amd64, linux-amd64 et windows-amd64. La plupart de notre équipe utilise Claude Code au quotidien — le chemin HTTP sans friction de configuration.


Construire le vôtre : la stack

Si vous voulez construire quelque chose de similaire, voici ce qu'on a utilisé :

Le serveur est écrit en Go avec le SDK Go officiel du Model Context Protocol (github.com/modelcontextprotocol/go-sdk). Le SDK gère le framing JSON-RPC, la sérialisation des schémas d'outils et la négociation du transport — vous écrivez les modules, il gère le protocole.

Un serveur MCP minimal avec un seul outil ressemble à ça :

package main

import (
    "context"
    gomcp "github.com/modelcontextprotocol/go-sdk/server"
)

func main() {
    s := gomcp.NewServer(
        &gomcp.Implementation{Name: "my-server", Version: "1.0.0"},
        nil,
    )

    // Enregistrer un outil
    s.AddTool(gomcp.Tool{
        Name:        "hello",
        Description: "Say hello",
    }, func(ctx context.Context, args map[string]any) (*gomcp.ToolResult, error) {
        return gomcp.NewToolResultText("Hello from MCP!"), nil
    })

    // Démarrer avec le transport stdio
    s.Run(context.Background(), &gomcp.StdioTransport{})
}

Pour passer en HTTP, remplacez le transport par StreamableHTTPHandler et montez-le sur votre routeur HTTP. On utilise le même binaire pour les deux — une variable d'environnement décide du transport à activer.

Le binaire proxy pour Claude Desktop est tout aussi simple : lire stdin, POST vers l'endpoint HTTP, écrire stdout. Le StdioTransport du SDK Go fait l'essentiel du travail.


Pourquoi pas des serveurs séparés ?

On a envisagé de lancer un serveur MCP par domaine — un serveur CRM, un serveur Linear, un serveur ERP. Les arguments contre :

  • La charge opérationnelle. Six serveurs à déployer, surveiller et maintenir contre un seul. Pour une petite équipe, ça compte plus que la pureté architecturale.
  • L'authentification partagée. La base CRM stocke les clés API pour tous les modules. Avec des serveurs séparés, chacun a besoin de sa propre solution d'authentification ou d'un service partagé — qui est juste un serveur de plus à maintenir.
  • La simplicité côté client. Claude Code, Claude Desktop, Cursor et le bot Telegram se connectent chacun à un seul endpoint. Une connexion, une liste d'outils, un seul mode de défaillance. Ajouter un module ne nécessite pas de reconfigurer chaque client.
  • L'efficacité des ressources. Un processus, un pool de connexions, un logger. Les modules qui chargent des fichiers XLSX en mémoire partagent la mémoire du processus au lieu que chacun réclame la sienne.

Le compromis : si un module plante, il pourrait entraîner les autres. En pratique, ça n'est jamais arrivé — le CallTool de chaque module catch les panics en interne. Et si le serveur entier tombe, un seul redémarrage ramène tout.


La suite (et ce qu'il faut surveiller)

L'architecture est facile à étendre — nouveau module, même interface, même serveur. L'IA gagne de nouvelles capacités sans aucun changement dans sa façon de raisonner sur les outils existants. C'est l'avantage.

Mais il y a un vrai coût à surveiller. Chaque définition d'outil MCP — nom, description, schéma de paramètres — est chargée dans le contexte du modèle à chaque appel. À 91 outils, c'est un volume significatif de tokens avant même que la conversation ne commence. Ajoutez un septième module avec 15 outils et vous payez 106 définitions d'outils dans chaque requête, que l'utilisateur en ait besoin ou non.

On n'est pas encore au point de douleur, mais on le voit venir. Les options : le filtrage d'outils par client (le bot Telegram ne charge que les outils CRM + Linear, Claude Desktop a tout), le lazy loading (les modules annoncent des catégories, les schémas d'outils se chargent au premier usage), ou simplement être rigoureux sur les modules que chaque transport expose. Le pire scénario serait d'ajouter des outils que personne n'utilise et de gonfler silencieusement chaque appel API.

Plus d'outils, ce n'est pas toujours mieux. Chacun doit mériter son coût en tokens.


En résumé

MCP n'est pas magique — c'est de la plomberie. Mais une bonne plomberie permet à vos assistants IA de faire du vrai travail sur de vrais systèmes, pas seulement de répondre à des questions. L'architecture modulaire garde chaque domaine propre et indépendant tout en donnant à l'IA une interface unifiée vers tout.

Si vous assemblez plusieurs outils métier avec l'IA, demandez-vous si un serveur MCP unique multi-module simplifierait votre stack. Pour nous, passer de « six panneaux d'administration différents » à « une seule conversation » a été le déclic — surveillez juste la facture de tokens à mesure que la liste d'outils grandit.

Construit en Go avec le SDK Go officiel MCP, déployé en un seul binaire. Au service de Claude Code, Claude Desktop, Cursor, d'un bot Telegram et d'un classifieur — le tout avec les mêmes 91 outils.