En este último capítulo de la serie de Spring AI veremos los términos importantes a comprender cuando se trabaja con MCP, además de exponer a alto nivel cuál es la arquitectura del MCP. Finalmente, terminaremos explicando la forma de crear aplicaciones que hagan uso de MCP tanto de la parte cliente como de la parte servidora con Spring AI.

A modo de recordatorio, por si te has perdido alguno de los artículos anteriores, puedes echarle un vistazo al resto de contenidos de la serie de Spring AI en los siguientes enlaces:

  1. "Deep Learning" sobre Spring AI: primeros pasos
  2. “Deep Learning” sobre Spring AI: multimodularidad, prompts y observabilidad
  3. "Deep Learning" sobre Spring AI: Advisors, Structured Output y Tool Calling
  4. “Deep Learning” sobre Spring AI: RAG, embeddings y vector databases
  5. “Deep Learning” sobre Spring AI: ETL y MCP

Bases del MCP

El MCP se basa en una arquitectura cliente-servidor donde una aplicación se puede conectar a varios servidores. En esta arquitectura se pueden identificar los siguientes componentes:

your computer: host with mcp client. 1. mcp protocol - mcp server A - local data soruce A. 2. mcp protocol - mcp server b - local data soruce b. 3. mcp protocol - mcp protocol server C - web APIs - remote service C (internet)

Algunos de los conceptos importantes con los que se trabajan en este protocolo son:

Java y Spring

