- Add note in documentation about preventing cyclic dependencies - Disable mcp tool callback auto-configuration: spring.ai.mcp.client.toolcallback.enabled=false Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
231 lines
8.0 KiB
Markdown
231 lines
8.0 KiB
Markdown
# Spring AI MCP Sampling Client
|
|
|
|
This project demonstrates how to implement a client for the MCP (Model Context Protocol) Sampling capability using Spring AI. It showcases how to route LLM requests to different providers (OpenAI and Anthropic) based on model preferences.
|
|
|
|
## Overview
|
|
|
|
The MCP Sampling Client:
|
|
- Connects to an MCP server using SSE (Server-Sent Events) transport
|
|
- Implements a sampling handler that routes requests to different LLM providers
|
|
- Integrates with both OpenAI and Anthropic models
|
|
- Demonstrates how to use model hints to select the appropriate LLM
|
|
- Combines creative responses from multiple LLMs into a single result
|
|
- Provides logging capabilities for MCP operations
|
|
|
|
## MCP Sampling Implementation
|
|
|
|
MCP Sampling is a powerful capability that allows an MCP server to delegate certain requests to LLM providers. This client implements the client-side handling of sampling requests:
|
|
|
|
1. **Sampling Handler Registration**: The client registers a sampling handler using the `McpSyncClientCustomizer`:
|
|
|
|
```java
|
|
@Bean
|
|
McpSyncClientCustomizer samplingCustomizer(Map<String, ChatClient> chatClients) {
|
|
return (name, mcpClientSpec) -> {
|
|
|
|
mcpClientSpec = mcpClientSpec.loggingConsumer(logingMessage -> {
|
|
System.out.println("MCP LOGGING: [" + logingMessage.level() + "] " + logingMessage.data());
|
|
});
|
|
|
|
mcpClientSpec.sampling(llmRequest -> {
|
|
var userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text();
|
|
String modelHint = llmRequest.modelPreferences().hints().get(0).name();
|
|
|
|
// Find the appropriate chat client based on the model hint
|
|
ChatClient hintedChatClient = chatClients.entrySet().stream()
|
|
.filter(e -> e.getKey().contains(modelHint)).findFirst()
|
|
.orElseThrow().getValue();
|
|
|
|
// Generate response using the selected model
|
|
String response = hintedChatClient.prompt()
|
|
.system(llmRequest.systemPrompt())
|
|
.user(userPrompt)
|
|
.call()
|
|
.content();
|
|
|
|
return CreateMessageResult.builder().content(new McpSchema.TextContent(response)).build();
|
|
});
|
|
System.out.println("Customizing " + name);
|
|
};
|
|
}
|
|
```
|
|
|
|
2. **Model Routing**: The client uses the model hint from the request to select the appropriate LLM provider:
|
|
- If the hint is "openai", it routes to the OpenAI model
|
|
- If the hint is "anthropic", it routes to the Anthropic model
|
|
|
|
3. **Chat Client Management**: The client creates and manages multiple chat clients, one for each LLM provider:
|
|
|
|
```java
|
|
@Bean
|
|
public Map<String, ChatClient> chatClients(List<ChatModel> chatModels) {
|
|
return chatModels.stream().collect(Collectors.toMap(
|
|
model -> model.getClass().getSimpleName().toLowerCase(),
|
|
model -> ChatClient.builder(model).build()));
|
|
}
|
|
```
|
|
|
|
4. **Integration with Spring AI**: The client leverages Spring AI's auto-configuration to set up the necessary components:
|
|
|
|
```java
|
|
var mcpToolProvider = new SyncMcpToolCallbackProvider(mcpClients);
|
|
|
|
ChatClient chatClient = ChatClient.builder(openAiChatModel)
|
|
.defaultToolCallbacks(mcpToolProvider)
|
|
.build();
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
The project requires the following Spring AI dependencies:
|
|
|
|
```xml
|
|
<dependencies>
|
|
<dependency>
|
|
<groupId>org.springframework.ai</groupId>
|
|
<artifactId>spring-ai-starter-mcp-client</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-model-anthropic</artifactId>
|
|
</dependency>
|
|
</dependencies>
|
|
```
|
|
|
|
## Configuration
|
|
|
|
### Application Properties
|
|
|
|
The application is configured through `application.properties`:
|
|
|
|
```properties
|
|
spring.application.name=mcp
|
|
spring.main.web-application-type=none
|
|
|
|
# Disable the chat client auto-configuration because we are using multiple chat models
|
|
spring.ai.chat.client.enabled=false
|
|
|
|
# API keys for LLM providers
|
|
spring.ai.openai.api-key=${OPENAI_API_KEY}
|
|
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
|
|
|
|
# MCP server connection
|
|
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8080
|
|
|
|
# Logging configuration
|
|
logging.level.io.modelcontextprotocol.client=WARN
|
|
logging.level.io.modelcontextprotocol.spec=WARN
|
|
|
|
#Disable MCP tool callbacks
|
|
spring.ai.mcp.client.toolcallback.enabled=false
|
|
```
|
|
|
|
## How It Works
|
|
|
|
The application demonstrates MCP Sampling with a weather-related query:
|
|
|
|
1. The client connects to the MCP Weather Server
|
|
2. It sends a weather-related question: "What is the weather in Amsterdam right now?"
|
|
3. The server retrieves the weather data and sends sampling requests to the client
|
|
4. The client routes each request to the appropriate LLM based on model hints
|
|
5. Each LLM generates a creative poem about the weather
|
|
6. The server combines the responses and returns them to the user
|
|
|
|
## Running the Application
|
|
|
|
1. First, start the MCP Weather Server:
|
|
```bash
|
|
cd ../mcp-weather-webmvc-server
|
|
./mvnw clean install -DskipTests
|
|
java -jar target/mcp-sampling-weather-server-0.0.1-SNAPSHOT.jar
|
|
```
|
|
|
|
2. Set the required environment variables:
|
|
```bash
|
|
export OPENAI_API_KEY=your-openai-key
|
|
export ANTHROPIC_API_KEY=your-anthropic-key
|
|
```
|
|
|
|
3. Build and run the MCP Sampling Client:
|
|
```bash
|
|
cd ../mcp-sampling-client
|
|
./mvnw clean install
|
|
java -Dai.user.input='What is the weather in Amsterdam right now?' -jar target/mcp-sampling-client-0.0.1-SNAPSHOT.jar
|
|
```
|
|
|
|
The application will:
|
|
1. Connect to the MCP Weather Server
|
|
2. Process the weather query
|
|
3. Handle sampling requests from the server
|
|
4. Route each request to the appropriate LLM
|
|
5. Display the combined creative responses
|
|
|
|
## Sample Output
|
|
|
|
When you run the application, you'll see output similar to:
|
|
|
|
```
|
|
> USER: What is the weather in Amsterdam right now?
|
|
Please incorporate all createive responses from all LLM providers.
|
|
After the other providers add a poem that synthesizes the poems from all the other providers.
|
|
|
|
> ASSISTANT: I checked the current weather in Amsterdam for you. Here are the creative responses from different AI providers:
|
|
|
|
OpenAI poem about the weather:
|
|
# Amsterdam's Embrace
|
|
|
|
In Amsterdam, where canals reflect the sky,
|
|
A gentle warmth of sixteen degrees goes by.
|
|
The autumn air, a crisp and tender touch,
|
|
Caresses faces of the Dutch.
|
|
|
|
Time stands still at this perfect hour,
|
|
As sunshine breaks through with gentle power.
|
|
The city breathes with calm delight,
|
|
In this moment, everything feels right.
|
|
|
|
Anthropic poem about the weather:
|
|
## Amsterdam Today
|
|
|
|
Sixteen degrees in Amsterdam's embrace,
|
|
A perfect autumn day unfolds with grace.
|
|
The canals reflect the passing clouds above,
|
|
As the city hums with life and love.
|
|
|
|
Neither cold nor warm, but just between,
|
|
The perfect weather for this Dutch scene.
|
|
Time captured in this moment's gentle hold,
|
|
As Amsterdam's story continues to unfold.
|
|
|
|
My synthesis of these weather poems:
|
|
# Amsterdam's Gentle Harmony
|
|
|
|
Where canals mirror skies in Dutch design,
|
|
Sixteen degrees - a temperature divine.
|
|
Neither cold nor warm, but perfectly between,
|
|
The autumn air paints Amsterdam's scene.
|
|
|
|
Time seems suspended in this golden hour,
|
|
As gentle sunshine shows its subtle power.
|
|
The city breathes, alive with calm delight,
|
|
In this moment, everything feels just right.
|
|
|
|
Amsterdam's story, continuing to unfold,
|
|
Embraces all within its gentle hold.
|
|
A perfect day that poets can't ignore,
|
|
In this beautiful city we all adore.
|
|
|
|
Current weather data shows the temperature is 16°C in Amsterdam right now.
|
|
```
|
|
|
|
## Additional Resources
|
|
|
|
* [Spring AI Documentation](https://docs.spring.io/spring-ai/reference/)
|
|
* [MCP Client Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html)
|
|
* [Model Context Protocol Specification](https://modelcontextprotocol.github.io/specification/)
|
|
* [Spring Boot Documentation](https://docs.spring.io/spring-boot/docs/current/reference/html/)
|