Vaduo - Accueil
Développement

MCP en Java : créer un Client et un Serveur avec Spring Boot et Mistral AI (gratuit)

Découvrez pas à pas comment mettre en place une architecture simple et moderne basée sur le protocole MCP avec Java Spring Boot et Mistral (Gratuit).

Par Brian Delmaire

Intro

L’IA et les agents autonomes sont au cœur des discussions, que ce soit pour automatiser des tâches ou interagir avec des outils externes (Gmail, Outlook, Notion…) via des plateformes comme n8n, ChatGPT ou Mistral.

Mais aujourd’hui, nous allons créer notre propre outil, en exploitant un LLM capable d’utiliser nos ressources. Et ici, pas de Python ni de JavaScript : MAIS DU JAVA !

📕 Au programme

  • MCP Client avec un modèle Mistral + MCP Serveur
    • SpringBoot <version>3.5.8-SNAPSHOT</version>
    • Java <java.version>25</java.version>
    • spring-ai <version>1.0.3</version>
      • <artifactId>spring-ai-mistral-ai</artifactId>
      • <artifactId>spring-ai-starter-mcp-client</artifactId>
      • <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>

🏗️ Architecture


Model Context Protocol (MCP)

Le MCP est un protocole standardisé qui permet aux modèles d'IA d'interagir avec des outils et des ressources externes de manière structurée. Il prend en charge plusieurs mécanismes de transport pour offrir une flexibilité dans différents environnements. Le MCP Java SDK fournit une implémentation Java du Model Context Protocol, permettant une interaction standardisée avec les modèles d'IA et les outils, via des modes de communication synchrone et asynchrone.

  • Le McpClient gère les opérations côté client
  • Le McpServer prend en charge les opérations côté serveur.

Les deux utilisent McpSession pour la gestion de la communication.

  • Couche Transport (McpTransport) : Assure la sérialisation et la désérialisation des messages JSON-RPC, avec une prise en charge de plusieurs implémentations de transport:
    • STDIO : pour les environnements locaux ou CLI (standard in and standard out)
    • SSE : Server-Sent Events, Streamable HTTP
Dans la suite de cet article, nous allons utiliser le protocole SSE. Pour avoir plus d’informations sur les transports : https://modelcontextprotocol.io/specification/2025-06-18/basic/transports

🤖 Clé API Mistral

  1. Créez un compte sur Mistral AI
  2. Générez une clé API dans l’onglet API Keys
ℹ️ Note : L’utilisation de la clé gratuite permet d’accéder à tous les modèles, mais les requêtes peuvent être utilisées à des fins d’entraînement par Mistral. Dans ce projet d’exemple, aucune donnée sensible n’est transmise.

admin.mistral.ai


🧠 MCP Serveur

👩‍🔧 Création et configuration de SpringBoot

Commencez par créer votre projet Spring Boot comme à votre habitude, puis ajoutez le BOM Spring AI dans la section dependencyManagement de votre pom.xml :

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>1.0.0-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
</dependencyManagement>

Le Bill of Materials (BOM) de Spring AI définit les versions recommandées de toutes les dépendances compatibles avec une version donnée de Spring AI.

Il s’agit d’un module qui ne contient que la gestion des dépendances, sans déclaration de plugins ni références directes à Spring ou Spring Boot.


Ajoutez ensuite la dépendance qui permettra de construire votre serveur MCP:

<dependencies>
        <!--Spring AI-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
        </dependency>
</dependencies>

Pour configurer notre serveur MCP, nous allons définir quelques propriétés dans le fichier application.properties:

spring.application.name=server
server.port=8082
# Using spring-ai-starter-mcp-server-webmvc
spring.ai.mcp.server.name=my-mcp-server
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.sse-message-endpoint=/mcp/messages
spring.ai.mcp.server.sse-endpoint=/mcp/sse
spring.ai.mcp.server.instructions="This server provides Pokemon TCG tools"

spring.ai.mcp.server.type : type de serveur MCP.

  • SYNC → appels synchrones, le client attend la réponse immédiatement.
  • ASYNC → appels asynchrones, le serveur peut envoyer la réponse plus tard via SSE.