Existe una SDK para Java (también para otros lenguajes como Python, Kotlin, Typescript, C#) que implementa el MCP y que es extendida por el módulo Spring AI MCP, integrando Spring Boot para ofrecer los respectivos starters de cliente y servidor.

La implementación de Java se basa en la siguiente arquitectura de capas:

En la base: stdio, sse… primera columna englobada en la base, de abajo a arriba: MCP transport (client), MCP session, mcp client. en la segunda columna paralela, de abajo a arriba: mcp transport (server), MCP session, mcp client

Cada capa gestiona distintas características:

  1. Capa de cliente/servidor: administra las distintas operaciones de cada parte:
  1. Capa de sesión: gestiona los patrones de comunicación y estado.
  2. Capa de transporte: maneja la serialización-deserialización de mensajes JSON-RPC para varias implementaciones de transporte (stdio, SSE o custom).
Estructura de MCP Server (SSE) y MCP Server (STD IO)

MCP Client

El starter para el cliente de MCP que proporciona Spring AI ofrece la autoconfiguración para crear un cliente MCP en aplicaciones Spring Boot. Además, da soporte a las implementaciones síncrona y asíncrona con varias opciones de transporte. El starter ofrece:

Starters

El starter estándar se conecta simultáneamente a uno o más servidores MCP sobre transportes stdio y/o SSE (este tipo de conexión usa la implementación de transporte de HttpClient). Cada conexión a un servidor MCP crea una nueva instancia de cliente MCP y se puede seleccionar clientes síncronos o asíncronos, pero no de los dos tipos a la vez.

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

El starter de WebFlux ofrece una funcionalidad similar al starter normal pero usando una implementación basada en WebFlux para el SSE.

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>

Propiedades

Las propiedades se dividen en comunes, stdio y SSE. Algunas propiedades relevantes son:

Property Descripción Default
request-timeout Timeout para las peticiones del cliente 20s
type Tipo del servidor (SYNC/ASYNC). Todos los clientes solo pueden ser de un único tipo: sync o async. SYNC
root-change-notification Habilitar las notificaciones de cambios de roots para todos los clientes true
toolcallback.enabled Habilitar la integración del tool callback de MCP con la ejecución de tools de Spring AI true
Property Descripción Default
servers-configuration Recurso que contiene la configuración de los servidores MCP en formato JSON -
connections Mapa con las distintas configuraciones por nombre de las conexiones stdio -
connections.[name].command Comando para ejecutar el servidor MCP -
connections.[name].args Lista de los argumentos del comando -
connections.[name].env Mapa de variables de entorno para el proceso del servidor -

En caso de configurar la propiedad servers-configuration se indicará la ruta en donde se encuentra el fichero con la configuración en formato Claude Desktop (este formato actualmente solo permite configurar conexiones de tipo stdio). Un ejemplo del formato de este fichero es:

{
  "mcpServers": {
    "filesystem": {
      "command": "docker",
      "args": [
        "run",
        "-i",
        "--rm",
        "--mount", "type=bind,src=/home/user/test,dst=/projects/test",
        "mcp/filesystem",
        "/projects/test"
      ]
    }
  }
}
Property Descripción Default
connections Mapa con las distintas configuraciones por nombre de las conexiones sse -
connections.[name].url Url base para la comunicación SSE con el servidor MCP -
connections.[name].sse-endpoint Endpoint sse a usar para la conexión /sse

Funcionalidades

Como hemos comentado, el starter soporta dos tipos de clientes:

Además, la autoconfiguración del starter permite la personalización de varios aspectos del cliente como timeouts, manejo de eventos o procesamiento de mensajes. Estas personalizaciones se podrán configurar mediante la implementación de las clases McpSyncClientCustomizer y McpAsyncClientCustomizer. Algunas personalizaciones disponibles son:

@Component
public class CustomMcpSyncClient implements McpSyncClientCustomizer {
    
    @Override
    public void customize(String serverConfigurationName, McpClient.SyncSpec spec) {      
      RootCapabilities rootCapabilities = new RootCapabilities(true);
      spec.capabilities(new ClientCapabilities(null, rootCapabilities, null));
      // Sets the root URIs that this client can access.
      spec.roots(Arrays.asList(new Root(System.getProperty("user.home") + "/out", "Out foler")));
    }
}

Se permiten varios tipos de transporte:

También es posible la integración con el ciclo de ejecución de “tools” (Tool-Calling) de Spring AI que tiene que ser habilitado mediante la propiedad spring.ai.mcp.client.toolcallback.enabled=true.

MCP Server

El starter para el servidor de MCP que proporciona Spring AI ofrece la autoconfiguración para crear un servidor MCP en aplicaciones Spring Boot. El starter ofrece:

Starters

Se tendrá que seleccionar un starter en función de las necesidades a nivel de capa de transporte:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>

El starter activa la clase McpServerAutoConfiguration ofreciendo:

  1. Configuración de los componentes básicos del servidor
  2. Manejo de las especificaciones de funciones (tools), recursos (resources) y prompts
  3. Gestión de las capacidades del servidor y notificaciones de cambio
  4. Implementaciones síncronas y asíncronas
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

El starter activa las clases McpWebMvcServerAutoConfiguration y McpServerAutoConfiguration ofreciendo:

  1. Transporte HTTP usando Spring MVC
  2. Configuración automática de endpoints SSE
  3. Transporte STDIO habilitado mediante la propiedad spring.ai.mcp.server.stdio=true.
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>

El starter activa las clases McpWebFluxServerAutoConfiguration y McpServerAutoConfiguration ofreciendo:

  1. Transporte reactivo usando Spring WebFlux.
  2. Configuración automática de endpoints SSE.
  3. Transporte STDIO habilitado mediante la propiedad spring.ai.mcp.server.stdio=true

Properties

Algunas de las propiedades importantes para la configuración, y que se tienen que indicar con el prefijo spring.ai.mcp.server, son:

Property Descripción Default
stdio Habilitar/deshabilitar el transporte stdio false
type Tipo del servidor (SYNC/ASYNC) SYNC
resource-change-notification Habilitar las notificaciones de cambios de resources true
prompt-change-notification Habilitar las notificaciones de cambios de prompts true
tool-change-notification Habilitar las notificaciones de cambios de tools true

Tipos

Los servidores pueden ser de tipo síncrono o asíncrono:

Funcionalidades

Este starter, entre otras cosas, permite a los servidores exponer funciones, recursos y prompts a los clientes. Convierte automáticamente los manejadores (handlers) registrados como beans de las funcionalidades mencionadas anteriormente en especificaciones sync/async según el tipo de servidor:

@Bean
ToolCallbackProvider userToolProvider(UserTools userTools) {
   return 
MethodToolCallbackProvider.builder().toolObjects(userTools).build();
}
@Bean
List<McpServerFeatures.SyncResourceSpecification> 
myResources(@Value("classpath:/static/trends.txt") Resource resource) {
    var systemInfoResource = new McpSchema.Resource("file://trends.txt", "Trends", "Fichero con 3 tendencias tecnologicas del año 2025", "text/plain", new Annotations(Arrays.asList(Role.USER), Double.valueOf("0")));
    var resourceSpecification = new 
McpServerFeatures.SyncResourceSpecification(systemInfoResource, (exchange, request) -> {
      try {
         String jsonContent = 
      resource.getContentAsString(StandardCharsets.UTF_8);
         return new McpSchema.ReadResourceResult(List.of(new 
      McpSchema.TextResourceContents(request.uri(), "text/plain", jsonContent)));
      } catch (Exception e) {
         throw new RuntimeException("Failed to generate system info", e);
      }
   });
   return List.of(resourceSpecification);
}
@Bean
List<McpServerFeatures.SyncPromptSpecification> myPrompts() {
   var prompt = new McpSchema.Prompt("search-user-by-name", "Prompt to search a user by name", List.of(new McpSchema.PromptArgument("name", "The user's name", true)));

   var promptSpecification = new 
McpServerFeatures.SyncPromptSpecification(prompt, 
      (exchange, getPromptRequest) -> {
         String nameArgument = (String) 
      getPromptRequest.arguments().get("name");
         if (nameArgument == null) {nameArgument = "friend";}
         var userMessage = new PromptMessage(Role.USER, new 
      TextContent("Existe el usuario " + nameArgument + "?"));
         return new GetPromptResult("Prompt to search a user by name", List.of(userMessage));
      });        
      return List.of(promptSpecification);
}
@Bean
BiConsumer<McpSyncServerExchange, List<McpSchema.Root>> rootsChangeHandler(){
    return (exchange, roots) -> {
        log.info("Cambio en los roots recibido: {}", roots);
    };
}

En referencia a los tipos de transporte, los expuestos por estos starters son:

Demo

Para comprobar el funcionamiento de ambos starters, creamos dos proyectos/aplicaciones, uno para el cliente y otro para el servidor:

  1. Cliente: mediante la la configuración de properties, indicamos dos servidores:
spring:
  ...
    mcp:
      client:
        toolcallback:
          enabled: true
        type: SYNC
        sse:
          connections:
            usuarios:
              url: http://localhost:8081
        stdio:
          connections:
            files:
              command: docker
              args:
                - 'run'
                - '-i'
                - '--rm'
                - '--mount'
                - 'type=bind,src=/home/user/test,dst=/projects/test'
                - 'mcp/filesystem'
                - '/projects/test'
  1. Servidor: ofrece una función (tool) para obtener una lista de usuarios, además de exponer recursos (resources) y prompts.

Para la ejecución de los endpoints será necesario levantar en primer lugar la aplicación del servidor MCP y después la del cliente MCP (el servidor de file-system se levanta con Docker automáticamente al levantar el cliente MCP).

En la app que actúa como cliente MCP se exponen los siguientes endpoints:

Estás en el directorio '/projects/test'. Tienes los archivos 'README.md', 'code.txt' y 'hola' en esta carpeta.
Mismo listado de archivos por comando
Los usuarios disponibles son: Jose, Lucia, David, Marga. ¿Necesitas algo más?
"name": "listUsers", "description": "devuelve una lista de usuarios", "inputSchema": { "type": "object", "properties": 13, "required": [], "additionalProperties": false }
"resources":  "uri": "file://trends.txt", "name": "Trends", "description": "Fichero con 3 tendencias tecnologicas del año 2025", "mimeType": "text/plain", "annotations": { "audience":  "user" . "priority": 0.0 } } }
"contents":  "uri": "file://trends.txt", "mimeType": "text/plain", "text": "1- La voz y los videos en la IA\n2 - Accesibilidad: desarrollando aplicaciones inclusivas\n3 - De Digital Native a AI Native: la nueva era de la IA en los negocios"
"prompts":  "name": "search-user-by-name", "description": "Prompt to search a user by name", "arguments":  "name": "name", "description": "The user's name", "required": true 10
"role": "user", "content": { "type": "text", "text": "Existe el usuario Juan?" }
Cambio en los roots recibido: Root uri=/home/simonrodriguez/springai, name=Spring AI foler, Root uri=/home/simonrodriguez/out, name=Out foler

