1. Visión general
Al trabajar con el Protocolo de Contexto de Modelo (MCP), existen escenarios en los que los servidores MCP necesitan detalles adicionales de los usuarios durante la ejecución de una herramienta que no fueron incluidos en la solicitud original. Sin una forma estandarizada de solicitar esta información, la herramienta no tiene manera de comunicarlo al cliente y falla al ejecutarse.
MCP Elicitaciones abordan este problema permitiendo que el servidor MCP se detenga y solicite explícitamente la información faltante al usuario. Esto nos permite construir herramientas interactivas capaces de recopilar dinámicamente contexto adicional.
En este tutorial, exploraremos cómo implementar MCP Elicitaciones usando Spring AI.
2. Creando un Servidor MCP
Comencemos construyendo un servidor MCP que exponga una herramienta para obtener detalles del autor.
Diseñaremos esta herramienta para que desencadene condicionalmente una solicitud de elicitación cuando falten detalles de la solicitud original.
2.1. Dependencias y Configuración
Añadamos las dependencias necesarias al archivo pom.xml de nuestro proyecto:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<version>1.1.2</version>
</dependency>
Importamos la dependencia del servidor MCP de Spring AI, que proporciona las clases necesarias para crear un servidor MCP basado en HTTP.
A continuación, editemos el archivo application.properties para configurar nuestra aplicación como servidor MCP:
spring.ai.mcp.server.name=author-server
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.protocol=streamable
Aquí configuramos un nombre para nuestro servidor MCP, lo marcamos como síncrono y especificamos el tipo de transporte como HTTP transmisible.
2.2. Definiendo una Herramienta
Definamos una herramienta que nuestro servidor MCP expondrá.
Crearemos una clase AuthorRepository que ofrece un método para obtener detalles del autor usando el título de un artículo. Si el artículo solicitado es premium, solicitaremos información adicional al usuario antes de devolver los detalles del autor:
private static final Logger log = LoggerFactory.getLogger(AuthorRepository.class);
@McpTool(description = "Obtener los detalles del autor de CodeJa usando el título del artículo")
Author getAuthorByArticleTitle(
@McpToolParam(description = "Título/nombre del artículo") String articleTitle,
@McpToolParam(required = false, description = "Nombre de usuario que solicita información del autor") String username,
@McpToolParam(required = false, description = "Razón para solicitar información del autor") String reason,
McpSyncRequestContext requestContext
) {
log.info("Autor solicitado para artículo: {}", articleTitle);
if (isPremiumArticle(articleTitle)) {
log.info("El artículo es premium, se requiere más información");
if ((isBlank(username) || isBlank(reason)) && requestContext.elicitEnabled()) {
log.info("Detalles requeridos faltan, iniciando elicitación");
StructuredElicitResult<PremiumArticleAccessRequest> elicitResult = requestContext.elicit(
e -> e.message("Se requiere nombre de usuario y razón de CodeJa."),
PremiumArticleAccessRequest.class
);
if (McpSchema.ElicitResult.Action.ACCEPT.equals(elicitResult.action())) {
username = elicitResult.structuredContent().username();
reason = elicitResult.structuredContent().reason();
log.info("Elicitación aceptada - usuario: {}, razón: {}", username, reason);
}
}
if (isSubscriber(username) && isValidReason(reason)) {
log.info("Acceso concedido, devolviendo detalles del autor");
return new Author("John Doe", "/cdn-cgi/l/email-protection");
}
}
return null;
}
record Author(String name, String email) {}
record PremiumArticleAccessRequest(String username, String reason) {}
Anotamos nuestro método getAuthorByArticleTitle() con la anotación @McpTool para exponerlo como una herramienta MCP. El método acepta articleTitle como parámetro obligatorio, junto con los parámetros opcionales username y reason.
Además, inyectamos el McpSyncRequestContext como parámetro del método, lo que nos brinda acceso a los metadatos de la solicitud actual y nos permite iniciar solicitudes de elicitación al cliente MCP.
Si el artículo solicitado es premium y alguno de los parámetros opcionales falta, usamos el método elicit() para desencadenar una solicitud de elicitación. Pasamos un mensaje explicando qué información se necesita y un esquema que define la estructura de respuesta esperada.
También debemos notar que, antes de iniciar esta solicitud de elicitación, llamamos a elicitEnabled() para verificar si el cliente MCP conectado soporta elicitación, ya que intentar elicitar desde un cliente no compatible resultaría en un error.
Si el usuario acepta la solicitud de elicitación y proporciona detalles válidos, extraemos username y reason del resultado y procedemos con las verificaciones de autorización antes de devolver los detalles del autor codificados.
Para nuestra demostración, los métodos privados isPremiumArticle(), isSubscriber() e isValidReason() devuelven siempre true.
3. Creando un Host MCP
Ahora que tenemos nuestro servidor MCP listo, necesitamos una aplicación que lo consuma.
Construiremos un chatbot usando el modelo Claude de Anthropic, que actuará como nuestro host MCP. Alternativamente, podemos usar un LLM local a través de Hugging Face o Ollama, ya que el modelo AI específico es irrelevante para esta demostración.
Crearemos una nueva aplicación Spring Boot en esta sección.
3.1. Dependencias y Configuración de un LLM
Primero, incluimos la dependencia necesaria en nuestro archivo pom.xml:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-anthropic</artifactId>
<version>1.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
<version>1.1.2</version>
</dependency>
La dependencia de inicio de Anthropic es un envoltorio alrededor de la API de Mensajes de Anthropic, y la usaremos para interactuar con el modelo Claude en nuestra aplicación.
Además, importamos la dependencia de inicio del cliente MCP, que nos permitirá configurar clientes dentro de nuestra aplicación Spring Boot que mantengan conexiones 1:1 con los servidores MCP.
A continuación, configuramos la clave API de Anthropic y el modelo de chat en el archivo application.properties:
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
spring.ai.anthropic.chat.options.model=claude-opus-4-5-20251101
Usamos el marcador de posición ${} para cargar el valor de nuestra clave API desde una variable de entorno.
También especificamos Claude Opus 4.5 de Anthropic, usando el ID de modelo claude-opus-4-5-20251101. Siéntete libre de explorar y usar un modelo diferente según los requisitos.
Con estas dos propiedades configuradas, Spring AI crea automáticamente un bean de tipo ChatModel, permitiéndonos interactuar con el modelo especificado.
3.2. Configurando un Cliente MCP y Habilitando la Elicitación MCP
Finalmente, para usar nuestro servidor MCP personalizado en nuestra aplicación de chatbot, necesitamos configurar un cliente MCP contra él:
spring.ai.mcp.client.capabilities.elicitation={}
spring.ai.mcp.client.streamable-http.connections.author-server.url=http://localhost:8081/mcp
En nuestro archivo application.properties, primero habilitamos la elicitación MCP, lo que permite que los servidores MCP envíen una solicitud de elicitación si una herramienta requiere información adicional.
A continuación, configuramos un nuevo cliente contra nuestro servidor MCP personalizado usando el tipo de transporte HTTP transmisible. Nuestra configuración asume que el servidor MCP se ejecuta en http://localhost:8081/mcp. Necesitamos asegurarnos de actualizar la propiedad url si se ejecuta en un host o puerto diferente.
Durante el inicio de la aplicación, Spring AI escaneará nuestra configuración, creará el cliente MCP y establecerá una conexión con el servidor MCP correspondiente. Además, crea un bean de tipo SyncMcpToolCallbackProvider, que proporciona una lista de todas las herramientas expuestas por los servidores MCP configurados.
3.3. Construyendo un Chatbot Básico
Con nuestro LLM y cliente MCP configurados, construyamos un chatbot simple.
Comenzaremos creando un bean de tipo ChatClient usando los beans ChatModel y SyncMcpToolCallbackProvider auto-configurados:
@Bean
ChatClient chatClient(ChatModel chatModel, SyncMcpToolCallbackProvider toolCallbackProvider) {
return ChatClient
.builder(chatModel)
.defaultToolCallbacks(toolCallbackProvider.getToolCallbacks())
.build();
}
La clase ChatClient actuará como nuestro punto de entrada principal para interactuar con nuestro modelo de completado de chat, es decir, Claude Opus 4.5.
A continuación, inyectemos el bean ChatClient en una clase controlador y exponamos una API REST:
@PostMapping("/chat")
ResponseEntity<ChatResponse> chat(@RequestBody ChatRequest chatRequest) {
String answer = chatClient
.prompt()
.user(chatRequest.question())
.call()
.content();
return ResponseEntity.ok(new ChatResponse(answer));
}
record ChatRequest(String question) {}
record ChatResponse(String answer) {}
Aquí simplemente pasamos la question del usuario al bean chatClient y devolvemos la respuesta del LLM. Usaremos este punto final de API para interactuar con nuestro chatbot más adelante en el tutorial.
3.4. Manejo de Solicitudes de Elicitación
Cuando el servidor MCP inicia una solicitud de elicitación, el cliente MCP debe contar con un mecanismo para interceptar esta solicitud y proporcionar los datos necesarios.
Definamos un método en nuestra clase de configuración para manejar estas solicitudes:
private static final Logger log = LoggerFactory.getLogger(ChatbotConfiguration.class);
@McpElicitation(clients = "author-server")
ElicitResult handleElicitation(ElicitRequest elicitRequest) {
log.info("Solicitada elicitación: {}", elicitRequest.message());
log.info("Esquema solicitado: {}", elicitRequest.requestedSchema());
return new ElicitResult(
ElicitResult.Action.ACCEPT,
Map.of(
"username", "john.smith",
"reason", "Contactando al autor para retroalimentación sobre el artículo"
)
);
}
Anotamos nuestro método handleElicitation() con @McpElicitation, especificando el atributo clients como author-server para vincularlo al cliente que configuramos anteriormente en nuestro application.properties.
Cuando el servidor MCP dispara una solicitud de elicitación, este manejador recibe la ElicitRequest que contiene el mensaje y el esquema solicitado.
En nuestro manejador, registramos los detalles de la elicitación y devolvemos un ElicitResult con la acción ACCEPT junto con los detalles solicitados. Para nuestra demostración, devolvemos valores codificados. En una aplicación de producción, el cliente MCP normalmente pediría al usuario esta información.
Cabe destacar que MCP Elicitation también admite un modo URL, donde el servidor dirige a los usuarios a una URL externa para solicitar datos sensibles o realizar acciones protegidas. Esto asegura que los datos sensibles nunca pasen por el cliente MCP. Sin embargo, al momento de escribir esto, Spring AI no soporta la elicitación en modo URL.
4. Interactuando con Nuestro Chatbot
Ahora que hemos construido nuestro servidor MCP y la aplicación host, interactuemos con nuestro chatbot y probemos el flujo de elicitación.
Usaremos la CLI HTTPie para invocar el punto final de API del chatbot:
http POST :8080/chat question="¿Quién escribió el artículo 'Testing CORS in Spring Boot?' en CodeJa, y cómo puedo contactarlos?"
Enviamos una pregunta simple solicitando detalles sobre el autor que escribió un artículo específico. No especificamos el nombre de usuario ni la razón en nuestra consulta para desencadenar el flujo de elicitación en el servidor MCP.
Veamos lo que obtenemos como respuesta:
{
"answer": "El artículo 'Testing CORS in Spring Boot' en CodeJa fue escrito por John Doe. Puedes contactarlo vía email en [/cdn-cgi/l/email-protection](mailto:/cdn-cgi/l/email-protection)."
}
Como se puede observar, el chatbot devuelve correctamente los detalles del autor.
Examinemos los registros de nuestro chatbot para confirmar que se recibió la solicitud de elicitación:
[2026-01-28 13:16:25] [INFO] [c.b.s.m.c.ChatbotConfiguration] - Solicitada elicitación: CodeJa username and reason required.
[2026-01-28 13:16:25] [INFO] [c.b.s.m.c.ChatbotConfiguration] - Esquema solicitado: {type=object, properties={reason={type=string}, username={type=string}}, required=[reason, username]}
Los registros confirman que nuestro manejador de elicitación recibió la solicitud del servidor MCP, incluyendo el mensaje y el esquema esperado para la respuesta.
Ahora, también examinemos los registros de nuestro servidor MCP para confirmar el flujo completo:
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Autor solicitado para artículo: Testing CORS in Spring Boot
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - El artículo es premium, se requiere más información
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Detalles requeridos faltan, iniciando elicitación
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Elicitación aceptada - usuario: john.smith, razón: Contacting author for article feedback
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Acceso concedido, devolviendo detalles del autor
Aquí, la herramienta detectó que el artículo es premium, inició una solicitud de elicitación para recopilar los parámetros faltantes, recibió los valores codificados de nuestro manejador de elicitación y finalmente ejecutó la lógica de la herramienta para devolver los detalles del autor.
5. Conclusión
En este artículo, exploramos cómo implementar MCP Elicitations con Spring AI.
Construimos un servidor MCP que expone una herramienta que solicita información adicional al acceder a contenido premium. Luego, creamos una aplicación host MCP con un manejador de elicitación que responde a estas solicitudes.
Finalmente, probamos nuestra implementación y verificamos el flujo de elicitación a través de los registros de la aplicación. Este patrón permite aplicaciones AI más interactivas donde las herramientas pueden recopilar contexto adicional del usuario cuando sea necesario.
Newsletter Semanal de Java
Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.
Sin spam. Cancela cuando quieras.