Pour les entreprises Pour les grandes entreprises Solutions Applications Tarifs Développeurs Blog Documentation Lancer un espace de travail
Blog / MCP pour les logiciels d'entreprise

MCP pour ERP : un guide pratique

Un guide pour les développeurs. Il suppose que vous avez lu la spécification ou l'article compagnon sur ce qu'est un serveur ERP MCP, et il se concentre sur les décisions que la spécification vous laisse : quelles actions publier, comment les nommer, comment l'identité de l'appelant atteint chaque appel, et ce qu'une écriture doit faire lorsqu'un modèle la réessaie. L'outil d'exemple est un outil réel.

7 min de lectureMis à jour 4 septembre 2026Ingénierie Sois, l'équipe qui construit la plateforme

Un banc de travail dans un petit studio d'ingénierie : tiroirs de pièces étiquetés, un schéma imprimé sous un clip, un ordinateur portable fermé et une lampe à souder éteinte.
Réponse courte

Construire MCP pour ERP se résume à quatre décisions. Exposez les transactions, pas les tables : un outil doit être quelque chose qu'une personne pourrait faire dans le système, comme créer une facture ou enregistrer un paiement, avec les règles commerciales à l'intérieur. Nommez et décrivez chaque outil pour le modèle qui lira la liste, avec les contraintes qu'il doit respecter dans la description plutôt que dans une documentation qu'il ne verra jamais. Laissez l'identité OAuth de chaque demande décider à la fois quels outils sont listés et si chaque appel s'exécute. Et concevez chaque écriture de manière à ce qu'une nouvelle tentative, un refus ou une question soit sans risque, car un modèle produira les trois.

Le transport, la découverte et la connexion sont spécifiés et tout SDK les gère. La valeur du serveur réside dans ces quatre décisions, et un système commercial qui les réussit est opérable par Claude, ChatGPT ou tout autre client sans que ce client n'en sache rien.

Commencez par la transaction, pas par la table

Le premier instinct lors de l'exposition d'un ERP est de générer un outil par table avec créer, lire, mettre à jour et supprimer sur chacune. Cela produit une grande liste uniforme que le modèle gère mal, car la règle commerciale selon laquelle une facture a besoin d'un taux de taxe sur chaque ligne, ou que le stock ne peut pas être expédié avant d'être réservé, n'existe nulle part où le modèle peut la voir. Exposez plutôt les actions. Un test utile est de savoir si une personne pourrait décrire l'outil comme quelque chose qu'elle a fait aujourd'hui : émettre une facture, enregistrer un paiement, déplacer un accord, mettre en veille une relance. Chacune de ces actions a ses règles, valide ses entrées et renvoie un résultat lisible.

En parallèle des actions, ajoutez un petit nombre d'outils de résumé qui répondent aux questions qu'un modèle pose avant d'agir. Un appel qui renvoie le profil d'un compte, sa santé, ses éléments ouverts et son historique récent permet d'économiser au modèle quatre appels et plusieurs milliers de tokens de contexte, et cela rend la prochaine action mieux informée. Sois appelle ces outils des outils d'examen; examinerContact et obtenirRésuméComptable sont deux. Gardez également la surface totale à l'esprit : Claude Code limite la sortie d'un serveur par appel par défaut, et à la fois Claude et OpenAI offrent un chargement différé ou une recherche d'outils pour de grandes listes, donc un serveur avec plusieurs centaines d'outils devrait les retourner dans un ordre déterministe (la spécification le demande afin que les clients puissent mettre en cache) et devrait filtrer par rôle avant de lister.

Nommer les outils afin qu'un modèle choisisse le bon

La spécification limite légèrement les noms : un maximum de 128 caractères, lettres, chiffres, underscore, tiret et point, sensible à la casse, unique au sein du serveur. Tout le reste est une convention, et la convention qui fonctionne est un verbe suivi du nom commercial dans une casse cohérente, avec les mêmes verbes signifiant les mêmes choses partout. Un modèle choisissant entre rechercherFactures, obtenirFacture et Créer une facture choisit entre une liste, un enregistrement et une écriture, et il apprend ce modèle une fois pour l'ensemble du serveur.

FaibleMieuxPourquoi
factureCréer une factureUn nom seul ne dit pas s'il lit ou écrit ; un client ne peut pas l'annoter et un modèle ne peut pas le classer par rapport à ses frères et sœurs.
factureCréerV2FinalCréer une factureLa version et le statut appartiennent au serveur, pas au nom. Les noms qui changent cassent les listes d'outils mises en cache et les caches de prompt.
faire de la comptabilitéenregistrerPaiement, envoyerRappelsDeFactureUn outil générique avec un argument de mode cache la transaction. Un nom par transaction permet au client d'appliquer une confirmation par outil.
get_invoice et getContact mixteUn cas uniqueLes clients agrègent les noms par serveur ; la cohérence à l'intérieur d'un serveur est ce sur quoi le modèle s'appuie.

La description porte le reste : quand utiliser l'outil, quand ne pas l'utiliser, et toute règle que le modèle doit respecter avant de l'appeler.

Les descriptions sont lues par un modèle sous pression d'agir, donc écrivez-les comme des instructions. Indiquez ce que fait l'outil dans la première phrase, puis les conditions. Si un outil frère est le bon choix pour une demande voisine, indiquez-le par son nom. Si un champ doit être défini pour que le résultat soit correct, dites-le en MAJUSCULES si nécessaire ; l'outil de facturation de Sois indique au modèle que le taux de taxe doit être défini sur chaque ligne et que les taux exacts proviennent de listTaxTypes, because an invoice with no VAT is a worse failure than a refused call. Include one example call. Everything the model needs to call the tool correctly should be in the tool, because it will never open your documentation.

Une définition d'outil exemple

Ceci est un outil de facturation Sois tel qu'un client le reçoit de tools/list , réduit aux champs qui comptent, avec des annotations et un schéma de sortie ajoutés sous la forme définie par la spécification actuelle. Cela montre le modèle : un nom verbe-nom, une description d'instruction, un schéma dont les descriptions de propriété préviennent les erreurs du modèle, et des indices qu'un client peut utiliser pour décider s'il doit confirmer.

{
  "name": "createInvoice",
  "title": "Create invoice",
  "description": "Create a new invoice of any type and return the draft with its auto-generated number. TAX: set tax_rate on each line (for example 20 for 20% VAT); call listTaxTypes for this workspace's exact rates. If the user says 'plus VAT' you MUST set tax_rate or the invoice goes out with no VAT. To email the result use sendInvoice. Example: createInvoice({ type: \"sales_invoice\", contact_id: \"uuid\", currency: \"GBP\", lines: [{ description: \"Consulting\", quantity: 10, unit_price: 150 }] })",
  "inputSchema": {
    "type": "object",
    "properties": {
      "type": { "type": "string", "description": "sales_invoice, purchase_invoice, sales_credit_note or purchase_credit_note" },
      "contact_id": { "type": "string", "description": "Contact UUID (bill-to for sales, bill-from for purchases)" },
      "currency": { "type": "string", "description": "ISO code, for example GBP. Uses the workspace default if omitted" },
      "invoice_date": { "type": "string", "description": "ISO date. Defaults to today" },
      "reference": { "type": "string" },
      "lines": {
        "type": "array",
        "description": "Line items. Every line MUST carry the numeric unit_price the user asked for",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "quantity": { "type": "number", "description": "Defaults to 1" },
            "unit_price": { "type": "number", "description": "NUMBER only: no currency symbols, no thousands separators. Use the exact amount stated; never guess or round" },
            "tax_rate": { "type": "number" },
            "discount_percent": { "type": "number" }
          },
          "required": ["description", "unit_price"]
        }
      }
    },
    "required": ["type"]
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "invoice_id": { "type": "string" },
      "number": { "type": "string" },
      "status": { "type": "string" },
      "total": { "type": "number" }
    },
    "required": ["invoice_id", "number", "status"]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": false,
    "idempotentHint": false,
    "openWorldHint": false
  }
}

