Apogée Consult
Retour au blog
Jules Ginhac
Jules GinhacCo-Founder & ingénieur IA

Je suis Jules Ginhac, Co-Founder & ingénieur IA chez Apogée Consult à Lyon. Je conçois et déploie des architectures IA génératives (RAG, agents, LLMOps) pour des PME, startups et organisations publiques.

MCP en production : ce qu'on a appris après 4 mois d'usage

  • ia-produit

Quatre mois à utiliser le Model Context Protocol en production sur des projets réels. Ce qui tient la route, ce qui casse, et ce qu'on ne referait pas pareil.

MCP en production : ce qu'on a appris après 4 mois d'usage

En novembre dernier, Anthropic a ouvert le Model Context Protocol à un usage plus large. On l'a intégré sur deux projets différents : un agent interne d'assistance développeur et un agent client dans un produit SaaS. Quatre mois plus tard, voici un bilan honnête, pas un tutoriel d'introduction, mais un retour sur ce qui s'est passé en conditions réelles.

Ce que MCP change réellement

MCP est un protocole standardisé pour connecter des LLM à des sources de données et des outils externes. L'idée centrale : au lieu de construire une intégration ad hoc pour chaque outil (une fonction Python ici, un appel API là), on définit des serveurs MCP qui exposent des ressources et des tools de façon normalisée, et le modèle peut les utiliser via le protocole.

La vraie valeur n'est pas dans la magie, c'est dans la standardisation. Avant MCP, chaque projet avait son propre pattern d'intégration d'outils. Avec MCP, on a un contrat commun : le serveur déclare ses capacités, le modèle interroge cette déclaration et appelle les tools disponibles.

Ce que ça change concrètement : le modèle peut explorer dynamiquement les outils disponibles plutôt que de les avoir codés en dur dans le prompt. Et les serveurs MCP sont réutilisables entre projets.

Ce qui a bien fonctionné

Les serveurs MCP pour les sources de données internes

On a construit un serveur MCP qui expose notre base de documentation interne (Notion, fichiers Markdown, confluence) comme des ressources interrogeables. Le modèle peut lire des pages spécifiques, rechercher par mot-clé, et naviguer dans la hiérarchie documentaire.

C'est l'usage le plus stable qu'on ait eu. Le serveur est stateless, les ressources sont en lecture seule, et les erreurs sont faciles à debugger parce que le protocole est explicite sur ce qui a été demandé et ce qui a été retourné.

# Exemple de serveur MCP minimaliste pour une source documentaire
from mcp.server import Server
from mcp.server.models import InitializationOptions
import mcp.types as types

server = Server("doc-server")

@server.list_resources()
async def list_resources() -> list[types.Resource]:
    docs = await fetch_all_docs()
    return [
        types.Resource(
            uri=f"doc://{doc.id}",
            name=doc.title,
            description=doc.excerpt,
            mimeType="text/markdown"
        )
        for doc in docs
    ]

@server.read_resource()
async def read_resource(uri: str) -> str:
    doc_id = uri.replace("doc://", "")
    return await fetch_doc_content(doc_id)

La découverte dynamique des tools

Sur l'agent développeur interne, on a exposé les outils de CI/CD, de monitoring et de gestion de tickets comme des MCP tools. Le modèle peut lister les pipelines actifs, lire les logs d'une build, créer un ticket Jira, le tout dans la même conversation, sans qu'on ait eu à lui expliquer explicitement quels outils existent.

C'est là que MCP montre son intérêt sur un agent function calling classique : l'énumération des tools disponibles est dynamique. On peut ajouter un nouveau tool sur le serveur sans modifier le prompt.

Ce qui a posé problème

La gestion des erreurs n'est pas standardisée

MCP définit le protocole de communication, mais pas la sémantique des erreurs métier. Si un tool renvoie une erreur parce que l'utilisateur n'a pas les droits, ou parce que la ressource n'existe pas, ou parce que le service externe est down, le protocole permet de transmettre ces erreurs, mais le format est libre.

En pratique, on s'est retrouvés avec des erreurs que le modèle interprétait mal parce que le message d'erreur était trop technique ou trop vague. Le modèle retentait l'opération avec des paramètres légèrement différents au lieu de communiquer l'erreur à l'utilisateur.

