¡Compártelo!

MCP con Spring Boot: cómo conectar un asistente de IA a tu backend Java

Si has probado algún asistente de código en los últimos meses, seguro que te has cruzado con MCP. Quienes trabajamos con Java nos hacemos una pregunta concreta: ¿cómo ofrecemos al asistente los servicios que ya tenemos en Spring? MCP con Spring Boot lo resuelve con muy poco código gracias a Spring AI.

Llevo poco tiempo trasteando con MCP. Vengo de diseñar arquitecturas distribuidas, así que lo abordé como un contrato de integración más, igual que una API REST o un servicio gRPC. Exponer la operación fue la parte fácil. Lo que me hizo replantear el diseño fue ver cómo un modelo decidía qué argumentos enviar a mi servicio y cómo reaccionaba a mis errores.

En este artículo veremos cómo exponer un servicio Java como herramienta MCP con Spring Boot y Spring AI, qué pasó en mi primera prueba con un asistente real y qué revisar antes de conectarlo a datos reales.

¿Qué necesitas saber de MCP antes de empezar?

MCP es un protocolo abierto para que una aplicación de IA, el host (Claude Code, un IDE o un chat propio), descubra y use las herramientas que expone un servidor MCP. Para este ejemplo basta con saber qué es una herramienta (tool): una función con nombre, descripción y un esquema JSON de sus argumentos. El modelo lee esa ficha y solicita la llamada; el host la ejecuta y le devuelve el resultado.

La especificación define dos transportes estándar. Con stdio, el asistente lanza el servidor como un proceso local. Con Streamable HTTP, el servidor puede vivir en otra máquina, que es lo habitual en un backend. Usaremos este segundo, y esa decisión pesa mucho en la seguridad.

¿Cómo exponer un servicio como herramienta MCP con Spring Boot?

Usamos Java 21, Spring Boot 4.1.1 y Spring AI 2.0.1, compatibles según la guía de inicio de Spring AI y una tienda ficticia con dos pedidos.

Paso 1: Añadir las dependencias

En el pom.xml importamos el BOM (Bill of Materials) de Spring AI, que fija las versiones de todos sus módulos, y añadimos sin versión el starter del servidor para Spring MVC:

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

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

Paso 2: Configurar el servidor

Elegimos Streamable HTTP y escuchamos solo en local mientras no haya seguridad delante:

spring.application.name=pedidos-mcp
server.address=127.0.0.1
server.port=8080
spring.ai.mcp.server.name=pedidos
spring.ai.mcp.server.protocol=STREAMABLE
spring.ai.mcp.server.type=SYNC

Paso 3: Escribir el servicio y la herramienta

La lógica de negocio vive en un servicio corriente que no sabe nada de MCP:

@Service
public class PedidoService {
    private final Map<String, String> pedidos = Map.of(
        "PED-1042", "EN_PREPARACION",
        "PED-1043", "ENVIADO"
    );

    public String consultarEstado(String pedidoId) {
        if (pedidoId == null || !pedidoId.matches("PED-[0-9]{4}")) {
            throw new IllegalArgumentException("Usa un identificador como PED-1042");
        }
        return pedidos.getOrDefault(pedidoId, "PEDIDO_NO_ENCONTRADO");
    }
}

La herramienta lo envuelve con las anotaciones que documenta la referencia de Spring AI:

@Component
public class PedidoTools {
    private final PedidoService pedidoService;

    public PedidoTools(PedidoService pedidoService) {
        this.pedidoService = pedidoService;
    }

    @McpTool(name = "consultar_estado_pedido",
        description = "Consulta el estado de un pedido por su identificador",
        annotations = @McpTool.McpAnnotations(
            readOnlyHint = true,
            destructiveHint = false,
            openWorldHint = false))
    public String consultarEstado(
        @McpToolParam(description = "Identificador como PED-1042",
            required = true) String pedidoId) {
        return pedidoService.consultarEstado(pedidoId);
    }
}

Al arrancar la aplicación, el endpoint queda en http://127.0.0.1:8080/mcp y puedes probarlo con el MCP Inspector antes de añadir un modelo.

El detalle que casi se me escapa: las anotaciones de la herramienta