En estos enlaces se encuentran los códigos de ejemplo del cliente y servidor MCP.

Conclusión

Hasta aquí el último capítulo de la serie de Spring AI. En este caso hemos explorado los conceptos importantes del MCP así como verificar qué funcionalidades ofrecen los starters de MCP con un ejemplo de implementación.

A lo largo de esta serie centrada en Spring AI hemos podido comprobar todas las funcionalidades para crear no solo aplicaciones de chat al uso, sino también otro tipo de aplicaciones que puedan contener diversos flujos de trabajo y tareas ayudándonos de un LLM para poder mantener una interfaz unificada de interacción a través de lenguaje natural.

A pesar de estar todavía en las primeras versiones, podemos comprobar la potencia del framework, siempre teniendo en cuenta las capacidades de los LLMs que usemos para crear nuevas aplicaciones centradas en la IA. Tampoco debemos olvidar que seguramente el equipo de Spring irá aumentando nuevas funcionalidades en este módulo a medida que los LLMs sigan evolucionando.

Es por todo esto que es importante prestar atención, más que nunca, a las últimas novedades para no quedarnos atrás en esta nueva etapa tecnológica, ya que todo lo relacionado con la IA avanza a un ritmo todavía más vertiginoso que antes. ¡Os leo en comentarios!

Referencias

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete