Notas prácticas: Spring AI + MCP: Exponga sus herramientas Java a cualquier cliente de IA (incluso
Guía paso a paso práctica: Spring AI + MCP: Exponga sus herramientas en Java a cualquier cliente de IA (incluso contratos, verificaciones y espacios para código integrable para equipos que implementan este patrón).
Esta guía reconstruye el proceso desde las materias primas hasta un sistema funcional para: Spring AI + MCP: Exponer sus herramientas en Java a cualquier cliente de IA (incluso Claude Desktop). El enfoque está en pasos operativos claros, verificaciones explícitas y código que puede integrarse directamente en un repositorio sin necesidad de adivinar su propósito.
Una guía práctica sobre el Protocolo de Contexto de Modelo (MCP) con Spring AI 2.0. Cree un servidor MCP que exponga herramientas de pago a través de SSE, y luego un cliente de IA independiente que las descubra e invoque; no se requieren definiciones de herramientas en el lado del cliente.
Para la guía práctica A, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Mantenga la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin necesidad de leer todo el sistema. Prefiera salidas estructuradas con validación de esquema sobre texto libre cuando el siguiente paso sea escribir código o realizar una llamada a una herramienta.
El problema
En la fase de “El Problema”, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las intentonas, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Autentique en la pasarela y vuelva a autorizarlo en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias.
¿Qué es MCP?
En la fase de ¿Qué es MCP?, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Prefiera unidades pequeñas y verificables en lugar de scripts extensos. Cuando un paso falla, el error debe indicar una única responsabilidad y no un proceso complicado. Autentique en la pasarela de entrada y vuelva a autorizarlo en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias.
Qué estamos construyendo
En la fase de “Lo que estamos construyendo”, defina las entradas, el responsable de cada paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido, sin tener que adivinar el estado oculto. Considere esta fase como un contrato entre las entradas y los resultados validados. Asigne nombres a los artefactos, defina verificaciones de éxito y rechace las completaciones parciales silenciosas. Autentíquese en la pasarela y vuelva a autorizarse en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias.
Requisitos previos
En la fase de Requisitos previos, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Registre los tiempos de ejecución y el costo de tokens o consultas junto con los resultados funcionales. La visibilidad temprana del costo evita facturas inesperadas cuando el proceso pasa de entornos de demostración a entornos compartidos. Autentíquese en la pasarela y vuelva a autorizarse en el plano de datos. Un token portador por sí solo no constituye un límite entre tenencias. En la fase de Requisitos previos, defina las entradas, el responsable del paso y los criterios de finalización antes de modificar el código. Los operadores deben poder volver a ejecutar el paso a partir de un punto de control conocido sin tener que adivinar el estado oculto. Documente tanto la ruta óptima como la ruta de recuperación. Las intentonas repetidas, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras realizadas posteriormente.
Configuración del proyecto
Al trabajar en la fase de configuración del proyecto, anote primero el contrato: los datos necesarios, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables a scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar agentes sin esa huella desperdicia horas.
POM padre
Al trabajar en la fase del POM padre, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Trate esta fase como un contrato entre los datos de entrada y los resultados validados. Asigne nombres a los artefactos, defina comprobaciones de éxito y rechace las completaciones parciales silenciosas. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar sin ese rastro desperdicia horas.
<groupId>com.anupam</groupId>
<artifactId>spring-ai-mcp</artifactId>
<packaging>pom</packaging>
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.0</spring-ai.version>
</properties>
<modules>
<module>mcp-server</module>
<module>mcp-client</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Parte 1: Construyendo el servidor MCP
Al trabajar en la Parte 1, “Construyendo el escenario”, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar bucles sin ese historial desperdicia horas.
Dependencias del servidor
Al trabajar en la etapa de Dependencias del Servidor, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de datos secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar sin ese rastro desperdicia horas.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>
</dependencies>
Configuración del Servidor
Al trabajar en la etapa de configuración del servidor, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Documente junto con ello el camino óptimo y el camino de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar agentes sin ese rastro desperdicia horas.
spring:
ai:
mcp:
server:
name: payment-tools-server
version: 1.0.0
description: "MCP server exposing payment lookup and exchange rate tools"
server:
port: 8081
Defina herramientas con @Tool
Al trabajar en la etapa de Definir herramientas con herramientas, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar bucles de agentes sin esa huella desperdicia horas.
@Component
public class PaymentMcpTools {
@Tool(description = "Get the current status and details of a payment by its transaction ID")
public PaymentInfo getPaymentStatus(
@ToolParam(description = "Transaction ID, e.g. TXN-9042") String transactionId) {
PaymentInfo info = payments.get(transactionId);
if (info == null) {
throw new RuntimeException("Payment not found: " + transactionId);
}
return info;
}
@Tool(description = "Get the current exchange rate between two currencies. " +
"Supported: USD, EUR, GBP, JPY, INR, CAD, AUD")
public ExchangeRate getExchangeRate(
@ToolParam(description = "Source currency code, e.g. USD") String from,
@ToolParam(description = "Target currency code, e.g. EUR") String to) {
// ... conversion logic
return new ExchangeRate(from, to, rate, LocalDateTime.now());
}
@Tool(description = "Calculate the total amount in a target currency for a given payment")
public String convertPaymentAmount(
@ToolParam(description = "Transaction ID") String transactionId,
@ToolParam(description = "Target currency code") String targetCurrency) {
PaymentInfo payment = getPaymentStatus(transactionId);
ExchangeRate rate = getExchangeRate(payment.currency(), targetCurrency);
BigDecimal converted = payment.amount().multiply(rate.rate());
return String.format("%s %s = %s %s", payment.amount(), payment.currency(),
converted, targetCurrency);
}
}
Iniciar el servidor
Al trabajar en la etapa de Iniciar el servidor, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de un fallo parcial. Esa lista de verificación ayuda a mantener honestas las futuras modificaciones del código. Considere esta etapa como un contrato entre los datos de entrada y las salidas validadas. Asigne nombres a los artefactos, defina comprobaciones de éxito y evite completaciones parciales silenciosas. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar sin ese historial desperdicia horas.
cd mcp-server
./mvnw spring-boot:run
Parte 2: Creación del cliente MCP
Al trabajar en la Parte 2, “Construyendo el escenario”, anote primero el contrato: los datos de entrada requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Registre los tiempos y el costo de tokens o consultas junto con los resultados funcionales. Tener visibilidad del costo desde el principio evita facturas inesperadas cuando el proceso pasa de la versión de demostración a entornos compartidos. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar bucles sin ese historial desperdicia horas.
Dependencias del cliente
Al trabajar en la fase de Dependencias del Cliente, anote primero el contrato: las entradas requeridas, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación mantiene honestas las futuras modificaciones del código. Guarde la configuración fuera del código de la aplicación. Los archivos de entorno, los almacenes de secretos y las banderas de funcionalidad deben estar en un lugar donde los operadores puedan auditarlos sin tener que leer todo el sistema. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar agentes sin ese rastro desperdicia horas.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
</dependencies>
Configuración del Cliente
Al trabajar en la fase de configuración del cliente, anote primero el contrato: los datos requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestos los cambios posteriores en el código. Documente tanto la ruta óptima como la ruta de recuperación. Las reintentos, los controles humanos y el manejo de mensajes no entregados forman parte del producto, no son mejoras posteriores. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar agentes sin ese historial desperdicia horas.
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o
temperature: 0.3
mcp:
client:
sse:
connections:
payment-tools:
url: http://localhost:8081
server:
port: 8080
Conectar herramientas MCP a ChatClient
Al trabajar con las herramientas Wire MCP para llevarlas a producción, anote primero el contrato: los parámetros requeridos, la señal de éxito y qué ocurre en caso de fallo parcial. Esa lista de verificación ayuda a mantener honestos los cambios posteriores en el código. Prefiera unidades pequeñas y probables sobre scripts extensos. Cuando un paso falla, el fallo debe apuntar a una única responsabilidad y no a un proceso complicado. Registre el nombre de la herramienta, el hash de los argumentos, la latencia y el resultado de cada llamada. Depurar bucles de agentes sin esa información desperdicia horas.
@Configuration
public class AiConfig {
@Bean
public ChatClient chatClient(ChatModel chatModel, SyncMcpToolCallbackProvider mcpTools) {
return ChatClient.builder(chatModel)
.defaultSystem("""
You are a helpful payments assistant. You have access to tools
provided by an MCP server. Use them to look up payment status,
get exchange rates, and convert payment amounts.
""")
.defaultTools(mcpTools)
.build();
}
}
El controlador (no se necesita conocimiento de herramientas)
@RestController
@RequestMapping("/api/v1/assistant")
public class AssistantController {
private final ChatClient chatClient;
public AssistantController(ChatClient chatClient) {
this.chatClient = chatClient;
}
@PostMapping("/chat")
public ResponseEntity<ChatResponse> chat(@Valid @RequestBody ChatRequest request) {
String answer = chatClient.prompt()
.user(request.message())
.call()
.content();
return ResponseEntity.ok(ChatResponse.of(answer));
}
}
Pruebas
1. Inicie ambas aplicaciones
# Terminal 1 — MCP Server
cd mcp-server
./mvnw spring-boot:run
# Terminal 2 - MCP Client
cd mcp-client
export OPENAI_API_KEY=sk-your-key-here
./mvnw spring-boot:run
2. Realice consultas a través del cliente
curl -X POST http://localhost:8080/api/v1/assistant/chat \
-H "Content-Type: application/json" \
-d '{"message": "What is the status of payment TXN-9042?"}'
{
"answer": "Payment TXN-9042 has been completed. It was $250.00 USD sent from Alice Johnson to Bob Smith on August 20, 2026.",
"timestamp": "2026-08-22T11:00:00"
}
3. Utilice una herramienta que enlace otras herramientas
curl -X POST http://localhost:8080/api/v1/assistant/chat \
-H "Content-Type: application/json" \
-d '{"message": "Convert TXN-9043 amount to EUR"}'
{
"answer": "Payment TXN-9043 is $1,200.50 USD. At the current rate of 0.92, that equals approximately €1,104.46 EUR.",
"timestamp": "2026-08-22T11:00:05"
}
MCP frente a herramientas locales: cuándo usar cada uno
Combinar herramientas locales y MCP
chatClient.prompt()
.user(question)
.tools(localTools, mcpTools) // Both in one call
.call()
.content();
Opciones de transporte
Ejemplo destdio (conexión a un servidor MCP basado en npm):
spring:
ai:
mcp:
client:
stdio:
connections:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
Consideraciones de seguridad
// Only allow specific tools from the MCP server
@Bean
McpToolFilter mcpToolFilter() {
return (serverName, toolName) ->
Set.of("getPaymentStatus", "getExchangeRate").contains(toolName);
}
Problemas comunes
Ejemplo completo funcional
git clone https://github.com/AnupamSinha/spring-boot-examples/tree/main/06-ai-mcp
cd spring-ai-mcp
# Terminal 1 - Start server
cd mcp-server && ../mvnw spring-boot:run
# Terminal 2 - Start client
cd mcp-client && export OPENAI_API_KEY=sk-... && ../mvnw spring-boot:run
Referencias
Lista de verificación operativa
Lecturas relacionadas
- Notas prácticas: Receta Spring AI: Proteger un servidor MCP con OAuth — Guía paso a paso de las Notas prácticas: Receta Spring AI: Proteger un servidor MCP con OAuth, incluyendo contratos, verificaciones y espacios de código listos para usar por los equipos que implementan este patrón.