Skip to content

Commit 54aa77c

Browse files
committed
improve mcp-annotations and sampling examples
Signed-off-by: Christian Tzolov <[email protected]>
1 parent 934b25d commit 54aa77c

File tree

38 files changed

+2660
-79
lines changed

38 files changed

+2660
-79
lines changed

model-context-protocol/mcp-annotations/README.md

Lines changed: 504 additions & 0 deletions
Large diffs are not rendered by default.

model-context-protocol/mcp-annotations/mcp-annotations-client/src/main/java/org/springframework/ai/mcp/samples/client/McpClientApplication.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,22 +63,22 @@ public CommandLineRunner predefinedQuestions(OpenAiChatModel openAiChatModel,
6363
}
6464

6565
@Bean
66-
List<SyncLoggingSpecification> loggingSpecs(ClientMcpHandlers clientMcpHandlers) {
66+
List<SyncLoggingSpecification> loggingSpecs(McpClientHandlers clientMcpHandlers) {
6767
return SyncMcpAnnotationProvider.createSyncLoggingSpecifications(List.of(clientMcpHandlers));
6868
}
6969

7070
@Bean
71-
List<SyncSamplingSpecification> samplingSpecs(ClientMcpHandlers clientMcpHandlers) {
71+
List<SyncSamplingSpecification> samplingSpecs(McpClientHandlers clientMcpHandlers) {
7272
return SyncMcpAnnotationProvider.createSyncSamplingSpecifications(List.of(clientMcpHandlers));
7373
}
7474

7575
@Bean
76-
List<SyncElicitationSpecification> elicitationSpecs(ClientMcpHandlers clientMcpHandlers) {
76+
List<SyncElicitationSpecification> elicitationSpecs(McpClientHandlers clientMcpHandlers) {
7777
return SyncMcpAnnotationProvider.createSyncElicitationSpecifications(List.of(clientMcpHandlers));
7878
}
7979

8080
@Bean
81-
List<SyncProgressSpecification> progressSpecs(ClientMcpHandlers clientMcpHandlers) {
81+
List<SyncProgressSpecification> progressSpecs(McpClientHandlers clientMcpHandlers) {
8282
return SyncMcpAnnotationProvider.createSyncProgressSpecifications(List.of(clientMcpHandlers));
8383
}
8484

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
import io.modelcontextprotocol.spec.McpSchema.ProgressNotification;
1919

2020
@Service
21-
public class ClientMcpHandlers {
21+
public class McpClientHandlers {
2222

23-
private static final Logger logger = LoggerFactory.getLogger(ClientMcpHandlers.class);
23+
private static final Logger logger = LoggerFactory.getLogger(McpClientHandlers.class);
2424

2525
@McpProgress(clientId = "server1")
2626
public void progressHandler(ProgressNotification progressNotification) {
@@ -36,22 +36,19 @@ public void loggingHandler(LoggingMessageNotification loggingMessage) {
3636

3737
@McpSampling
3838
public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
39-
40-
logger.info(" MCP SAMPLING REQUEST: {}", llmRequest);
39+
logger.info("MCP SAMPLING: {}", llmRequest);
4140

4241
String userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text();
43-
4442
String modelHint = llmRequest.modelPreferences().hints().get(0).name();
4543

4644
return CreateMessageResult.builder()
4745
.content(new McpSchema.TextContent("Response " + userPrompt + " with model hint " + modelHint))
4846
.build();
49-
};
47+
}
5048

5149
@McpElicitation
5250
public ElicitResult elicitationHandler(McpSchema.ElicitRequest request) {
53-
logger.info("MCP ELICITATION REQUEST: {}", request);
54-
51+
logger.info("MCP ELICITATION: {}", request);
5552
return new ElicitResult(ElicitResult.Action.ACCEPT, Map.of("message", request.message()));
5653
}
5754

model-context-protocol/mcp-annotations/mcp-annotations-client/src/main/java/org/springframework/ai/mcp/samples/client/customizers/AnnotationSyncClientCustomizer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626

2727
import io.modelcontextprotocol.client.McpClient.SyncSpec;
2828

29+
/**
30+
* NOTE: This class temporarily here. It will be moved to the Spring-AI project
31+
*/
2932
public class AnnotationSyncClientCustomizer implements McpSyncClientCustomizer {
3033

3134
private final List<SyncSamplingSpecification> syncSamplingSpecifications;

model-context-protocol/sampling/README.md

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,19 @@ The MCP Sampling examples showcase:
1414

1515
## Projects
1616

17-
This directory contains two main projects:
17+
This directory contains several projects demonstrating MCP Sampling:
1818

19-
1. **[mcp-weather-webmvc-server](./mcp-weather-webmvc-server)**: An MCP server that provides weather information and uses MCP Sampling to generate creative content
19+
### Main Projects
20+
1. **[mcp-sampling-server](./mcp-sampling-server)**: An MCP server that provides weather information and uses MCP Sampling to generate creative content
2021
2. **[mcp-sampling-client](./mcp-sampling-client)**: An MCP client that handles sampling requests and routes them to different LLM providers
2122

23+
### Annotation-based Examples
24+
25+
Showcase the same MCP Sampling functionality as the main sampling examples but use the annotation-based approach for simplified development - find more in the **[README](./annotations/README.md)**
26+
27+
3. **[annotations/mcp-sampling-server-annotations](./annotations/mcp-sampling-server-annotations)**: Server implementation using Spring AI's annotation-based approach
28+
4. **[annotations/mcp-sampling-client-annotations](./annotations/mcp-sampling-client-annotations)**: Client implementation using Spring AI's annotation-based approach
29+
2230
## What is MCP Sampling?
2331

2432
MCP Sampling is a powerful capability of the Model Context Protocol that allows:
@@ -74,38 +82,38 @@ The MCP Weather Server implements the server-side of MCP Sampling:
7482

7583
```java
7684
public String callMcpSampling(ToolContext toolContext, WeatherResponse weatherResponse) {
77-
String openAiWeatherPoem = "<no OpenAI poem>";
78-
String anthropicWeatherPoem = "<no Anthropic poem>";
79-
80-
if (toolContext != null && toolContext.getContext().containsKey("exchange")) {
81-
// Spring AI MCP Auto-configuration injects the McpSyncServerExchange into the ToolContext
82-
McpSyncServerExchange exchange = (McpSyncServerExchange) toolContext.getContext().get("exchange");
83-
if (exchange.getClientCapabilities().sampling() != null) {
84-
var messageRequestBuilder = McpSchema.CreateMessageRequest.builder()
85-
.systemPrompt("You are a poet!")
86-
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
87-
new McpSchema.TextContent(
88-
"Please write a poem about this weather forecast (temperature is in Celsius)..."))));
89-
90-
// Request poem from OpenAI
91-
var openAiLlmMessageRequest = messageRequestBuilder
92-
.modelPreferences(ModelPreferences.builder().addHint("openai").build())
93-
.build();
94-
CreateMessageResult openAiLlmResponse = exchange.createMessage(openAiLlmMessageRequest);
95-
openAiWeatherPoem = ((McpSchema.TextContent) openAiLlmResponse.content()).text();
96-
97-
// Request poem from Anthropic
98-
var anthropicLlmMessageRequest = messageRequestBuilder
99-
.modelPreferences(ModelPreferences.builder().addHint("anthropic").build())
100-
.build();
101-
CreateMessageResult anthropicAiLlmResponse = exchange.createMessage(anthropicLlmMessageRequest);
102-
anthropicWeatherPoem = ((McpSchema.TextContent) anthropicAiLlmResponse.content()).text();
103-
}
104-
}
85+
StringBuilder openAiWeatherPoem = new StringBuilder();
86+
StringBuilder anthropicWeatherPoem = new StringBuilder();
87+
88+
McpToolUtils.getMcpExchange(toolContext)
89+
.ifPresent(exchange -> {
90+
if (exchange.getClientCapabilities().sampling() != null) {
91+
var messageRequestBuilder = McpSchema.CreateMessageRequest.builder()
92+
.systemPrompt("You are a poet!")
93+
.messages(List.of(new McpSchema.SamplingMessage(McpSchema.Role.USER,
94+
new McpSchema.TextContent(
95+
"Please write a poem about this weather forecast (temperature is in Celsius). Use markdown format :\n "
96+
+ ModelOptionsUtils.toJsonStringPrettyPrinter(weatherResponse)))));
97+
98+
// Request poem from OpenAI
99+
var openAiLlmMessageRequest = messageRequestBuilder
100+
.modelPreferences(ModelPreferences.builder().addHint("openai").build())
101+
.build();
102+
CreateMessageResult openAiLlmResponse = exchange.createMessage(openAiLlmMessageRequest);
103+
openAiWeatherPoem.append(((McpSchema.TextContent) openAiLlmResponse.content()).text());
104+
105+
// Request poem from Anthropic
106+
var anthropicLlmMessageRequest = messageRequestBuilder
107+
.modelPreferences(ModelPreferences.builder().addHint("anthropic").build())
108+
.build();
109+
CreateMessageResult anthropicAiLlmResponse = exchange.createMessage(anthropicLlmMessageRequest);
110+
anthropicWeatherPoem.append(((McpSchema.TextContent) anthropicAiLlmResponse.content()).text());
111+
}
112+
});
105113

106114
// Combine responses
107-
return "OpenAI poem about the weather: " + openAiWeatherPoem + "\n\n" +
108-
"Anthropic poem about the weather: " + anthropicWeatherPoem + "\n" +
115+
return "OpenAI poem about the weather: " + openAiWeatherPoem.toString() + "\n\n" +
116+
"Anthropic poem about the weather: " + anthropicWeatherPoem.toString() + "\n" +
109117
ModelOptionsUtils.toJsonStringPrettyPrinter(weatherResponse);
110118
}
111119
```
@@ -154,9 +162,9 @@ NOTE: To prevent cyclic dependencies you have to disable MCP tool callbacks auto
154162
### Step 1: Start the MCP Weather Server
155163

156164
```bash
157-
cd mcp-weather-webmvc-server
158-
./mvnw clean install -DskipTests
159-
java -jar target/mcp-sampling-weather-server-0.0.1-SNAPSHOT.jar
165+
cd mcp-sampling-server
166+
./mvnw clean package -DskipTests
167+
java -jar target/mcp-sampling-server-0.0.1-SNAPSHOT.jar
160168
```
161169

162170
### Step 2: Set Environment Variables
@@ -170,7 +178,7 @@ export ANTHROPIC_API_KEY=your-anthropic-key
170178

171179
```bash
172180
cd mcp-sampling-client
173-
./mvnw clean install
181+
./mvnw clean package
174182
java -Dai.user.input='What is the weather in Amsterdam right now?' -jar target/mcp-sampling-client-0.0.1-SNAPSHOT.jar
175183
```
176184

@@ -182,8 +190,10 @@ When you run the application, you'll see creative responses from both OpenAI and
182190

183191
Spring AI provides several MCP client and server implementations:
184192

185-
- **[starter-default-client](../client-starter/starter-default-client)**: A default MCP client implementation using Spring Boot
186-
- **[starter-webflux-client](../client-starter/starter-webflux-client)**: An MCP client implementation using Spring WebFlux
193+
- **[MCP Annotations](../mcp-annotations)**: Examples using Spring AI's annotation-based MCP approach
194+
- **[Weather Server](../weather)**: A simple weather MCP server example
195+
- **[SQLite Server](../sqlite)**: An MCP server that provides SQLite database access
196+
- **[Filesystem Server](../filesystem)**: An MCP server for file system operations
187197

188198
## Additional Resources
189199

0 commit comments

Comments
 (0)