MCP (Model Context Protocol) : c'est quoi ? Expliqué pour dev Java
1. Vue d’ensemble
MCP (Model Context Protocol) est un standard ouvert qui aide les LLM (large language model) à se connecter à des données, des outils et des systèmes externes de manière unifiée, au lieu que chaque application IA doive écrire ses propres intégrations spécifiques pour chaque source de données. Cet article explique ce qu’est MCP, comment son architecture fonctionne, et vous guide pour écrire un serveur MCP simple avec Spring Boot (via Spring AI) afin d’exposer un outil que le LLM peut appeler. À la fin, vous comprendrez quand utiliser MCP et quand c’est juste de l’over-engineering.
2. Le problème que MCP résout
Avant MCP, chaque application IA (chatbot, agent) qui voulait accéder à une base de données, un système de fichiers, une API interne… devait écrire son propre adaptateur. Si vous aviez M applications IA et N sources de données, vous aviez besoin de M × N codes d’intégration différents.
MCP normalise cette couche de communication : l’application IA (appelée Host) n’a besoin de parler le standard MCP qu’une fois, et n’importe quel système exposant un serveur MCP peut « communiquer » avec lui. C’est comme l’USB-C pour les appareils électroniques — un standard de connecteur universel à la place de dizaines de types de ports différents.
Note : MCP n’est pas un framework pour remplacer REST ou gRPC dans la communication service-to-service classique. Il a été créé pour que les LLM/agents appellent des outils, pas pour que deux microservices backend communiquent ensemble.
3. Architecture MCP
MCP a 3 rôles principaux :
- Host : l’application contenant le LLM (par exemple Claude Desktop, un agent que vous avez écrit).
- Client : le composant à l’intérieur du Host, maintenant une connexion 1-1 avec un serveur MCP.
- Server : le processus exposant les capacités (outils, ressources, prompts) selon le standard MCP.
┌──────────────┐ ┌──────────────┐ ┌──────────────────┐
│ Host │ 1-1 │ MCP Client │ JSON-RPC │ MCP Server │
│ (contient le │◄──────►│ │◄──────────►│ (DB, API, file) │
│ LLM) │ │ │ │ │
└──────────────┘ └──────────────┘ └──────────────────┘
La communication entre le client et le serveur utilise JSON-RPC 2.0, transmise via l’un des deux transports courants :
- stdio : le serveur s’exécute comme un processus enfant, client/serveur communiquent via stdin/stdout. Convient quand le serveur s’exécute localement.
- SSE/HTTP (Streamable HTTP) : utilisé quand le serveur s’exécute à distance, via le réseau.
3.1. Trois types de capacités qu’un serveur MCP peut exposer
| Capability | Signification | Exemple |
|---|---|---|
| Tools | Fonctions que le LLM peut appeler pour effectuer une action | getWeather(city), createOrder(...) |
| Resources | Données que le LLM peut lire (comme GET) | Fichiers log, enregistrements de base de données |
| Prompts | Templates de prompts prédéfinis par le serveur | « Résumer la commande selon le modèle X » |
La plupart des cas d’usage réels tournent autour des Tools, donc les exemples ci-dessous se concentrent là-dessus.
4. Écrire un serveur MCP avec Spring AI
Spring AI (à partir de la version 1.0.0-M6) prend en charge MCP via un starter dédié, vous permettant d’exposer un bean Java en tant qu’outil MCP via une annotation, sans avoir à implémenter JSON-RPC vous-même.
4.1. Ajouter la dépendance
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
4.2. Définir un outil
@Service
public class WeatherService {
@Tool(description = "Obtenir la température actuelle d'une ville")
public String getTemperature(String city) {
return switch (city.toLowerCase()) {
case "ha noi" -> "28°C, nuageux";
case "sai gon" -> "32°C, ensoleillé";
default -> "Aucune donnée pour la ville : " + city;
};
}
}
@Toolmarque cette méthode comme un outil MCP. L’attributdescriptionest très important — le LLM l’utilise pour décider s’il faut ou non appeler l’outil, et avec quels paramètres.getTemperaturereçoit uneString cityet retourne une chaîne décrivant la météo. Les paramètres et le type de retour génèrent automatiquement un schéma JSON que le LLM comprend.
4.3. Enregistrer l’outil auprès du serveur MCP
@Configuration
public class McpServerConfig {
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder()
.toolObjects(weatherService)
.build();
}
}
MethodToolCallbackProvider scanne les méthodes avec @Tool dans weatherService et les enregistre automatiquement auprès du serveur MCP au démarrage du contexte d’application.
4.4. Configurer le serveur
spring.ai.mcp.server.name=weather-mcp-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.stdio=true
Avec stdio=true, le serveur s’exécute comme un processus indépendant, communiquant via stdin/stdout — idéal pour tester rapidement avec Claude Desktop ou un client MCP personnalisé. Si vous voulez déployer le serveur en tant que service indépendant s’exécutant à distance, vous basculez vers le transport SSE et l’exposez via un endpoint HTTP au lieu de stdio.
Démarrer le serveur :
mvn spring-boot:run
Application WeatherMcp démarrée en 1.124 secondes
Outil MCP enregistré : getTemperature
5. Appeler le serveur MCP depuis le client
Supposez que vous écrivez une application Host (par exemple un agent interne) qui a besoin d’appeler l’outil getTemperature ci-dessus.
5.1. Dépendance client
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
5.2. Appeler l’outil
@Component
public class WeatherAgent {
private final McpSyncClient mcpClient;
public WeatherAgent(McpSyncClient mcpClient) {
this.mcpClient = mcpClient;
}
public String askTemperature(String city) {
CallToolRequest request = new CallToolRequest(
"getTemperature",
Map.of("city", city));
CallToolResult result = mcpClient.callTool(request);
return result.content().get(0).text();
}
}
McpSyncClientest le client Spring AI qui crée automatiquement un bean en fonction de la configuration du transport (stdio ou SSE) déclarée dansapplication.yml.CallToolRequestencapsule le nom de l’outil (getTemperature) et les paramètres (city) dans un appel JSON-RPC.result.content()retourne une liste de contenu de réponse ; ici nous prenons la première partietext.
Configuration du client (par exemple, se connecter au serveur via stdio en réexécutant le jar du serveur comme un sous-processus) :
spring:
ai:
mcp:
client:
stdio:
connections:
weather-server:
command: java
args: -jar weather-mcp-server.jar
6. Tester l’outil avec JUnit 5 + AssertJ
Puisque @Tool est juste une annotation sur une méthode Java classique, vous testez la logique métier indépendamment de MCP — pas besoin de déployer tout le serveur :
class WeatherServiceTest {
private final WeatherService weatherService = new WeatherService();
@Test
void shouldReturnTemperatureForKnownCity() {
String result = weatherService.getTemperature("Ha Noi");
assertThat(result).contains("28°C");
}
@Test
void shouldReturnFallbackMessageForUnknownCity() {
String result = weatherService.getTemperature("Tokyo");
assertThat(result)
.startsWith("Aucune donnée pour la ville :")
.contains("Tokyo");
}
}
Si vous voulez aussi tester l’intégration MCP (si l’outil est enregistré avec le bon nom et le bon schéma), vous pouvez écrire un test au niveau @SpringBootTest et vérifier ToolCallbackProvider :
@SpringBootTest
class McpServerConfigTest {
@Autowired
private ToolCallbackProvider toolCallbackProvider;
@Test
void shouldExposeTemperatureTool() {
var toolNames = toolCallbackProvider.getToolCallbacks().stream()
.map(callback -> callback.getToolDefinition().name())
.toList();
assertThat(toolNames).contains("getTemperature");
}
}
Ce test assure que chaque fois que vous refactorisez WeatherService, l’outil est toujours exposé avec le bon nom — évitant le cas où vous renommeriez une méthode en oubliant de mettre à jour les endroits où le LLM y fait référence.
7. Erreurs courantes et quand ne pas utiliser MCP
Description d’outil trop vague. Si description dit juste « récupérer les données », le LLM aura du mal à choisir le bon outil quand il y a plusieurs outils similaires. Écrivez des descriptions précises, clarifiez les entrées/sorties attendues.
Utiliser MCP pour remplacer REST/gRPC interne. MCP ajoute une couche JSON-RPC et un transport dédié, optimisé pour le contexte où un LLM appelle dynamiquement des outils — pas optimal pour deux services backend qui s’appellent mutuellement avec une fréquence élevée et une faible latence. Utiliser MCP ici est de l’over-engineering.
Ne pas contrôler les permissions de l’outil. Le serveur MCP s’exécute généralement avec les permissions du processus qui l’héberge. Si vous exposez un outil capable de supprimer des fichiers ou d’exécuter des commandes shell sans limiter les paramètres d’entrée, le LLM (ou une injection de prompt) pourrait abuser de cet outil. Validez toujours les paramètres dans l’outil, ne faites pas confiance aveuglément aux entrées du LLM.
Exécuter le serveur stdio dans un environnement serverless. Le transport stdio a besoin de spawner un processus enfant — ne fonctionne pas bien dans les environnements serverless/conteneur de courte durée. Dans ce cas, utilisez le transport SSE/HTTP.
Ne pas synchroniser les versions entre client et serveur. MCP est encore en développement rapide (beaucoup de changements cassants entre les versions de spec). Quand vous mettez à niveau le SDK, vérifiez attentivement le changelog avant de déployer en production.
8. Conclusion
MCP normalise la façon dont les LLM appellent des outils et lisent les données externes, résolvant le problème d’intégration M × N entre les applications IA et les sources de données. Avec Spring AI, vous pouvez exposer un service Java en tant qu’outil MCP avec juste l’annotation @Tool, sans avoir à implémenter JSON-RPC vous-même. Utilisez MCP quand vous construisez un agent/chatbot qui a besoin d’appeler des outils dynamiquement selon les décisions du LLM — et évitez de l’utiliser pour la communication service-to-service classique, où REST ou gRPC restent des choix plus appropriés.