eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

When working with Model Context Protocol (MCP), there are scenarios where MCP servers need additional details from users during tool execution that weren’t included in the original request. Without a standardized way to request this information, the tool has no way to communicate this back to the client and fails to execute.

MCP Elicitations address this issue by allowing the MCP server to pause and explicitly request the missing information from the user. This enables us to build interactive tools capable of dynamically gathering additional context.

In this tutorial, we’ll explore how to implement MCP Elicitations using Spring AI.

2. Creating an MCP Server

Let’s start by building an MCP server that exposes a tool for fetching author details.

We’ll design this tool to conditionally trigger an elicitation request for additional details if they are missing from the original request.

2.1. Dependencies and Configuration

Let’s start by adding the necessary dependencies to our project’s pom.xml file:

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

We import Spring AI’s MCP server dependency, which provides the necessary classes for creating a custom HTTP-based MCP server.

Next, let’s edit the application.properties file to configure our application as an MCP server:

spring.ai.mcp.server.name=author-server
spring.ai.mcp.server.type=SYNC
spring.ai.mcp.server.protocol=streamable

Here, we configure a name for our MCP server, set it to be synchronous, and specify the transport type as streamable HTTP.

2.2. Defining a Tool

Next, let’s define a tool that our MCP server will expose.

We’ll create an AuthorRepository class that provides a method to fetch author details using an article title. If the requested article is a premium one, we’ll elicit additional information from the user before returning the author details:

private static final Logger log = LoggerFactory.getLogger(AuthorRepository.class);

@McpTool(description = "Get Baeldung author details using an article title")
Author getAuthorByArticleTitle(
    @McpToolParam(description = "Title/name of the article") String articleTitle,
    @McpToolParam(required = false, description = "Name of user requesting author information") String username,
    @McpToolParam(required = false, description = "Reason for requesting author information") String reason,
    McpSyncRequestContext requestContext
) {
    log.info("Author requested for article: {}", articleTitle);
    if (isPremiumArticle(articleTitle)) {
        log.info("Article is premium, further information required");
        if ((isBlank(username) || isBlank(reason)) && requestContext.elicitEnabled()) {
            log.info("Required details missing, initiating elicitation");
            StructuredElicitResult<PremiumArticleAccessRequest> elicitResult = requestContext.elicit(
                e -> e.message("Baeldung username and reason required."),
                PremiumArticleAccessRequest.class
            );
            if (McpSchema.ElicitResult.Action.ACCEPT.equals(elicitResult.action())) {
                username = elicitResult.structuredContent().username();
                reason = elicitResult.structuredContent().reason();
                log.info("Elicitation accepted - username: {}, reason: {}", username, reason);
            }
        }
        if (isSubscriber(username) && isValidReason(reason)) {
            log.info("Access granted, returning author details");
            return new Author("John Doe", "[email protected]");
        }
    }
    return null;
}

record Author(String name, String email) {
}

record PremiumArticleAccessRequest(String username, String reason) {
}

We annotate our getAuthorByArticleTitle() method with the @McpTool annotation to expose it as an MCP tool. The method accepts the articleTitle as a required parameter, along with optional username and reason parameters.

Additionally, we inject the McpSyncRequestContext as a method parameter, which provides access to the current request’s metadata and enables us to initiate elicitation requests back to the MCP client.

If the requested article is premium and any of the optional parameters are missing, we use the elicit() method to trigger an elicitation request. We pass a message explaining what information is needed and a schema defining the expected response structure.

We should also note that before initiating this elicitation request, we call the elicitEnabled() method to check whether the connected MCP client supports elicitation, since attempting to elicit from an unsupported client would result in an error.

If the user accepts the elicitation request and provides valid details, we extract the username and reason from the result and proceed with the authorization checks before returning hardcoded author details.

For our demonstration, the isPremiumArticle(), isSubscriber(), and isValidReason() private methods always return true.

3. Creating an MCP Host

Now that we have our MCP server ready, we need an application to consume it.

We’ll be building a chatbot using Anthropic’s Claude model, which will act as our MCP host. Alternatively, we can use a local LLM via Hugging Face or Ollama, as the specific AI model is irrelevant for this demonstration.

We’ll be creating a new Spring Boot application in this section.

3.1. Dependencies and Configuring an LLM

First, let’s include the necessary dependency in our pom.xml file:

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

The Anthropic starter dependency is a wrapper around the Anthropic Message API, and we’ll use it to interact with the Claude model in our application.

Additionally, we import the MCP client starter dependencywhich will allow us to configure clients inside our Spring Boot application that maintain 1:1 connections with the MCP servers.

Next, let’s configure our Anthropic API key and chat model in the application.properties file:

spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
spring.ai.anthropic.chat.options.model=claude-opus-4-5-20251101

We use the ${} property placeholder to load the value of our API Key from an environment variable.

Additionally, we specify Claude Opus 4.5 by Anthropic, using the claude-opus-4-5-20251101 model ID. Feel free to explore and use a different model based on requirements.

With these two properties set, Spring AI automatically creates a bean of type ChatModel, allowing us to interact with the specified model.

3.2. Configuring an MCP Client and Enabling MCP Elicitation

Finally, to use our custom MCP server in our chatbot application, we need to configure an MCP client against it:

spring.ai.mcp.client.capabilities.elicitation={}
spring.ai.mcp.client.streamable-http.connections.author-server.url=http://localhost:8081/mcp

In our application.properties file, we first enable MCP elicitation, which allows the MCP servers to send back an elicitation request if a tool requires additional information.

Next, we configure a new client against our custom MCP server using the streamable HTTP transport type. Our configuration assumes the MCP server to be running at http://localhost:8081/mcp. We need to make sure to update the url property if it’s running on a different host or port.

During application startup, Spring AI will scan our configuration, create the MCP client, and establish a connection with the corresponding MCP server. Additionally, it creates a bean of type SyncMcpToolCallbackProvider, which provides a list of all the tools exposed by the configured MCP servers.

3.3. Building a Basic Chatbot

With our LLM and MCP client configured, let’s build a simple chatbot.

We’ll start by creating a bean of type ChatClient using the auto-configured ChatModel and SyncMcpToolCallbackProvider beans:

@Bean
ChatClient chatClient(ChatModel chatModel, SyncMcpToolCallbackProvider toolCallbackProvider) {
    return ChatClient
      .builder(chatModel)
      .defaultToolCallbacks(toolCallbackProvider.getToolCallbacks())
      .build();
}

The ChatClient class will act as our main entry point for interacting with our chat completion model, i.e., Claude Opus 4.5.

Next, let’s inject the ChatClient bean in a controller class and expose a REST API:

@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) {
}

Here, we simply pass the user’s question to the chatClient bean and return the LLM’s response. We’ll use this API endpoint to interact with our chatbot later in the tutorial.

3.4. Handling Elicitation Requests

When the MCP server initiates an elicitation request, the MCP client must have a mechanism to intercept this request and provide the necessary data.

Let’s define a method in our configuration class to handle these requests:

private static final Logger log = LoggerFactory.getLogger(ChatbotConfiguration.class);

@McpElicitation(clients = "author-server")
ElicitResult handleElicitation(ElicitRequest elicitRequest) {
    log.info("Elicitation requested: {}", elicitRequest.message());
    log.info("Requested schema: {}", elicitRequest.requestedSchema());

    return new ElicitResult(
      ElicitResult.Action.ACCEPT,
      Map.of(
        "username", "john.smith",
        "reason", "Contacting author for article feedback"
      )
    );
}

We annotate our handleElicitation() method with @McpElicitation, specifying the clients attribute as author-server to link it to the client we configured earlier in our application.properties file.

When the MCP server triggers an elicitation request, this handler receives the ElicitRequest containing the message and requested schema.

In our handler, we log the elicitation details and return an ElicitResult with the ACCEPT action along with the requested details. For our demonstration, we’re returning hardcoded values. In a production application, the MCP client would typically prompt the user for this information.

It’s worth noting that MCP Elicitation also supports a URL mode, where the server directs users to an external URL to request sensitive details or perform protected actions. This ensures sensitive data never passes through the MCP client. However, at the time of this writing, Spring AI does not support URL mode elicitation.

4. Interacting With Our Chatbot

Now that we’ve built our MCP server and host application, let’s interact with our chatbot and test the elicitation flow.

We’ll use the HTTPie CLI to invoke the chatbot’s API endpoint:

http POST :8080/chat question="Who wrote the article 'Testing CORS in Spring Boot?' on Baeldung, and how can I contact them?"

Here, we send a simple question asking for details regarding the author who wrote a specific article. We deliberately don’t specify the username or reason in our query to trigger the elicitation flow on the MCP server.

Let’s see what we get as a response:

{
    "answer": "The article 'Testing CORS in Spring Boot' on Baeldung was written by John Doe. You can contact him via email at [[email protected]](mailto:[email protected])."
}

As we can see, the chatbot successfully returns the author’s details.

Let’s look at the logs of our chatbot to confirm the elicitation request was received:

[2026-01-28 13:16:25] [INFO] [c.b.s.m.c.ChatbotConfiguration] - Elicitation requested: Baeldung username and reason required.
[2026-01-28 13:16:25] [INFO] [c.b.s.m.c.ChatbotConfiguration] - Requested schema: {type=object, properties={reason={type=string}, username={type=string}}, required=[reason, username]}

The logs confirm that our elicitation handler received the request from the MCP server, including the message and the expected schema for the response.

Now, let’s also examine the logs from our MCP server to confirm the complete flow:

[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Author requested for article: Testing CORS in Spring Boot
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Article is premium, further information required
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Required details missing, initiating elicitation
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Elicitation accepted - username: john.smith, reason: Contacting author for article feedback
[2026-01-28 15:28:00] [INFO] [c.b.s.m.s.AuthorRepository] - Access granted, returning author details

Here, the tool detected that the article is premium, initiated an elicitation request to gather the missing parameters, received the hardcoded values from our elicitation handler, and finally executed the tool logic to return the author details.

5. Conclusion

In this article, we explored how to implement MCP Elicitations with Spring AI.

We built an MCP server exposing a tool that requests additional information when accessing premium content. Then, we created an MCP host application with an elicitation handler that responds to these requests.

Finally, we tested our implementation and verified the elicitation flow through the application logs. This pattern enables more interactive AI applications where tools can gather additional context from the user when needed.

As always, all the code examples used in this article are available over on GitHub.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
guest
0 Comments
Oldest
Newest
Inline Feedbacks
View all comments