Les annotations disent : cela écrit, cela ajoute seulement (un brouillon), l'appeler deux fois crée deux brouillons, et cela ne touche rien en dehors du système. Les clients doivent traiter les annotations comme non fiables à moins que le serveur ne soit fiable, donc ce sont des indices pour le comportement de confirmation, pas un substitut aux propres vérifications du serveur.

Trois choix dans cette définition sont délibérés. Le résultat renvoie un identifiant que le modèle doit transporter dans sendInvoice et enregistrerPaiement, which is the specification's recommended way to relate calls now that servers hold no session state. The tool creates a draft, not a posted invoice, so the write is additive and a person or a separate approval tool finalises it. And the output schema means an integration can read the number and total as data while the model reads the same result as text.

Adapter les outils à l'utilisateur

Sur HTTP, l'appelant arrive avec un jeton d'accès OAuth lié à votre serveur, et ce jeton identifie une personne. La spécification permet que le résultat de tools/list de varier en fonction des identifiants de la demande, donc la première décision de portée est de filtrer la liste en fonction du rôle de cette personne avant de la retourner : un utilisateur d'entrepôt ne reçoit pas approuver la factureLa liste ne doit pas varier selon la connexion ou en raison d'autres appels, uniquement par autorisation, ce qui la rend mise en cache.

