Blog Zenika

#CodeTheWorld

Autres

Serveur MCP avec SpringBoot – Partie 1

Qu’est-ce que le SpringBoot MCP Server ?

Le SpringBoot MCP Server est une application Spring Boot qui utilise la librairie Spring AI MCP pour exposer vos données en tant qu’outils pour les modèles d’IA. Ainsi l’IA peut récupérer les données dont elle a besoin en fonction de ce qu’on lui demande, via des outils MCP standardisés. Nous utiliserons dans ce tutoriel Claude Desktop comme client MCP mais il existe aussi Cursor et d’autres client MCP. J’utiliserai le protocole STDIO (les paramètres et réponses sont donc transmises via stdin / stdout – entrée / sortie standard) qui nous permettra de lancer Claude Desktop en local. Je vous montrerai aussi comment utiliser une partie de ce que propose MCP inspector pour debugger votre serveur avec des points d’arrêts et sans LLM.

 À la fin de ce tutoriel vous pourrez donc debugger avec MCP inspector, demander à un LLM de Claude Desktop quel sera le prochain trajet entre Rennes et Paris, car notre serveur MCP fera des appelles à l’API SNCF.

Diagramme illustrant l'architecture d'une application Spring Boot utilisant MCP, montrant la communication entre le client MCP (Claude Desktop), le serveur MCP, et l'API SNCF, avec des outils pour obtenir des trajets et des lieux.

 nous nous concentrerons donc sur un serveur qui offre deux outils en s’appuyant sur l’API de la SNCF :

  • getJourney (En donnant un ID de gare de départ et un ID de gare d’arrivée, on reçoit un trajet et sa durée)
  • getPlace (En donnant une ville on reçoit un ID de coordonnée de gare SNCF)

Prérequis

Avant de commencer, assurez-vous d’avoir :

Ou via votre package manager préféré (brew sous Mac, apt sous debian…)

Étape 1 : Créer un nouveau projet Spring Boot