On a résolu ça en standardisant nos messages d'erreur pour qu'ils soient explicitement lisibles par un LLM :

# Au lieu de :
raise Exception("DB connection timeout after 30s")

# On renvoie :
return {
    "error": True,
    "error_type": "service_unavailable",
    "message": "Le service de base de données est temporairement indisponible. Ne retente pas cette opération automatiquement. Informe l'utilisateur.",
    "retry_recommended": False
}

La sécurité des tools d'écriture

Les outils en lecture seule sont simples à sécuriser. Les outils d'écriture, créer un ticket, envoyer un email, modifier un enregistrement, sont une autre histoire.

On a eu un incident sur l'agent interne : le modèle a interprété une requête ambiguë comme une demande de clôturer plusieurs tickets Jira (au lieu d'en clôturer un seul). L'impact était limité parce qu'on travaillait sur un projet de test, mais ça nous a forcés à revoir l'architecture.

On a ajouté une étape de confirmation obligatoire pour tous les tools d'écriture à impact non réversible :

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
    tool = get_tool(name)
    
    if tool.requires_confirmation:
        confirmation_prompt = tool.describe_action(arguments)
        return [types.TextContent(
            type="text",
            text=f"ACTION REQUISE, Confirmation nécessaire avant d'exécuter : {confirmation_prompt}. Demande à l'utilisateur de confirmer explicitement."
        )]
    
    return await tool.execute(arguments)

C'est une instruction dans le retour du tool, pas dans le system prompt, ce qui la rend moins susceptible d'être ignorée si le contexte est long.

La latence des serveurs MCP distants

Les serveurs MCP en HTTP (par opposition aux serveurs stdio pour les usages locaux) ajoutent de la latence. Sur un agent avec plusieurs tools appelés séquentiellement, cette latence s'accumule.

On a mesuré des latences de 200-400ms par appel tool sur nos serveurs distants. Sur une conversation qui enchaîne 5 appels tools, ça représente 1 à 2 secondes de délai perçu, visible pour l'utilisateur.

La solution partielle qu'on a mise en place : paralléliser les appels tools indépendants en utilisant les capacités de parallel tool use d'Anthropic. Quand le modèle identifie plusieurs tools à appeler qui ne dépendent pas l'un de l'autre, ils sont exécutés en parallèle plutôt que séquentiellement.

Ce qu'on ferait différemment

Commencer par des tools en lecture seule. C'est moins impressionnant à démo mais c'est un périmètre de risque maîtrisé. On aurait dû attendre d'avoir une compréhension solide du comportement du modèle avant d'exposer des tools d'écriture.

Tester les cas d'erreur explicitement. On a bien testé les happy paths. On a sous-testé les cas d'erreur (service down, mauvais paramètres, timeout). Ces cas sont exactement ceux où le comportement du modèle est le plus imprévisible.

Documenter les tools pour le modèle, pas pour les développeurs. La description d'un tool dans le schéma MCP est lue par le modèle, pas par un développeur. Elle doit être rédigée en fonction : précise sur les contraintes, explicite sur les cas où le tool ne doit pas être utilisé, et lisible sans contexte supplémentaire.

L'état de l'écosystème

L'écosystème MCP est encore jeune. Les serveurs MCP officiels maintenus par Anthropic et les partenaires couvrent les cas d'usage courants, mais la qualité est variable. Certains serveurs sont bien documentés et stables. D'autres sont des preuves de concept qui ne sont pas prêtes pour la production.

La question qu'on se pose maintenant : MCP va-t-il s'imposer comme le standard de facto pour les intégrations LLM, ou est-ce qu'on va voir plusieurs standards coexister ? Les six prochains mois donneront probablement une réponse plus claire.

Disponible pour de nouveaux projets

Un projet à concrétiser ?
Parlons-en, sans engagement.

Un échange de 30 minutes pour cadrer votre besoin, qualifier la faisabilité et vous proposer une trajectoire claire.

1// kick-off : réponse sous 24h
2const project = await apogee.scope({
3 type: 'web | mobile | IA',
4 timeline: '6 à 16 semaines',
5 approach: 'sur-mesure'
6})
7// → cadrage offert
MCP en production : retour d'expérience 4 mois | Apogée Consult