Profile Software Services

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:

¿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:

¿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.

Salir de la versión móvil