spring.ai.mcp.server.sse-message-endpoint : endpoint pour envoyer les messages Server-Sent Events (notifications ou réponses partielles).

spring.ai.mcp.server.sse-endpoint : endpoint pour gérer le flux SSE côté client.

spring.ai.mcp.server.instructions : instructions système pour guider le modèle lorsqu’il interagit avec le serveur.


🛠️ Création de notre outil

Nous allons maintenant créer notre premier outil MCP. Pour l’exemple, imaginons un outil simple, capable de rechercher des cartes Pokémon à partir d’une API externe.

Nous utiliserons l’API de TCGdex - The manager for your Pokémon TCG Collection


Lors de la création d’un outil, il est essentiel de soigner sa définition :

  • lui donner un nom clair name = "search_pokemon",
  • rédiger une description précise de sa fonction description = "Search Pokemon cards by name",
  • et fournir des explications complètes sur ses paramètres @ToolParam(description = "Pokemon's name").

Ces éléments permettent à l’IA de comprendre le rôle de la méthode et de choisir quand l’utiliser, en fonction du prompt de l’utilisateur et de la cohérence avec le contexte.

@Tool(name = "search_pokemon", description = "Search Pokemon cards by name")
    public String pokemonByName(@ToolParam(description = "Pokemon's name") final String name) throws IOException, InterruptedException {

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(BASE_URL + name))
                .GET()
                .build();

        HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());

        return response.body() != null ? response.body() : String.format("Aucune carte trouvée pour %s", name);
    }

On renvoie la réponse brute de l’API, que l’on pourrait retravailler ou filtrer selon nos besoins. Ici, nous faisons confiance à l’IA pour générer elle-même une réponse claire et bien formatée à partir des résultats de l’outil.

Réponse :

[
  {
    "id": "fut2020-1",
    "localId": "1",
    "name": "Pikachu on the Ball",
    "image": "https://assets.tcgdex.net/en/swsh/fut2020/1"
  },
  ...
]

Il est également possible, grâce à différentes instructions système, d’orienter l’IA pour favoriser l’usage d’un outil plutôt qu’un autre, ou d’aller plus loin dans la configuration du comportement.

Cependant, dans cet exemple, nous allons rester simples afin de bien saisir les bases. Pour approfondir, je vous invite à consulter la documentation officielle de Spring Boot.


🗿 Exposition des outils

Nous allons maintenant exposer notre outil. Le MCP Server Boot Starter offre les fonctionnalités suivantes :

  • Prise en charge des notifications de changements
  • Conversion automatique des outils Spring AI en spécifications synchrone ou asynchrone, selon le type de serveur
  • Génération automatique de la spécification des outils via des beans Spring
@Bean
    public ToolCallbackProvider pokemonTcgTools(PokemonTcgService pokemonTcgService) {
        return MethodToolCallbackProvider.builder().toolObjects(pokemonTcgService).build();
    }

Et voilà ! Au démarrage de l’application Spring, l’outil sera exposé, et le modèle pourra l’appeler via le protocole MCP à l’aide d’une requête du type :

{
  "type": "tool_call",
  "name": "search_pokemon",
  "arguments": { "name": "Pikachu" }
}

🧑‍💻 MCP Client

👩‍🔧 Création et configuration de SpringBoot

Dans cette partie, nous allons construire le client MCP, c’est-à-dire la partie qui enverra un prompt utilisateur au modèle de langage et communiquera avec le serveur MCP que nous venons de créer.

Comme précédemment, commencez par créer un projet Spring Boot avec le starter web, et n’oubliez pas d’ajouter le BOM Spring AI.


Ajoutez ensuite les dépendances nécessaires dans votre fichier pom.xml :

<dependencies>

   <!--Spring AI-->
  <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-starter-mcp-client</artifactId>
  </dependency>

<!-- Mistral Model-->
  <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-starter-model-mistral-ai</artifactId>
  </dependency>

  <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-mistral-ai</artifactId>
  </dependency>

</dependencies>