En mi primera versión no puse el bloque de anotaciones. Al listar las herramientas vi que Spring AI anunciaba mi consulta con readOnlyHint=false y destructiveHint=true. Son los valores por defecto del esquema de MCP: si el servidor no dice nada, el cliente debe suponer que la herramienta puede modificar o borrar datos.

Mi servicio solo lee, pero el protocolo no lo sabe hasta que se lo dices. Con readOnlyHint = true y openWorldHint = false, la herramienta se anuncia como una lectura sobre un dominio cerrado. Los clientes pueden usar estas pistas para decidir si piden confirmación, aunque la especificación les pide no fiarse de ellas si el servidor no es de confianza.

Mi primera prueba con un asistente real: el pedido número 42

Conecté el servidor a Claude Code, con el modelo Claude Haiku 5.5, y le hice cuatro preguntas:

mcp springboot claude

Los tres primeros casos salieron como esperaba y reconozco que me pareció casi demasiado fácil. Sin identificador, el modelo se lo pidió al usuario y no llamó a la herramienta.

La cuarta pregunta la escribí como lo haría cualquier cliente. En el registro de llamadas vi cómo el modelo probaba PED-42, recibía el error, completaba con ceros y volvía a intentarlo con PED-0042. Ese mensaje de error lo había escrito pensando en un desarrollador que lee un log, y lo estaba usando un modelo para decidir su siguiente paso. Terminó bien porque no encontró nada y lo dijo. Con datos reales, PED-0042 podría ser el pedido de otro cliente.

Solo probé con un asistente y un modelo, pero me dejó dos lecciones:

  • Los mensajes de error son instrucciones para el modelo. Los lee y decide qué hacer a partir de ellos.
  • Los argumentos pueden no venir del usuario. El modelo puede inventarse un identificador, como hizo con PED-0042.

¿Qué revisar antes de conectar datos reales?

Con HTTP, cualquiera que llegue al puerto puede hablar con el servidor. Antes de salir del equipo local, revisa esto:

  • Autenticación en el endpoint. La documentación de Spring AI advierte de que los transportes HTTP exponen el endpoint sin autenticación: cualquiera que lo alcance puede listar e invocar todas las herramientas. Añade una capa delante con Spring Security o MCP Security; la especificación define un framework de autorización basado en OAuth.
  • Permisos sobre cada pedido. El servicio tiene que comprobar que el pedido pertenece a quien pregunta, con la identidad del token; lo que el usuario afirme en el chat no sirve como prueba. Con ese control, el reintento con PED-0042 se queda en un «no encontrado» inofensivo.
  • Errores escritos para el modelo. Un mensaje que describe el formato invita a probar variantes; si prefieres que pregunte al usuario, dilo: «Identificador no válido; solicita al usuario el identificador completo».

¿Cuándo compensa montar un servidor MCP?

Cuando comparé gRPC y REST para microservicios, mi criterio fue elegir el protocolo según quién consume el servicio. Con MCP aplico el mismo.

Un servidor MCP compensa cuando quieres ofrecer la misma operación a varios asistentes compatibles, como el IDE del equipo, un chat interno o un agente propio, sin escribir una integración para cada uno.

Si solo hay un asistente y lo construyes tú con Spring AI, es más sencillo anotar el método con @Tool dentro de esa misma aplicación. Te ahorras otro servicio que desplegar y proteger.

La API REST sigue siendo el contrato para frontends y otros servicios, y puede convivir con MCP reutilizando el mismo PedidoService.

Conclusión

Empecé con MCP pensando que sería otro contrato de integración, y técnicamente lo es: una dependencia, unas líneas de configuración y un método anotado. Lo que cambia es quién está al otro lado: un modelo que interpreta, improvisa y reintenta.

Para tu backend es un cliente más, con una diferencia: puede inventarse argumentos y reaccionar a tus errores de formas que no habías previsto. Por eso merece los mismos permisos que una API pública.

Si te animas a probar MCP con Spring Boot, empieza en local con datos ficticios y dedica más tiempo a hacerle preguntas raras que a escribir código. Las llamadas que haga el modelo te enseñarán dónde falla tu contrato.

¿Quieres llevar estas ideas a la práctica?
Contáctanos y te ayudamos a hacerlo realidad.

Artículos ​ relacionados