Spring AI Guide to MCP Elicitations
As AI systems become more interactive, handling incomplete or ambiguous user input becomes critical for building reliable applications. MCP elicitations provide a structured way to bridge this gap by enabling real-time clarification during execution. Let us understand Spring AI MCP elicitations and how they help handle incomplete inputs in a more interactive and reliable way during AI-driven workflows.
1. Understanding MCP Elicitations
In the world of Generative AI, the Model Context Protocol (MCP) has become the “USB port” for Large Language Models. While standard tool calling allows an LLM to execute functions, it often fails if the user provides incomplete information. MCP Elicitations solve this by allowing the Server to talk back to the Client during a tool execution. Instead of the tool simply failing because a parameter is missing, the server “elicits” the missing information from the user. This creates a seamless “human-in-the-loop” interaction where the AI can clarify requirements—like asking for a coffee size or a flight date—before completing the task.
2. Building MCP Elicitations with Spring AI
In this section, we will walk through a complete working example of MCP elicitations using Spring AI. We will first set up the required dependencies, then build the MCP server and host, and finally see how the interaction flows end-to-end. For brevity, we will focus only on the important classes relevant to this article and skip standard boilerplate code.
2.1 Project Setup and Dependencies
You can quickly bootstrap this project using Spring Initializr by selecting Java 21, Spring Boot, and adding WebFlux as a dependency. Once the base project is generated, include the Spring AI MCP dependencies shown below to enable server and client capabilities.
<project>
<properties>
<java.version>21</java.version>
<spring-ai.version>2.0.0-M3</spring-ai.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
</dependencies>
<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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
This configuration uses Java 21 and Spring AI version 2.0.0-M3. The spring-ai-starter-mcp-server-webflux dependency enables building an MCP server using reactive WebFlux, while spring-ai-starter-mcp-client allows the application to act as an MCP client/host for interacting with servers. The spring-ai-openai-spring-boot-starter integrates OpenAI models for LLM capabilities. The spring-boot-starter-webflux provides reactive web support for building non-blocking APIs. Additionally, the spring-ai-bom manages compatible versions of all Spring AI dependencies, and the Spring Milestones repository is included to fetch milestone releases.
2.2 application.yml
This configuration sets up the OpenAI LLM integration and defines the MCP server metadata for tool communication.
spring:
ai:
openai:
api-key: ${OPENAI_API_KEY}
chat:
options:
model: gpt-4o-mini
mcp:
server:
name: pizza-server
version: 1.0.0
Spring Boot automatically maps the spring.ai.openai configuration to an OpenAI chat model, which is injected into the ChatClient.Builder used in the ChatbotService. This means no explicit wiring is required to connect the LLM. Additionally, the mcp.server.name property (“pizza-server”) is used to identify the MCP server and must match the value specified in the @McpElicitation annotation. This ensures that elicitation requests from the correct server are routed to the appropriate handler in the host application.
2.2.1 Code Explanation
This configuration connects the application to an OpenAI chat model using the provided API key and specifies the model to be used for processing user queries. You can obtain an OpenAI API key by signing up on the OpenAI platform, navigating to the API keys section, and generating a new secret key, which can then be exported as an environment variable. Additionally, it registers the MCP server with a unique name and version, allowing the client (host) to correctly identify and interact with it during tool execution and elicitation workflows.
2.3 Implementing the MCP Server
The MCP Server is the provider of the logic. Using Spring AI, we can define a tool that requests additional details if they aren’t provided in the initial LLM call.
// Dependency: spring-ai-starter-mcp-server-webflux
@Configuration
class PizzaMcpServerConfig {
@Bean
public ToolCallbackProvider pizzaTools() {
return MethodToolCallbackProvider.builder()
.toolObjects(new PizzaOrderService())
.build();
}
}
class PizzaOrderService {
@McpTool(description = "Place a pizza order")
public String orderPizza(
@McpArg(description = "The type of pizza") String pizzaType,
@McpArg(description = "The size of the pizza", required = false) String size,
McpSyncRequestContext context) {
// If the LLM didn't provide a size, we elicit it!
if (size == null || size.isBlank()) {
var elicited = context.elicit(Map.of(
"size", "What size would you like? (Small, Medium, Large)"
));
size = (String) elicited.get("size");
}
return "Confirmed: " + size + " " + pizzaType + " pizza is on the way!";
}
}
2.3.1 Code Explanation
This Spring AI configuration defines a Model Context Protocol (MCP) server where the PizzaMcpServerConfig class registers a ToolCallbackProvider bean to expose the logic within PizzaOrderService as executable tools for an LLM. The orderPizza method, annotated with @McpTool, allows the AI to facilitate orders by accepting parameters like the pizza type and an optional size, but the standout feature is the use of McpSyncRequestContext to handle incomplete information. If the size parameter is missing, the code invokes context.elicit(), which sends a structured request back to the host application to prompt the user for the missing detail, waits for the response, and then dynamically populates the variable to return a final confirmation, demonstrating a seamless way to manage human-in-the-loop interactions within a microservice architecture.
2.4 Implementing the MCP Host (Chatbot)
The MCP Host (your chatbot) connects to the server and handles the elicitation requests. It serves as the intermediary between the LLM and the user.
// Dependency: spring-ai-starter-mcp-client
@Service
public class ChatbotService {
private final ChatClient chatClient;
public ChatbotService(ChatClient.Builder builder, SyncMcpToolCallbackProvider mcpTools) {
this.chatClient = builder
.defaultToolCallbacks(mcpTools.getToolCallbacks())
.build();
}
@McpElicitation(clients = {"pizza-server"})
public McpSchema.ElicitResult handleElicitation(McpSchema.ElicitRequest request) {
String question = (String) request.requestedSchema().get("description");
System.out.println("SERVER ASKS: " + question);
// Replace this with UI / API input in real apps
Scanner scanner = new Scanner(System.in);
System.out.print("USER INPUT: ");
String userResponse = scanner.nextLine();
return new McpSchema.ElicitResult(Map.of("size", userResponse));
}
public String ask(String userQuery) {
return chatClient.prompt(userQuery).call().content();
}
}
2.4.1 Code Explanation
This ChatbotService acts as the MCP Host and integrates the LLM using Spring AI’s ChatClient. The SyncMcpToolCallbackProvider automatically registers tool callbacks exposed by the MCP server, allowing the LLM to invoke them as part of its reasoning process. The @McpElicitation annotation defines a handler for elicitation requests coming from the “pizza-server”; when the server detects missing input (such as pizza size), it sends a request that is intercepted by the handleElicitation method. This method extracts the question, prompts the user (simulated here via console input), and returns the response wrapped in an ElicitResult. The ask method serves as the entry point, sending user queries to the LLM, which can then orchestrate tool execution and elicitation seamlessly within a single interaction flow.
2.5 REST Controller to Trigger Chat
This REST controller exposes an endpoint to interact with the chatbot and trigger the LLM-driven MCP workflow.
@RestController
@RequestMapping("/chat")
public class ChatController {
private final ChatbotService chatbotService;
public ChatController(ChatbotService chatbotService) {
this.chatbotService = chatbotService;
}
@GetMapping
public String chat(@RequestParam String message) {
return chatbotService.ask(message);
}
}
2.5.1 Code Explanation
This controller defines a simple HTTP GET endpoint (/chat) that accepts a user message as a request parameter and forwards it to the ChatbotService. The service then invokes the LLM via ChatClient, which can dynamically call MCP tools and handle elicitation if required. This setup provides a minimal API layer to test the complete conversational flow end-to-end.
2.6 Running the Application and Output
Run the application using the following commands after setting your OpenAI API key:
export OPENAI_API_KEY=your_api_key_here mvn spring-boot:run
When a user initiates a request like “I want to order a Pepperoni pizza,” the system does not fail or make assumptions about missing parameters. Instead, it enters a structured interaction loop powered by the MCP protocol.
- Intent Detection: The LLM identifies the
orderPizzatool as the correct action but realizes thesizeargument is missing from the user’s prompt. - Server Call: The Client (Host) invokes the tool on the MCP Server.
- Elicitation Trigger: Inside the Server, the
context.elicit()method pauses execution and sends a JSON-RPC request back to the Host asking for the missing “size”. - Host Interception: The Host’s
@McpElicitationhandler captures this request, prompts the user (or retrieves input), and sends the response back to the Server. - Resumption: The Server receives the missing data, resumes execution, and returns the final result to the LLM.
This entire interaction occurs within a single logical execution flow, avoiding multiple LLM turns and reducing latency compared to traditional retry-based approaches.
User -> Client -> LLM -> Server (Elicit) -> Client (Handler) -> User -> Server (Execute) -> LLM -> User
Below is a sample interaction demonstrating how missing information is dynamically resolved:
User: I want to order a Pepperoni pizza. [System Internals: LLM calls orderPizza(pizzaType="Pepperoni")] [System Internals: Server sends Elicitation Request for "size"] SERVER ASKS: What size would you like? (Small, Medium, Large) USER INPUT: Large AI Response: Confirmed: Large Pepperoni pizza is on the way!
In this flow, the system detects missing input (pizza size), pauses execution, asks for clarification, and then resumes processing once the required information is provided—ensuring accurate and complete responses without relying on assumptions.
2.6.1 Why MCP Elicitations Matter
In traditional REST-based tool calling, the LLM would either have to hallucinate a size (bad UX) or the tool would return an error message like “Error: size is required,” forcing the LLM to start a whole new conversation turn. MCP elicitations keep the tool execution warm, reduce latency, and ensure data integrity by handling clarification at the protocol level rather than the application level.
3. Conclusion
MCP Elicitations bring a new level of robustness to Spring AI applications. By allowing tools to “ask back,” we move away from fragile tool calls that require perfect user prompts and toward natural, conversational agents. Whether you’re building complex enterprise workflows or a simple coffee bot, elicitations ensure that your AI has the context it needs to succeed. This shift from rigid tool calls to adaptive, conversational workflows is a key step toward building truly intelligent AI systems.