Définissons maintenant la configuration du client MCP dans le fichier application.properties :

spring.application.name=client
server.port=8080
# Using spring-ai-starter-mcp-client
spring.ai.mcp.client.name=my-mcp-client # nom unique du client MCP.
spring.ai.mcp.client.version=1.0.0 # version du client.
spring.ai.mcp.client.request-timeout=30s # durée maximale d’attente d’une réponse.
spring.ai.mcp.client.type=SYNC # type de connexion (SYNC ou ASYNC).
spring.ai.mcp.client.sse.connections.pokemon-tcg.url=http://localhost:8082
spring.ai.mcp.client.sse.connections.pokemon-tcg.sse-endpoint=/mcp/sse

mistral.api.key=YOUR_API_KEY # votre clé API Mistral pour authentifier les appels au modèle.

La propriété spring.ai.mcp.client.sse.connections.* permet de définir les connexions vers les serveurs MCP distants. Par exemple, pour se connecter à notre serveur local Pokémon, vous pouvez configurer :

  • URL du serveur : localhost:8082
  • Endpoint SSE : /mcp/sse
  • Nom de la connexion : pokemon-tcg (ou un identifiant personnalisé de votre choix)

🧠 Création du modèle de chat Mistral

Nous allons maintenant construire un service Mistral pour initialiser notre modèle de chat avec les bons paramètres :

  • model: on va utiliser Mistral Small
  • MistralAiApi: clé API Mistral (à mettre dans votre application.properties)
  • temperature: entre 0 et 1
    • 0 rend les réponses plus déterministes et répétitives
    • 1 les rend plus aléatoires et créatives.
  • max_tokens: Limite la longueur de la réponse générée
  • autres: Pour le reste des paramètres nous allons utiliser les valeurs par défauts.
@Service
public class MistralService {
    private final ToolExecutionEligibilityPredicate toolExecutionEligibilityPredicate;
    private final ToolCallingManager toolCallingManager;
    @Value("${mistral.api.key}")
    private String mistralApiKey;

    public MistralService() {
        this.toolExecutionEligibilityPredicate = new DefaultToolExecutionEligibilityPredicate();
        this.toolCallingManager = ToolCallingManager.builder().build();
    }

    public MistralAiChatModel createMistralChatModel() {

        return new MistralAiChatModel(
                new MistralAiApi(mistralApiKey),
                MistralAiChatOptions.builder()
                        .model(MistralAiApi.ChatModel.SMALL.getValue())
                        .temperature(0.4)
                        .maxTokens(500)
                        .build(),
                toolCallingManager,
                RetryTemplate.defaultInstance(),
                ObservationRegistry.NOOP,
                toolExecutionEligibilityPredicate
        );
    }
}

Ce service crée une instance configurée d’un modèle Mistral, mais l’architecture de l’application reste ouverte à d’autres modèles. Par exemple, si vous souhaitez utiliser OpenAI, il vous suffit d’intégrer la dépendance Spring dédiée (comme spring-ai-openai) et de remplacer ce service par votre propre implémentation.


🗣️ Orchestration des échanges avec le modèle

Une fois notre modèle prêt, construisons un service d’orchestration pour gérer la communication entre le client, le modèle et les outils MCP. Pour simplifier, nous partons du principe que les prompts envoyés par l’utilisateur sont déjà "propres" et ne nécessitent pas de prétraitement.

Par exemple, vérifier si le prompt mentionne bien des Pokémon. Si ce n’est pas le cas, le modèle pourrait répondre poliment :


Nous allons instancier un ChatClient en utilisant notre MistralAiChatModel. À ce client, nous passerons les tools récupérés automatiquement par Spring via les endpoints MCP configurés dans application.properties Il ne reste plus qu’à appeler notre ChatClient avec la saisie de notre utilisateur.

@Service
public class ModelOrchestratorService {

    private final MistralService mistralService;
    private final SyncMcpToolCallbackProvider toolCallbackProvider;

    public ModelOrchestratorService(final MistralService mistralService,
                                    final SyncMcpToolCallbackProvider toolCallbackProvider) {
        this.mistralService = mistralService;
        this.toolCallbackProvider = toolCallbackProvider;
    }

