MCP en détail : messages JSON-RPC, serveurs FastMCP et suivi ReAct
Suivre une demande de protocole de contexte de modèle depuis les outils/liste jusqu’à la réponse finale, en utilisant des serveurs FastMCP, un agent LangGraph ReAct ainsi qu’un suiveur qui enregistre chaque étape.
Sources: How MCP Works: A Deep Dive with Code
Le Model Context Protocol, ou MCP, est une norme ouverte d’Anthropic qui permet aux modèles d’IA tels que Claude d’accéder à des outils externes, à des sources de données et à des services. Une bonne image mentale est celle d’une prise universelle : au lieu d’écrire une intégration personnalisée pour chaque outil, il existe une méthode standard permettant à un modèle de communiquer avec des systèmes externes.
Les composants
La structure est composée de trois éléments. L’hôte MCP est l’application que l’utilisateur utilise réellement, qu’il s’agisse d’une fenêtre de chat ou d’un IDE, et il contient un client chargé de gérer la connexion. Le protocole MCP constitue la couche de communication normalisée entre l’hôte et les serveurs. Chaque serveur MCP se connecte à un type de ressource, par exemple une base de données, une API externe ou un environnement d’exécution de code, et agit en tant que pont vers celle-ci.
Ce qui se passe en temps de exécution
À un niveau général, une demande traverse ces étapes :
- L’utilisateur envoie une requête à l’hôte.
- L’hôte demande aux serveurs MCP quels outils ils proposent.
- Les serveurs renvoient leurs listes d’outils.
- L’hôte envoie la requête ainsi que la liste d’outils à l’LLM.
- L’LLM choisit un outil et l’indique à l’hôte.
- L’hôte appelle le serveur MCP approprié pour l’exécuter.
- Le serveur exécute l’outil et renvoie le résultat.
- L’hôte transmet ce résultat à l’LLM.
- L’LLM rédige la réponse finale que l’hôte affiche.
Un serveur expose trois types d’éléments. Les outils sont des fonctions que le modèle peut invoquer, telles que search_web, query_database ou send_email. Les ressources sont des données lisible que l’hôte peut récupérer en tant que contexte, comme des fichiers, des enregistrements de base de données, des événements de calendrier et des documents, que le modèle peut lire mais pas exécuter. Les prompts sont des modèles prédéfinis proposés par le serveur. Un serveur GitHub, par exemple, peut fournir un modèle « résumer ce PR » que l’hôte peut afficher à l’utilisateur.
En fin de compte, MCP n’est qu’un moyen standardisé permettant à un hôte de communiquer avec un serveur. Il définit le format des messages et les règles de la conversation, rien de plus. Plus précisément, il définit quatre éléments.
- Forme des messages. Tout est basé sur JSON-RPC 2.0. Une requête contient un
id, unmethodet desparams, tandis qu’une réponse répète leidaccompagné soit d’unresultsoit d’unerror; ainsi, les deux parties communiquent de la même manière. - Méthodes disponibles. Le protocole définit un vocabulaire fixe des appels que le hôte peut effectuer, par exemple
tools/listpour la découverte,tools/callpour l’exécution d’une fonctionnalité,resources/readpour la récupération de données, etprompts/getpour la récupération d’un modèle. Les serveurs doivent les implémenter. - Échange initial. Lors de la première connexion, les deux parties échangent des messages
initializeindiquant leur version de protocole et leurs capacités, afin que chacune sache ce que l’autre prend en charge avant toute autre action.
Une seule requête vers l’outil se divise en quatre messages. D’abord, l’hôte demande quels outils sont disponibles :
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {}
}
Le serveur répond avec des descriptions de ses outils :
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "search_web",
"description": "Search the web for a given query",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
}
}
]
}
}
Le présentateur appelle alors l’un d’eux :
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search_web",
"arguments": {
"query": "latest news on MCP protocol"
}
}
}
Et le serveur renvoie le résultat :
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "Anthropic released MCP in Nov 2024 as an open standard..."
}
]
}
}
Apprendre en développant est la méthode la plus efficace ; par conséquent, la suite de ce texte présente un petit serveur MCP utilisé par un agent.
Qu’est-ce que FastMCP fait
FastMCP est un framework Python permettant d’écrire des serveurs MCP avec un minimum de code générique. Sans lui, un développeur devrait implémenter le cycle de messages JSON-RPC, sérialiser et désérialiser chaque requête et réponse, écrire manuellement des schémas JSON pour chaque fonction exposée, et gérer les messages de protocole tels que initialize, tools/list, tools/call, resources/list et prompts/list. Avec lui, une simple fonction Python accompagnée d’un décorateur suffit, et le framework s’occupe du reste :
from fastmcp import FastMCP
mcp = FastMCP("My Server") # creates the server
@mcp.tool() # registers the function as an MCP tool
def add(a: float, b: float) -> float:
"""Add two numbers.""" # docstring → tool description sent to the LLM
return a + b # type hints → JSON Schema sent to the LLM
mcp.run() # starts the stdio message loop
D’après les annotations de type, FastMCP construit lui-même un schéma JSON, et la documentation sert en même temps de description en langage courant que le modèle lit lorsqu’il évalue si un outil vaut la peine d’être appelé. L’appel à mcp.run() démarre une boucle qui bloque sur stdin et stdout tout en utilisant le format de communication MCP, et c’est précisément à cette boucle que MultiServerMCPClient s’attache.
Trois primitives dans les serveurs d’exemple
Chaque serveur du projet est une application FastMCP autonome qui expose ces trois types de primitives.
Les outils viennent en premier : les fonctions que le modèle est autorisé à appeler. Il suffit de décorer une fonction Python avec @mcp.tool(), et FastMCP transforme ces annotations ainsi que la documentation en un schéma JSON qui est envoyé au client lors de la requête tools/list. Le modèle étudie ce schéma pour déterminer quand l’outil doit être utilisé et quels arguments fournir.
# servers/math_server.py
@mcp.tool()
def divide(a: float, b: float) -> float:
"""Divide a by b. Raises an error if b is zero."""
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Les ressources sont des données en lecture seule situées à une URI. Le modèle ne les invoque pas automatiquement, il faut donc les demander explicitement. Dans ce projet, elles ne servent qu’à illustrer l’idée, tandis que leurs utilisations en environnement de production pourraient inclure des fichiers de configuration, des snapshots d’une base de données ou de la documentation de référence.
# servers/math_server.py
@mcp.resource("math://constants")
def get_math_constants() -> str:
"""Common mathematical constants."""
return f"π = {math.pi}\n e = {math.e}\n ..."
@mcp.resource("math://formulas/{category}")
def get_formulas(category: str) -> str:
"""Retrieve mathematical formulas by category (geometry | algebra | statistics)."""
catalog = {
"geometry": (
"Geometry Formulas:\n"
" Circle area: A = π × r²\n"
" Circle circumference: C = 2π × r\n"
" Rectangle area: A = length × width\n"
" Triangle area: A = (base × height) / 2\n"
" Sphere volume: V = (4/3) × π × r³\n"
),
"algebra": (
"Algebra Formulas:\n"
" Quadratic formula: x = (−b ± √(b²−4ac)) / 2a\n"
" Difference of squares: a²−b² = (a+b)(a−b)\n"
" Perfect square: (a+b)² = a²+2ab+b²\n"
" Sum of arithmetic seq: S = n(a₁+aₙ)/2\n"
),
"statistics": (
"Statistics Formulas:\n"
" Mean: μ = Σx / n\n"
" Variance: σ² = Σ(x−μ)² / n\n"
" Std Dev: σ = √(Σ(x−μ)² / n)\n"
" Z-score: z = (x−μ) / σ\n"
),
}
return catalog.get(
category,
f"Unknown category '{category}'. Available: geometry, algebra, statistics",
)
Le URI math://formulas/{category} est un modèle. Sa partie {category} correspond à un paramètre de chemin, ce qui permet à math://formulas/geometry et math://formulas/algebra d’être deux ressources distinctes fournies par la même fonction.
Les prompts sont des modèles d’instructions réutilisables. Il s’agit de messages système paramétrés que le client peut demander, permettant d’emballer une personnalité d’expert ou des instructions de tâche au sein du serveur afin que tout client connecté puisse les utiliser de manière cohérente.
# servers/math_server.py
@mcp.prompt()
def math_tutor(difficulty: str = "intermediate") -> str:
return (
f"You are an expert math tutor for {difficulty}-level students. "
"Break every problem into numbered steps..."
)
Les serveurs mettent ensemble à disposition 19 outils, comme le montrent les sections suivantes.
Le point de vue du client
C’est exactement ce qui se passe après l’exécution de python main.py avec une requête. Le parcours de la requête de bout en bout est le suivant :
You type a query
│
▼
main.py ← entry point, parses args, kicks off async loop
│
▼
client/agent.py ← spawns 3 MCP servers, builds the agent, invokes it
│ │
│ ▼
│ utils/tracker.py ← fires on every LLM call, tool call, and result
│
▼
LangGraph ReAct loop ← think → call tool → observe → repeat
│
▼
servers/{math,text,data}_server.py ← each runs as an isolated subprocess
Étape 1 : où commence l’exécution
main.py est le premier fichier que Python exécute. Il charge les variables d’environnement, analyse les arguments de la ligne de commande, affiche un panneau de bienvenue, puis passe le contrôle à un exécuteur asynchrone.
async def _run(queries: list) -> None:
from client.agent import run_query # imported here (late) to keep startup fast
for i, q in enumerate(queries):
await run_query(q)
def main() -> None:
...
asyncio.run(_run(queries))
asyncio.run() crée un nouveau cycle d’événements et bloque jusqu’à ce que _run() soit terminé. La requête utilisée pour la démonstration était :
Generate 8 random numbers between 5 and 50 using seed=42,
then calculate their statistics, and tell me if there are any outliers.
Exécutez-le depuis la ligne de commande de la manière suivante :
python3 main.py --query "Generate 8 random numbers between 5 and 50 using seed=42, \
then calculate their statistics, and tell me if there are any outliers."
Un panneau d’information statique est ensuite affiché par _print_header() :
╭────────────────────────────── 🔌 MCP Example ───────────────────────────────╮
│ Multi-Server MCP Demo │
│ │
│ Three FastMCP servers, each exposing tools + resources + prompts: │
│ ● Math Server — add, subtract, multiply, divide, power, sqrt, │
│ percentage │
│ ● Text Server — count_words, word_frequency, reverse, transform, │
│ extract_emails │
│ ● Data Server — generate_numbers, calculate_statistics, find_outliers, │
│ normalise │
│ │
│ Stack : FastMCP · LangChain · LangGraph · OpenAI · Rich │
│ Track : live Rich panels + JSONL log files under logs/ │
╰──────────────────────────────────────────────────────────────────────────────╯
Étape 2 : création de l’agent
client/agent.py contient run_query(query), la fonction asynchrone qui orchestre tout le processus. Elle commence par créer un suiveur ainsi qu’un fichier de journal.
log_file = str(LOGS_DIR / f"run_{int(time.time())}.jsonl")
tracker = MCPTracker(log_file=log_file)
Avant que tout serveur ne soit connecté, un MCPTracker est créé et un fichier JSONL daté est préparé dans le répertoire logs/. Dès lors, chaque événement y est enregistré en temps réel.
Ensuite, la requête de l’utilisateur est affichée :
╭──── 💬 USER QUERY ────╮
│ Generate 8 random... │
╰────────────────────────╯
Ensuite, les serveurs à générer sont définis :
def _server_config() -> Dict[str, Any]:
silent_env = {**os.environ, "FASTMCP_LOG_LEVEL": "ERROR"}
return {
"math_server": {
"command": sys.executable,
"args": ["servers/math_server.py"],
"transport": "stdio",
"env": silent_env,
},
"text_server": { ... },
"data_server": { ... },
}
Chaque entrée décrit un serveur MCP exécuté en tant que sous-processus. La valeur command correspond à l’interpréteur déjà en cours d’exécution (sys.executable), args indique le nom du script du serveur, et transport précise comment le client et le serveur communiquent, stdio désignant les canaux d’entrée et de sortie standard. Le champ env définit l’environnement du sous-processus, tandis que FASTMCP_LOG_LEVEL=ERROR masque l’afficheur de démarrage de FastMCP.
Le transport stdio est le plus simple dans MCP : le client envoie des messages JSON-RPC vers stdin du serveur et lit les réponses depuis stdout, de sorte qu’aucune porte réseau n’est ouverte.
Ensuite, le client se connecte à chaque serveur afin de découvrir les outils disponibles :
client = MultiServerMCPClient(_server_config())
tools = await client.get_tools()
MultiServerMCPClient, provenant de langchain-mcp-adapters, effectue les actions suivantes pour chaque entrée de serveur en même temps. Il crée un processus enfant, envoie la séquence d’initialisation MCP via stdio, envoie une requête tools/list, reçoit la liste des schémas d’outils et enveloppe chaque schéma dans un objet Tool de LangChain.
Lorsque await client.get_tools() est terminé, le résultat est une liste plate de 19 objets StructuredTool : sept provenant du serveur de mathématiques, sept du serveur de texte et cinq du serveur de données. Cette liste représente l’ensemble complet des actions disponibles pour le modèle.
Connected to 3 servers (math_server, text_server, data_server) with 19 tools: add, subtract, multiply,
divide, power, square_root, calculate_percentage, count_words, word_frequency, reverse_text,
transform_case, find_and_replace, extract_emails, count_vowels_consonants, generate_numbers,
calculate_statistics, find_outliers, sort_values, normalize_values
Ensuite, le modèle de langage et l’agent sont créés :
model = ChatOpenAI(model=model_name, temperature=0)
agent = create_react_agent(model, tools)
La fonction create_react_agent de LangGraph crée un graphe d’état compilé ayant cette structure :
START
│
▼
[call_model] ─── no tool call ──▶ END
│
tool call requested
│
▼
[call_tools]
│
▼
[call_model] (loop again with tool result in context)
C’est le modèle ReAct, qui combine raisonnement et action. Le modèle réfléchit à la prochaine étape, agit en appelant une outil, observe le résultat et répète le processus jusqu’à disposer d’informations suffisantes pour répondre.
Finalement, l’agent est invoqué :
config = {"callbacks": [tracker]}
result = await agent.ainvoke({"messages": [("human", query)]}, config=config)
L’entrée "callbacks": [tracker] s’intègre au système de rappels de LangChain. Chaque fois que le framework effectue une action importante, comme lancer ou terminer une appel à un LLM ou à une outil, il appelle la méthode correspondante sur tracker. C’est ainsi que le suiveur peut observer tout ce qui se passe sans être intégré à la logique elle-même.
Étape 3 : le suivre en temps réel
MCPTracker étend BaseCallbackHandler de LangChain. Ses méthodes ne sont jamais appelées directement, car LangChain les exécute au bon moment.
agent.ainvoke() called
│
├─▶ on_chain_start() "▶ AGENT STARTED" panel
│
├─▶ on_chat_model_start() "🤖 LLM CALL #1" panel (timer starts)
├─▶ on_llm_end() "LLM responded ⏱ 1.23s → will call: add, multiply"
│
├─▶ on_tool_start() "🔧 TOOL CALL #1" panel (timer starts)
├─▶ on_tool_end() "✓ TOOL RESULT ⏱ 0.01s" panel
│
├─▶ on_tool_start() (second tool, if any)
├─▶ on_tool_end()
│
├─▶ on_chat_model_start() "🤖 LLM CALL #2" (LLM synthesises final answer)
├─▶ on_llm_end() no tools this time → loop ends
│
└─▶ agent.ainvoke() returns
Chaque fois que on_tool_start est déclenché, le suiveur détermine quel serveur possède l’outil et lui attribue une couleur correspondante. Le seul but est d’améliorer la lisibilité.
TOOL_SERVER_MAP = {
"add": "math_server",
"count_words": "text_server",
"generate_numbers": "data_server",
...
}
SERVER_COLORS = {
"math_server": "cyan",
"text_server": "green",
"data_server": "yellow",
}
Chaque événement devient une ligne JSON dans logs/run_<timestamp>.jsonl. Le fichier est ouvert en mode ajout et écrit immédiatement, de sorte que l’exécution de tail -f logs/run_*.jsonl dans un autre terminal affiche les événements bruts qui s’affichent au fur et à mesure que l’agent fonctionne. Lorsque agent.ainvoke() renvoie une valeur, client/agent.py appelle manuellement print_summary(), qui indique le nombre total d’appels à l’LLM, le nombre d’appels aux outils ainsi que le temps écoulé pour toute l’exécution.
Le premier panneau qui apparaît correspond à l’appel n°1 à l’LLM, avec un seul message :
── 🤖 LLM CALL #1 model=gpt-5-mini messages=1 ──╮
│ Role Content preview │
│ Human Generate 8 random numbers between 5 and 50… │
╰──────────────────────────────────────────────────────╯
Le graphe ReAct vient de commencer. Le modèle reçoit le prompt système, injecté par LangGraph avec la liste des outils et leurs schémas, ainsi que le seul message humain, d’où messages=1.
LLM responded ⏱ 5.35s → will call: generate_numbers
Le suiveur a examiné la réponse brute du modèle. Au lieu de du texte en prose, le modèle a répondu par une demande structurée visant à appeler une outil :
{
"name": "generate_numbers",
"arguments": {"count": 8, "min_val": 5, "max_val": 50, "seed": 42}
}
Dans response.generations[0][0].message.tool_calls, le suiveur a détecté la requête et a affiché son nom. Les 5,35 secondes correspondent simplement au temps de trajet aller-retour vers l’API d’OpenAI. LangGraph lit cette décision et passe au nœud call_tools, ce qui génère la première appel à outil :
╭── 🔧 TOOL CALL #1 ──────────────────╮
│ Tool : generate_numbers │
│ Server: data_server │
│ Args : │
│ {'count': 8, 'min_val': 5, │
│ 'max_val': 50, 'seed': 42} │
╰───────────────────────────────────────╯
D’après le nom de l’outil generate_numbers, le suiveur a consulté TOOL_SERVER_MAP, qui renvoie à data_server, et a coloré le panneau en jaune car SERVER_COLORS["data_server"] vaut "yellow". À ce moment précis, LangChain envoie un message JSON-RPC tools/call par le canal stdio vers le processus enfant data_server :
{"method": "tools/call", "params": {"name": "generate_numbers",
"arguments": {"count": 8, "min_val": 5, "max_val": 50, "seed": 42}}}
Le processus le reçoit, exécute la fonction Python et écrit le résultat dans son stdout.
╭── ✓ TOOL RESULT ⏱ 0.63s ────────────────────────────╮
│ [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91] │
╰──────────────────────────────────────────────────────────╯
Il a exécuté ce code exact :
random.seed(42)
return [round(random.uniform(5, 50), 2) for _ in range(8)]
# → [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91]
Les 0,63 seconde couvrent le temps écoulé entre l’envoi de la demande et la réception de la réponse via le pipeline, y compris les coûts liés à la planification des processus Python. LangGraph ajoute le résultat à la liste de messages de l’agent sous forme de ToolMessage, de sorte que la liste contient désormais trois éléments : l’interaction humaine, l’IA (la décision de faire appel à un outil) et l’outil lui-même (le résultat).
Vient ensuite l’appel numéro 2 au LLM, avec trois messages :
╭── 🤖 LLM CALL #2 model=gpt-5-mini messages=3 ──╮
│ Role Content preview │
│ Human Generate 8 random numbers… │
│ AI (the tool-call decision) │
│ Tool [33.77, 6.13, 17.38, ...] │
╰──────────────────────────────────────────────────────╯
Le modèle est à nouveau appelé avec l’ensemble de l’historique jusqu’à présent. Il voit la question initiale, sa propre décision antérieure d’appeler generate_numbers, ainsi que les nombres qui en sont ressortis. Son raisonnement se déroule à peu près comme suit : les nombres sont disponibles, et la question demandait également des statistiques, donc calculate_statistics doit être exécuté avec eux. Cela génère le deuxième appel à l’outil et son résultat :
Tool : calculate_statistics
Server: data_server
Args : {'numbers': [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91]}
Result: {"count":8, "min":6.13, "max":45.15, "mean":24.9962,
"median":25.575, "std_dev":14.8181, "variance":219.5755,
"range":39.02, "q1":15.04, "q3":38.14}
Aucun souvenir n’a été conservé par le modèle ici. Il a extrait les valeurs, mot par mot, du précédent ToolMessage présent dans sa fenêtre de contexte et les a insérées dans les arguments de l’outil suivant. C’est ainsi que se manifeste la chaînage dans un cycle ReAct : ce que produit un outil devient un contexte que la prochaine appel du modèle peut lire et réutiliser.
Dans l’appel n°3 du LLM, il y a cinq messages, et le modèle appelle find_outliers :
Tool : find_outliers
Server: data_server
Args : {'numbers': [...], 'threshold': 2}
Result: {"outliers": [], "outlier_count": 0,
"total_checked": 8, "mean": 24.9962, "std_dev": 14.8181}
La fonction find_outliers calcule le score z de chaque valeur (z = (x − moyenne) / écart_type) et vérifie si l’un d’eux dépasse le seuil de 2. Aucun ne le fait, donc outliers est une liste vide.
Le numéro d’appel du LLM 4 affiche sept messages :
╭── 🤖 LLM CALL #4 messages=7 ──╮
│ Human / AI / Tool │
│ AI / Tool │
│ AI / Tool │
╰───────────────────────────────────╯
LLM responded ⏱ 30.25s
Le suffixe « will call » manquant indique que le modèle a généré du texte ordinaire et non une demande d’outil, ce qui met fin au cycle ReAct. L’appel a duré 30 secondes, car le contexte était volumineux (sept messages, résultats JSON bruts inclus) et le modèle a élaboré une synthèse longue et approfondie, en calculant manuellement un z-score pour chaque valeur. LangGraph ne détecte aucune demande d’outil dans la réponse et redirige vers END, qui affiche la réponse finale :
╭── ✅ FINAL ANSWER ───────────────────────────────╮
│ Random numbers: [33.77, 6.13, 17.38, ...] │
│ Mean: 24.9962 / Median: 25.575 / Std Dev: 14.8181│
│ Outliers: none (all |z| < 2) │
╰───────────────────────────────────────────────────╯
Ensuite, le résumé de la course apparaît :
╭─────────────────────────────────────────────────────╮
│ LLM Calls 4 │
│ Tool Calls 3 │
│ Total Time 42.25s │
│ Log File logs/run_1776864980.jsonl │
╰─────────────────────────────────────────────────────╯
La séquence complète des messages
Ceci représente chaque élément de la liste des messages de l’agent lorsque agent.ainvoke() a renvoyé :
1 HumanMessage "Generate 8 random numbers…"
2 AIMessage [tool_call: generate_numbers({count:8, min:5, max:50, seed:42})]
3 ToolMessage [33.77, 6.13, 17.38, 15.04, 38.14, 35.45, 45.15, 8.91]
4 AIMessage [tool_call: calculate_statistics({numbers:[...]})]
5 ToolMessage {count:8, mean:24.9962, std_dev:14.8181, ...}
6 AIMessage [tool_call: find_outliers({numbers:[...], threshold:2})]
7 ToolMessage {outliers:[], outlier_count:0, ...}
8 AIMessage "Here are the results. Random numbers: …" ← final answer
Chaque appel à l’LLM prend en compte tout ce qui a été accumulé jusqu’à présent, ce qui fait que la valeur de messages dans les panneaux de suivi augmente de 1, 3, 5, 7.
Le journal JSONL
Tous les événements mentionnés ci-dessus ont également été enregistrés dans logs/run_x.jsonl au fur et à mesure de leur survenue, un objet JSON par ligne :
{"timestamp": "...", "event": "agent_start", "data": {"runnable": "LangGraph", "run_id": "..."}}
{"timestamp": "...", "event": "llm_call", "data": {"call_number": 1, "model": "gpt-5-mini", "messages": 1}}
{"timestamp": "...", "event": "llm_end", "data": {"elapsed": "5.35s", "tool_calls_requested": ["generate_numbers"]}}
{"timestamp": "...", "event": "tool_start", "data": {"call_number": 1, "tool": "generate_numbers", "server": "data_server", "args": "..."}}
{"timestamp": "...", "event": "tool_end", "data": {"elapsed": "0.63s", "output_preview": "[33.77, 6.13, ...]"}}
{"timestamp": "...", "event": "llm_call", "data": {"call_number": 2, "model": "gpt-5-mini", "messages": 3}}
...
{"timestamp": "...", "event": "run_summary", "data": {"llm_calls": 4, "tool_calls": 3, "total_elapsed": "42.25s"}}
Pourquoi c’est important
La véritable valeur de MCP réside dans sa capacité de distribution. Avant l’existence d’une norme, toute équipe souhaitant dépasser les capacités d’un LLM devait créer sa propre couche d’intégration, et tous ces efforts n’étaient pas transférables à d’autres modèles ou frameworks. Aujourd’hui, un serveur est écrit une seule fois et sert n’importe quel client conforme. Une équipe de base de données peut publier un serveur en lecture seule pour son schéma, une équipe DevOps peut mettre à disposition des outils de déploiement, et une plateforme de données peut offrir des fonctions de requête et d’analyse. Tout agent peut se connecter à tous ces éléments, et les équipes n’ont jamais besoin de s’accorder sur les détails d’implémentation.
Lectures complémentaires
- Reconstruire un agent de style Claude Code à l’aide de LangChain Deep Agents — Comment fonctionne le système d’exploitation d’un agent de codage, du cycle de base aux outils, à la planification, à la mémoire, aux sous-agents et aux validations en environnement isolé, reconstruit étape par étape avec DeepAgents.
- Dix serveurs MCP à installer en premier, par ordre de priorité pour les développeurs — Une liste classée de serveurs MCP adaptés aux assistants de codage IA, allant de Context7 et GitHub à Postgres et les systèmes de mémoire, accompagnée de configurations d’installation et de conseils concernant les permissions.