La deuxième décision est de vérifier à nouveau lors de l'exécution. Un client peut envoyer n'importe quel appel qu'il souhaite, et un modèle peut être manipulé par du texte dans un résultat d'outil pour en essayer un. Résolvez l'utilisateur à partir du jeton à chaque appel, vérifiez la permission requise par l'outil, et refusez avec une erreur d'exécution d'outil que le modèle peut lire. Gardez les portées OAuth larges (Sois émet des portées de lecture, d'écriture et hors ligne) et laissez les propres rôles de l'ERP être la frontière fine, car ces rôles existent déjà, sont déjà maintenus et signifient déjà quelque chose pour l'entreprise. Attribuez chaque appel à la personne dans le journal avec ses arguments et son résultat, afin qu'un travail d'agent soit examinable exactement comme celui d'une personne.

  1. Le jeton arriveValidez la signature et que le public est ce serveur, comme l'exige la RFC 8707 ; rejetez tout autre avec 401.
  2. Résolvez la personneAssociez le jeton à un utilisateur dans l'espace de travail et chargez son rôle et les applications installées.
  3. Filtrer la listeRetournez uniquement les outils que ce rôle peut utiliser, dans un ordre stable, à partir de tools/list.
  4. Vérifiez l'appelSur tools/call, vérifiez à nouveau la permission et refusez avec isError si elle est manquante ; rien ne s'exécute.
  5. Exécutez et consignezExécutez la transaction, mesurez-la si votre propre agent a effectué le raisonnement, et écrivez l'appel dans le journal d'audit sous cette personne.

Gestion des écritures

Un modèle qui reçoit un résultat ambigu appellera à nouveau, et celui qui reçoit une erreur essaiera une entrée corrigée. Concevez cela. Les écritures qui créent doivent renvoyer un identifiant et, lorsque cela est possible, accepter une clé d'idempotence ou une clé naturelle afin qu'une répétition soit détectée. Les écritures qui changent d'état doivent être explicites sur la transition qu'elles effectuent et refuser celles qui sont impossibles avec une raison lisible : enregistrer un paiement contre une facture annulée est un isError résultat le disant, pas un non-op silencieux et pas une trace de pile. Ne laissez jamais une écriture partielle ; si un outil à plusieurs étapes ne peut pas se terminer, annulez et signalez.

  • Préférez les brouillons et les approbations. Rendez la création additive (un brouillon) et donnez à la finalisation son propre outil avec sa propre permission, de sorte que l'étape destructive soit celle qu'un client confirme et qu'un rôle contrôle.
  • Marquez les outils destructeurs. Définir destructiveHint sur les annulations et suppressions et le dire dans la description ; Claude et ChatGPT utilisent tous deux de tels signaux lorsqu'ils décident de demander avant d'appeler.
  • Demandez plutôt que de deviner. Lorsque un appel nécessite une décision que l'outil ne peut pas prendre, renvoyez un résultat nécessitant une entrée avec une demande d'élucidation ; le client pose la question à la personne et réessaie l'appel avec la réponse.
  • Limitez le rayon d'impact. Limitez le taux par connexion, plafonnez les dépenses par intégration où votre propre agent effectue un raisonnement, et validez chaque entrée côté serveur indépendamment du schéma, car le schéma est un conseil pour le modèle, pas une contrainte.

Tester la surface avec un vrai client

L'inspecteur MCP exercera tools/list et tools/call et suivre le flux OAuth. Le véritable test est un modèle. Connectez Claude en tant que connecteur personnalisé, ou ChatGPT en mode développeur, connectez-vous en tant qu'utilisateur avec un rôle restreint, et demandez un résultat routinier qui nécessite trois ou quatre outils. Observez quels outils il choisit et pourquoi ; un mauvais choix est presque toujours un problème de description. Ensuite, connectez-vous en tant qu'utilisateur sans l'une des autorisations et confirmez que l'exécution s'arrête à l'appel approprié avec une raison que le modèle répète.

Voici comment le serveur de l'espace de travail Sois est construit et vérifié : transactions en tant qu'outils, descriptions d'instructions, une liste filtrée par rôle, une seconde vérification sur chaque appel, des brouillons avant approbations, et un journal qu'une personne peut lire. Les développeurs construisant des applications pour le marché publient des outils dans la même liste selon les mêmes règles, de sorte qu'une application soit opérationnelle par n'importe quel agent dès qu'elle est installée. Le modèle n'est pas spécifique à un produit ; tout ERP qui l'adopte devient quelque chose qu'un agent peut exécuter.

Questions que les gens posent

Combien d'outils un serveur ERP MCP devrait-il exposer ?

Autant qu'il y a de transactions valant la peine d'être automatisées, filtrées par utilisateur afin que chaque appelant voie un ensemble fonctionnel. Plusieurs centaines est normal pour un système complet ; ce qui compte, c'est que la liste soit stable, filtrée par rôle, et organisée par des verbes cohérents afin que le modèle puisse classer les candidats.

Devrais-je utiliser des portées OAuth pour des autorisations fines ?

Utilisez des portées larges pour la connexion et les rôles propres à l'ERP pour la limite fine, vérifiés à chaque appel. Les rôles existent déjà et sont maintenus par l'entreprise ; un schéma de portée parallèle s'en éloignerait.

Comment un écriture doit-elle se comporter si le modèle l'appelle deux fois ?

Soit détectez la répétition par une clé d'idempotence ou naturelle et renvoyez l'enregistrement existant, soit rendez l'écriture additive et clairement signalée afin que le duplicata soit visible. Ne jamais échouer silencieusement et ne jamais laisser une écriture partielle.

Les annotations d'outil sont-elles appliquées par le client ?

Non. Ce sont des indices, et la spécification indique aux clients de les traiter comme non fiables à moins que le serveur ne soit fiable. Les clients les utilisent pour choisir le comportement de confirmation ; les propres vérifications de permission et de validation du serveur sont ce qui empêche les dommages.

Sources
  1. Spécification du Protocole de Contexte de Modèle (2026-07-28) : outils noms d'outils, schémas, annotations, résultats structurés, gestion des erreurs et directives de gestion d'état
  2. spécification du protocole de contexte de modèle : autorisation validation de l'audience du jeton, défis de portée et modèle d'autorisation par demande
  3. OpenAI Apps SDK : construire un serveur MCP comment ChatGPT utilise readOnlyHint, destructiveHint et openWorldHint pour le comportement de confirmation
  4. Documentation Sois : le serveur MCP de l'espace de travail la référence d'outil dont l'exemple est tiré, filtrage des rôles, limites et codes d'erreur

Cet article est révisé lorsque les produits qu'il décrit changent. Prochaine révision prévue : 4 décembre 2026.

Démarrer

Construisez sur Sois.

Connectez votre propre agent aux outils de construction, décrivez l'application, validez-la, publiez-la et gagnez de l'argent grâce à son utilisation.

  • Gratuit pour commencer
  • Apportez votre propre agent
  • Pas de verrouillage fournisseur