Spring Initializr :

  • Aller sur start.spring.io (https://start.spring.io/).
  • Configurer votre projet :
  • Projet : Maven
  • Langage : Java 25
  • Spring Boot : 3.5.X
  • Packaging : Jar
  • Le group : com.zenika
  • L’Artifact : mcp_server
  • Ajouter la dépendance Model Context Protocol Server et générer le projet puis l’ouvrir dans intelliJ.

Étape 2 : Comprendre la structure du projet

  • JourneySncf / JourneySummary: Modèles pour les trajets SNCF.
  • PlaceSncf / PlaceSummary: Modèles pour les gares/lieux de la SNCF.
  • SncfService : Contient des outils MCP avec des annotations @Tool que l’on peut comparer à des endpoints mais avec juste un nom get_place ou get_journey.
  • SncfRepository : Repository pour les appels à l’API SNCF.
  • McpServerConfig : Bean de configuration pour le serveur MCP pour le SncfService.
  • RestClientConfig : Bean de configuration pour les appels à l’api SNCF pour le SncfRepository.
  • McpServerApplication : La classe d’application principale qui enregistre les outils.
  • application.properties : Configure le serveur MCP pour le transport STDIO et pour gérer l’url de l’api SNCF et notre token.
Arborescence du projet Spring Boot avec des packages pour la configuration, le modèle, le service et le dépôt, incluant des classes telles que JourneySncf, PlaceSncf et McpServerApplication.

Étape 3 : Config

Ajouter cette dépendance dans le pom.xml pour avoir Rest client et désactiver certaines options dans les properties :

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-web</artifactId>

</dependency>

Voici une configuration pour le application.properties :

spring.application.name=mcp_server
# ne démarre pas de serveur web
spring.main.web-application-type=none
spring.ai.mcp.server.name=mcp_server
spring.ai.mcp.server.version=0.0.1
# ces 2 lignes sont necessaires sinon claude donnera des erreurs quand il se
# connectera au serveur MCP
spring.main.banner-mode=off
logging.threshold.console=off

sncf.api.base-url=https://api.sncf.com/v1/coverage/sncf/
sncf.api.token=YOUR_SNCF_TOKEN
logging.level.org.springframework.web.client=DEBUG

On aura besoin d’une config qui scannera nos tools :

import com.zenika.mcp_server.service.SncfService;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class McpServerConfig {

    @Bean
    public List<ToolCallback> danTools(SncfService sncfService) {
        return List.of(ToolCallbacks.from(sncfService));
    }
}

Et une autre pour gérer l’API SNCF :

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {
    @Value("${sncf.api.token}")
    private String apiToken;

    @Value("${sncf.api.base-url}")
    private String apiBaseUrl;

    @Bean
    public RestClient sncfRestClient() {
        return RestClient.builder()
                .baseUrl(apiBaseUrl)
                .defaultHeader("Authorization", apiToken)
                .build();
    }
}

Étape 4 : Définir le modèle de données pour un trajet

Créer un simple record JourneySncf pour représenter les données que l’on récupère de l’endpoint Journey de l’API SNCF :

public record JourneySncf(List<JourneySummary> journeys) {
}

et un second :

public record JourneySummary(String departure_date_time, String arrival_date_time, int duration) {
}

Étape 5 : Définir le repository

On va définir le repository en mettant en entrée de la méthode getJourneys des coordonnées SNCF de ce genre ci : stop_area:SNCF:87471003. Cette coordonnée est l’équivalent de la gare de Rennes.

import com.zenika.mcp_server.repository.model.JourneySncf;
import com.zenika.mcp_server.repository.model.JourneySummary;
import org.springframework.stereotype.Repository;
import org.springframework.web.client.RestClient;

import java.time.LocalDateTime;
import java.util.List;

@Repository
public class SncfRepository {

    private final RestClient restClient;

    public SncfRepository(RestClient restClient) {
        this.restClient = restClient;
    }

    public List<JourneySummary> getJourneys(
            String startSncfCoordinate,
            String endSncfCoordinate
    ) {
        var response = restClient
                .get()
                .uri(uriBuilder ->
                        uriBuilder
                                .path("journeys")
                                .queryParam("from", startSncfCoordinate)
                                .queryParam("to", endSncfCoordinate)
                                .queryParam("datetime", LocalDateTime.now())
                                .build()
                )
                .retrieve()
                .body(JourneySncf.class);

        return response != null ? response.journeys() : List.of();
    }
}

Étape 6 : Définir le tool service

On crée un SncfService qui servira pour nos différents Tools, donc des outils à disposition d’un client MCP pour répondre à un besoin. Ici avec 2 coordonnées l’IA pourra donner à l’utilisateur le trajet le plus proche de la date actuelle et sa durée.

import com.zenika.mcp_server.repository.SncfRepository;
import com.zenika.mcp_server.repository.model.JourneySummary;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;

import java.util.Comparator;

@Service
public class SncfService {

    private final SncfRepository sncfRepository;

    public SncfService(SncfRepository sncfRepository) {
        this.sncfRepository = sncfRepository;
    }

    @Tool(name = "get_journey", description = "Get a journey for a route")
    public JourneySummary getJourney(
            String startSncfCoordinate,
            String endSncfCoordinate
    ) {
        var journeys = sncfRepository.getJourneys(
                startSncfCoordinate,
                endSncfCoordinate
        );
        if (journeys.isEmpty()) {
            throw new RuntimeException("Journey not found");
        }
        return journeys.getFirst();
    }
}

Étape 7 : Build et débugger

Créer le jar qui permet de lancer le serveur mcp :

mvn clean package

Vous verrez alors dans le dossier target :  mcp_server-0.0.1-SNAPSHOT.jar. Si vous voulez debugger lancer cette commande qui permet de mettre à jour la jar quand vous avez modifier le code et de lancer en local MCP inspector. Il nous permet de simuler les tools utiliser par un LLM :

mvn clean install && npx @modelcontextprotocol/inspector -e JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 java -jar target/mcp_server-0.0.1-SNAPSHOT.jar --port 8080 --host localhost

Si le build passe, une fois sur l’interface MCP inspector, cliquez sur connect

Interface utilisateur de MCP inspector montrant les options d'authentification, de configuration, et un bouton de connexion.

Sur intelliJ, crée une configuration et cliquez sur Edit :

Capture d'écran de la configuration d'un projet dans IntelliJ IDEA, montrant des options de configuration et des boutons pour sauvegarder, modifier ou gérer les configurations de projet.

Cliquez sur le + en haut à gauche et sélectionnez Remote JVM Debug et faites la même config :

Configuration de la fenêtre de débogage pour le serveur MCP dans IntelliJ, avec les paramètres du JVM et les options de transport.

Ensuite lancé le mod debug. Puis retourner sur MCP inspector , cliquez sur Tools, puis List Tools et vous verrez vos Tools. Enfin, cliquez sur votre tool et mettez des coordonnées SNCF :

Interface d'une plateforme avec des boutons pour Resources, Prompts et Tools.

Renseignez les informations de la gare de Rennes vers une gare de Paris :

Départ : stop_area:SNCF:87471003

 Arrivé: stop_area:SNCF:87391003

Et cliquez sur Run Tool et vous verrez alors le résultat tout en étant en mod debug avec des points d’arrêt dans intellij.

Étape 8 : Lancer le serveur avec un client MCP

Nous allons utilisez Claude Desktop comme client, il faudra créer une configuration pour que le LLM puisse accéder au serveur MCP :

  • En haut à gauche dans Fichier ->Paramètres -> Développeur -> modifier la config
  • ouvrez claude_desktop_config et coller :
{
  "mcpServers": {
    "mcp-server": {
      "command": "java",

      "args": [
        "-jar",
        "YOUR_BASE_PATH/mcp_server/target/mcp_server-0.0.1-SNAPSHOT.jar"
      ]
    }
  }
}

Puis redémarrer Claude en le tuant complètement. Vous verrez alors dans la configuration du serveur MCP votre config.

Choisissez le LLM sonnet 4.5, puis demander à avoir le prochain trajet entre stop_area:SNCF:87471003 et stop_area:SNCF:87391003, vous verrez alors ceci :

Interface montrant une requête d'IA pour obtenir le prochain trajet entre deux gares SNCF.

L’absence de la précision « En utilisant uniquement le serveur MCP » permettra à Claude de fournir des informations supplémentaires non disponibles via le serveur MCP, comme la ville correspondant à stop_area:SNCF:87471003 ou d’autres données complémentaires.

Et en effet ça serait plus simple de donner une ville plutôt qu’un ID.

Étape 9 : Donnons un second tool à l’IA

Créer un simple record PlaceSncf pour représenter les données d’un lieux qui peut être une gare de l’API SNCF :

public record PlaceSncf(List<PlaceSummary> places){}

et un second :

public record PlaceSummary(String id, String embedded_type, Integer quality) {
}

Ici on va avoir id pour les id SNCF qu’on a déjà vu ensemble, embedded_type pour le type de lieu, exemple stop_area et quality pour la qualité d’un lieu. On récupère ces deux derniers attributs car pour Rennes on va avoir par exemple plusieurs lieux alors que nous on veut la gare de Rennes, donc on filtrera par stop_area et par ordre descendant de quality pour tomber sur les gares. ( conseil obtenu par Claude desktop)

On va créer la méthode du repository :

import com.zenika.mcp_server.repository.model.JourneySncf;
import com.zenika.mcp_server.repository.model.JourneySummary;
import com.zenika.mcp_server.repository.model.PlaceSncf;
import com.zenika.mcp_server.repository.model.PlaceSummary;
import org.springframework.stereotype.Repository;
import org.springframework.web.client.RestClient;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

public Optional<List<PlaceSummary>> getPlaces(String city) {
    var response = restClient
            .get()
            .uri(uriBuilder ->
                    uriBuilder.path("places").queryParam("q", city).build()
            )
            .retrieve()
            .body(PlaceSncf.class);

    return response != null
            ? Optional.of(response.places())
            : Optional.of(List.of());
}

Et le tool service :

import com.zenika.mcp_server.repository.SncfRepository;
import com.zenika.mcp_server.repository.model.JourneySummary;
import com.zenika.mcp_server.repository.model.PlaceSummary;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;

import java.util.Comparator;

@Tool(name = "get_place", description = "Get a coordinate for a city")
public PlaceSummary getPlace(String city) {
    var places = sncfRepository.getPlaces(city);
    return places
            .map(place ->
                    place
                            .stream()
                            .sorted(
                                    Comparator.comparingInt(PlaceSummary::quality).reversed()
                            )
                            .filter(placeSummary ->
                                    placeSummary.embedded_type().equals("stop_area")
                            )
                            .toList()
                            .getFirst()
            )
            .orElseThrow(() -> new RuntimeException("Place not found"));
}

Étape 10 : Posons notre question à Claude

Capture d'écran du serveur MCP demandant à l'IA de donner le prochain train entre Rennes et Paris, affichant les étapes de requêtes et les horaires.

On voit bien qu’ici Claude à compris qu’il devait d’abord récupérer les ID SNCF avant d’avoir le trajet et nous convertit les données reçues en format lisible pour un humain.

Conclusion

Vous avez appris à configurer et à utiliser le SpringBoot MCP Server pour apporter un accès aux données alimenté par l’IA à vos applications. De la création d’un projet Spring Boot à sa connexion avec Claude Desktop ainsi que du debug avec MCP inspector, vous êtes maintenant prêt à construire des intégrations IA plus intelligentes. Vous pourrez aussi construire un serveur MCP en protocole HTTP pour avoir un serveur qui fonctionne pour plusieurs clients et c’est d’ailleurs le prochain tuto.

Auteur/Autrice

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.

En savoir plus sur Blog Zenika

Abonnez-vous pour poursuivre la lecture et avoir accès à l’ensemble des archives.

Poursuivre la lecture