Skip to content

Commit 5a0fafe

Browse files
authored
feat: Sample Code for MCP Support (#513)
* MCP PoC * cleanup * Update to a working example with the AI SDK repository itself * Move to a Spring Profile * Linting
1 parent 511447b commit 5a0fafe

File tree

6 files changed

+116
-0
lines changed

6 files changed

+116
-0
lines changed

sample-code/spring-app/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Finally, you can start the sample app:
2727

2828
Head to http://localhost:8080 in your browser to see all available endpoints.
2929

30+
To test the MCP integration, run with `mvn spring-boot:run -Dspring-boot.run.arguments="--spring.profiles.active=mcp"`:
31+
3032
## Run the E2E Test
3133

3234
Trigger the [GitHub Action](https://github.com/SAP/ai-sdk-java/actions/workflows/e2e-test.yaml).

sample-code/spring-app/pom.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,24 @@
107107
<groupId>org.springframework.ai</groupId>
108108
<artifactId>spring-ai-client-chat</artifactId>
109109
</dependency>
110+
<dependency>
111+
<groupId>org.springframework.ai</groupId>
112+
<artifactId>spring-ai-mcp</artifactId>
113+
<scope>runtime</scope>
114+
</dependency>
115+
<dependency>
116+
<groupId>org.springframework.ai</groupId>
117+
<artifactId>spring-ai-autoconfigure-mcp-client</artifactId>
118+
<scope>runtime</scope>
119+
<exclusions>
120+
<exclusion>
121+
<!-- MCP brings 3.4.5 which conflicts with our version 3.5.0.
122+
Since we bring the necessary Spring dependencies, we can just exclude the transitive dependency here -->
123+
<groupId>org.springframework.boot</groupId>
124+
<artifactId>spring-boot-starter</artifactId>
125+
</exclusion>
126+
</exclusions>
127+
</dependency>
110128
<dependency>
111129
<groupId>org.springframework.boot</groupId>
112130
<artifactId>spring-boot-autoconfigure</artifactId>

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ Object toolCalling(
131131
return text.isEmpty() ? message.getToolCalls().toString() : text;
132132
}
133133

134+
@GetMapping("/mcp")
135+
Object mcp(@Nullable @RequestParam(value = "format", required = false) final String format) {
136+
val response = service.toolCallingMcp();
137+
138+
if ("json".equals(format)) {
139+
return ((OrchestrationSpringChatResponse) response)
140+
.getOrchestrationResponse()
141+
.getOriginalResponse();
142+
}
143+
final AssistantMessage message = response.getResult().getOutput();
144+
final String text = message.getText();
145+
return text.isEmpty() ? message.getToolCalls().toString() : text;
146+
}
147+
134148
@GetMapping("/chatMemory")
135149
Object chatMemory(
136150
@Nullable @RequestParam(value = "format", required = false) final String format) {

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.sap.ai.sdk.app.services;
22

33
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GEMINI_1_5_FLASH;
4+
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O;
45
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O_MINI;
56

67
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -24,11 +25,16 @@
2425
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
2526
import org.springframework.ai.chat.memory.InMemoryChatMemoryRepository;
2627
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
28+
import org.springframework.ai.chat.messages.SystemMessage;
29+
import org.springframework.ai.chat.messages.UserMessage;
2730
import org.springframework.ai.chat.model.ChatModel;
2831
import org.springframework.ai.chat.model.ChatResponse;
2932
import org.springframework.ai.chat.prompt.Prompt;
3033
import org.springframework.ai.chat.prompt.PromptTemplate;
3134
import org.springframework.ai.support.ToolCallbacks;
35+
import org.springframework.ai.tool.ToolCallbackProvider;
36+
import org.springframework.beans.factory.annotation.Autowired;
37+
import org.springframework.beans.factory.annotation.Value;
3238
import org.springframework.stereotype.Service;
3339
import reactor.core.publisher.Flux;
3440

@@ -40,6 +46,13 @@ public class SpringAiOrchestrationService {
4046
new OrchestrationModuleConfig().withLlmConfig(GPT_4O_MINI);
4147
private final OrchestrationChatOptions defaultOptions = new OrchestrationChatOptions(config);
4248

49+
@Nullable
50+
@Autowired(required = false)
51+
ToolCallbackProvider toolCallbackProvider;
52+
53+
@Value("${spring.profiles.active:}")
54+
private String activeProfile;
55+
4356
/**
4457
* Chat request to OpenAI through the Orchestration service with a simple prompt.
4558
*
@@ -172,6 +185,41 @@ public ChatResponse toolCalling(final boolean internalToolExecutionEnabled) {
172185
return client.call(prompt);
173186
}
174187

188+
/**
189+
* Example using an MCP client to use a file system tool. Enabled via dedicated Spring profile,
190+
* since it requires an actual MCP server to run.
191+
*
192+
* @return the assistant response object
193+
*/
194+
@Nonnull
195+
public ChatResponse toolCallingMcp() {
196+
// check if spring profile is set to 'mcp'
197+
if (!activeProfile.equals("mcp")) {
198+
throw new IllegalStateException(
199+
"The 'mcp' Spring profile is not active. Set it, e.g. by passing a JVM parameter '-Dspring.profiles.active=mcp'.");
200+
}
201+
if (toolCallbackProvider == null) {
202+
throw new IllegalStateException(
203+
"No MCP clients were found. Ensure that you configured the clients correctly in the application.yaml file.");
204+
}
205+
// GPT-4o-mini doesn't work too well with the file system tool, so we use 4o here
206+
val options = new OrchestrationChatOptions(config.withLlmConfig(GPT_4O));
207+
options.setToolCallbacks(List.of(toolCallbackProvider.getToolCallbacks()));
208+
options.setInternalToolExecutionEnabled(true);
209+
210+
val sys =
211+
new SystemMessage(
212+
"""
213+
Please read through the markdown files in my file system.
214+
Ensure to first query the allowed directories.
215+
Then use any `.md` files you find to answer the user's question.
216+
Do **NOT** query for `*.md` since that doesn't work, ensure to query for `.md` instead.""");
217+
val usr = new UserMessage("How can I use Spring AI with the SAP AI SDK?");
218+
219+
val prompt = new Prompt(List.of(sys, usr), options);
220+
return client.call(prompt);
221+
}
222+
175223
/**
176224
* Chat request to OpenAI through the Orchestration service using chat memory.
177225
*
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#---
2+
spring:
3+
config.activate.on-profile:
4+
mcp
5+
ai:
6+
mcp:
7+
client:
8+
# refer to https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html#_starters
9+
type: SYNC
10+
stdio:
11+
connections:
12+
# allows file system access to the current directory
13+
# requires npx to run
14+
# refer to https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem
15+
localFS:
16+
command: npx
17+
args:
18+
- "-y"
19+
- "@modelcontextprotocol/server-filesystem"
20+
- "."
21+
env:
22+
DEBUG: "true"

sample-code/spring-app/src/main/resources/static/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,18 @@ <h5 class="mb-1">Orchestration Integration</h5>
776776
</div>
777777
</div>
778778
</li>
779+
<li class="list-group-item">
780+
<div class="info-tooltip">
781+
<button type="submit"
782+
formaction="/spring-ai-orchestration/mcp"
783+
class="link-offset-2-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover endpoint">
784+
<code>/spring-ai-orchestration/mcp</code>
785+
</button>
786+
<div class="tooltip-content">
787+
Use an MCP file system server as tool to answer questions about the SDK itself. ⚠️ Only works if the server is started with the "mcp" Spring profile ⚠️.
788+
</div>
789+
</div>
790+
</li>
779791
<li class="list-group-item">
780792
<div class="info-tooltip">
781793
<button type="submit"

0 commit comments

Comments
 (0)