    public String invokeMistral(final ChatRequest req) {

        final MistralAiChatModel chatModel = this.mistralService.createMistralChatModel();

        final ChatClient chatClient = ChatClient.builder(chatModel)
                .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks())
                .build();

        return chatClient.prompt()
                .user(req.userPrompt())
                .system(SYSTEM_PROMPT)
                .call()
                .content();
    }
}

Mais attends, c’est quoi ce .system(SYSTEM_PROMPT) ?

Ce prompt système définit le comportement du modèle. Sans lui, le modèle exécuterait automatiquement l’outil Pokémon dès que l’utilisateur mentionne ce sujet, sans contexte ni contrôle. Par défaut, le modèle peut :

  • Simplement renvoyer la réponse brute de l’outil, sans traitement supplémentaire.
  • Interpréter et reformuler la réponse à sa manière, ce qui peut entraîner des variations à chaque appel (et donc des résultats moins cohérents).
private static final String SYSTEM_PROMPT = """
            When presenting Pokemon card results:
            1. For each card, display the information in a clear format
            2. CRITICAL: All image URLs must end with /low.png
            3. CRITICAL: URLs must NOT have any spaces (write https://assets.tcgdex.net/en/base/basep/1/low.png NOT https: //assets.tcgdex.net/en/base/basep/1/low.png)
            4. Format example:
            
               **Card Name**
               - ID: card-id
            
               - ![card-id](https://complete-url/low.png)
            
            5. Present up to 5 cards maximum
            6. Keep URLs as valid links without any spaces or line breaks
            7. If the user's message contains a Pokémon name that is misspelled, correct the name to the closest known Pokémon. If you are unsure, leave it as is.
            """;

🎯 Ici, on indique au modèle (car notre outil balance une réponse brut)

  • Limite de 5 cartes maximum par réponse.
  • Format de sortie : Les résultats doivent être présentés en Markdown
  • Correction des noms de Pokémon : Si le nom est mal orthographié, le modèle doit demander une confirmation à l’utilisateur avant d’appeler l’outil.
  • Optimisation des images : À chaque URL d’image de carte Pokémon, le modèle doit ajouter /low.png en fin d’URL. Cela permet de spécifier le format et la qualité de l’image, car l’API ne les fournit pas par défaut.

📨 Et enfin notre contrôleur

Enfin, exposons un simple contrôleur pour interagir avec notre modèle :

@RestController
public class ChatController {

    private final ModelOrchestratorService modelOrchestratorService;

    public ChatController(final ModelOrchestratorService modelOrchestratorService) {
        this.modelOrchestratorService = modelOrchestratorService;
    }

    @PostMapping(value = "/ai/ask", produces = "text/markdown; charset=UTF-8")
    public String ask(@RequestBody final ChatRequest req) {
        return this.modelOrchestratorService.invokeMistral(req);
    }
}

🚀 Testons

Vous pouvez maintenant tester l’API avec Postman, cURL, ou un petit front maison.

Pour tester plus facilement, j’ai demandé à Junie (l’IA de JetBrains) de générer une petite interface de chat Java — disponible sur le GitHub du projet. localhost:8081

✔️ Conclusion

Nous venons de voir comment créer un écosystème complet autour d’un modèle LLM en Java, en utilisant MCP pour orchestrer les échanges entre un client, un serveur et des outils externes.

En résumé, MCP et Spring AI offrent une base solide pour transformer un modèle de langage en véritable agent autonome, extensible et contrôlable. Il ne reste plus qu’à explorer, expérimenter et laisser libre cours à votre créativité !


🚀 Liens utiles

Lien du projet Github : https://github.com/duocrafters/java-mcp-client-server

Spring AI Documentation : https://docs.spring.io/spring-ai/reference/

Mistral AI : https://docs.mistral.ai/

Model Context Protocol (MCP) : https://github.com/modelcontextprotocol

Sécurité MCP: https://loud-technology.com/blog/mcp-secrets-securite-agents-ia/

Les autres articles à explorer