Skip to content

Commit 65018cd

Browse files
committed
samples: simplify clients and remove "InMemoryRepo"
Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
1 parent 28b95e3 commit 65018cd

File tree

7 files changed

+60
-223
lines changed

7 files changed

+60
-223
lines changed

samples/sample-mcp-client-webflux/src/main/java/org/springaicommunity/mcp/security/sample/client/DemoController.java

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,31 @@
1616

1717
package org.springaicommunity.mcp.security.sample.client;
1818

19-
import java.io.IOException;
19+
import java.util.List;
2020
import java.util.stream.Collectors;
2121

22-
import jakarta.servlet.http.HttpServletResponse;
22+
import io.modelcontextprotocol.client.McpSyncClient;
23+
import io.modelcontextprotocol.spec.McpSchema;
2324

2425
import org.springframework.ai.chat.client.ChatClient;
2526
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
2627
import org.springframework.util.StringUtils;
2728
import org.springframework.web.bind.annotation.GetMapping;
28-
import org.springframework.web.bind.annotation.RequestParam;
2929
import org.springframework.web.bind.annotation.RestController;
3030

3131
@RestController
3232
class DemoController {
3333

34-
private final InMemoryMcpClientRepository mcpClientRepo;
34+
private final SyncMcpToolCallbackProvider mcpToolCallbacks;
3535

36-
private ChatClient chatClient;
36+
private final List<McpSyncClient> clients;
3737

38-
DemoController(ChatClient.Builder chatClientBuilder, InMemoryMcpClientRepository mcpClientRepository) {
38+
private final ChatClient chatClient;
39+
40+
DemoController(ChatClient.Builder chatClientBuilder, List<McpSyncClient> clients) {
3941
this.chatClient = chatClientBuilder.build();
40-
this.mcpClientRepo = mcpClientRepository;
42+
this.mcpToolCallbacks = SyncMcpToolCallbackProvider.builder().mcpClients(clients).build();
43+
this.clients = clients;
4144
}
4245

4346
@GetMapping("/")
@@ -47,9 +50,9 @@ String index(String query) {
4750
var chatResponse = chatClient.prompt("""
4851
What is the weather in %s right now?
4952
Compare to historical data over the past 5 years.
50-
Tell me if it is within the usual range.
53+
Concisely tell me if it is within the usual range.
5154
Format the output in plain HTML, no CSS.""".formatted(query))
52-
.toolCallbacks(new SyncMcpToolCallbackProvider(mcpClientRepo.getClients()))
55+
.toolCallbacks(mcpToolCallbacks)
5356
.call()
5457
.content();
5558

@@ -62,8 +65,9 @@ String index(String query) {
6265
""".formatted(query, chatResponse);
6366
}
6467

65-
var currentMcpServersBlock = this.mcpClientRepo.getClientNames()
66-
.stream()
68+
var currentMcpServersBlock = this.clients.stream()
69+
.map(McpSyncClient::getClientInfo)
70+
.map(McpSchema.Implementation::name)
6771
.map(" <li>%s</li>"::formatted)
6872
.collect(Collectors.joining("\n"));
6973

@@ -86,20 +90,7 @@ String index(String query) {
8690
<ul>
8791
%s
8892
</ul>
89-
<form action="/mcp/add" method="GET">
90-
<input type="text" name="name" placeholder="My MCP server" value="weather data history" />
91-
<input type="text" name="url" placeholder="http://localhost:8090" value="http://localhost:8090" />
92-
<button type="submit">Add</button>
93-
</form>
9493
""".formatted(currentWeatherBlock, currentMcpServersBlock);
9594
}
9695

97-
// TODO: this should be a POST but that won't work with Spring Security
98-
@GetMapping("/mcp/add")
99-
void addMcpServer(@RequestParam String url, @RequestParam String name, HttpServletResponse response)
100-
throws IOException {
101-
this.mcpClientRepo.addClient(url, name);
102-
response.sendRedirect("/");
103-
}
104-
10596
}

samples/sample-mcp-client-webflux/src/main/java/org/springaicommunity/mcp/security/sample/client/InMemoryMcpClientRepository.java

Lines changed: 0 additions & 87 deletions
This file was deleted.

samples/sample-mcp-client-webflux/src/main/java/org/springaicommunity/mcp/security/sample/client/McpConfiguration.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@
1616

1717
package org.springaicommunity.mcp.security.sample.client;
1818

19+
import java.util.List;
20+
1921
import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
2022
import org.springaicommunity.mcp.security.client.sync.oauth2.webclient.McpOAuth2AuthorizationCodeExchangeFilterFunction;
2123

2224
import org.springframework.ai.mcp.customizer.McpSyncClientCustomizer;
25+
import org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration;
26+
import org.springframework.ai.model.tool.autoconfigure.ToolCallingAutoConfiguration;
27+
import org.springframework.ai.tool.resolution.StaticToolCallbackResolver;
28+
import org.springframework.ai.tool.resolution.ToolCallbackResolver;
2329
import org.springframework.context.annotation.Bean;
2430
import org.springframework.context.annotation.Configuration;
2531
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
@@ -30,6 +36,26 @@
3036
@Configuration
3137
class McpConfiguration {
3238

39+
/**
40+
* If the default {@link ToolCallbackResolver} from
41+
* {@link ToolCallingAutoConfiguration} is imported, then all MCP-based tools are
42+
* added to the resolver. In order to do so, the {@link ToolCallbackResolver} bean
43+
* lists all MCP tools, therefore initializing MCP clients and listing the tools.
44+
* <p>
45+
* This is an issue when the MCP server is secured with OAuth2, because to obtain a
46+
* token, a user must be involved in the flow, and there is no user present on app
47+
* startup.
48+
* <p>
49+
* To avoid this issue, we must exclude the default {@link ToolCallbackResolver}. We
50+
* can't easily disable the entire {@link ToolCallingAutoConfiguration} class, because
51+
* it is imported directly by the chat model configurations, such as
52+
* {@link AnthropicChatAutoConfiguration}. Instead, we provide a default, no-op bean.
53+
*/
54+
@Bean
55+
ToolCallbackResolver resolver() {
56+
return new StaticToolCallbackResolver(List.of());
57+
}
58+
3359
@Bean
3460
McpSyncClientCustomizer syncClientCustomizer() {
3561
return (name, syncSpec) -> syncSpec.transportContextProvider(new AuthenticationMcpTransportContextProvider());

samples/sample-mcp-client-webflux/src/main/resources/application.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ server.port=8081
33

44
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
55

6-
#spring.ai.mcp.client.sse.connections.current-weather.url=http://localhost:8091
6+
spring.ai.mcp.client.streamable-http.connections.historical-weather.url=http://localhost:8090
77
spring.ai.mcp.client.streamable-http.connections.current-weather.url=http://localhost:8091
88
spring.ai.mcp.client.type=SYNC
9+
spring.ai.mcp.client.initialized=false
910

1011
# For obtaining tokens for calling the tool
1112
spring.security.oauth2.client.registration.authserver.client-id=default-client

samples/sample-mcp-client/src/main/java/org/springaicommunity/mcp/security/sample/client/DemoController.java

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,31 @@
1616

1717
package org.springaicommunity.mcp.security.sample.client;
1818

19-
import java.io.IOException;
19+
import java.util.List;
2020
import java.util.stream.Collectors;
2121

22-
import jakarta.servlet.http.HttpServletResponse;
22+
import io.modelcontextprotocol.client.McpSyncClient;
23+
import io.modelcontextprotocol.spec.McpSchema;
2324

2425
import org.springframework.ai.chat.client.ChatClient;
2526
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
2627
import org.springframework.util.StringUtils;
2728
import org.springframework.web.bind.annotation.GetMapping;
28-
import org.springframework.web.bind.annotation.RequestParam;
2929
import org.springframework.web.bind.annotation.RestController;
3030

3131
@RestController
3232
class DemoController {
3333

34-
private final InMemoryMcpClientRepository mcpClientRepo;
34+
private final SyncMcpToolCallbackProvider mcpToolCallbacks;
3535

36-
private ChatClient chatClient;
36+
private final List<McpSyncClient> clients;
3737

38-
DemoController(ChatClient.Builder chatClientBuilder, InMemoryMcpClientRepository mcpClientRepository) {
38+
private final ChatClient chatClient;
39+
40+
DemoController(ChatClient.Builder chatClientBuilder, List<McpSyncClient> clients) {
3941
this.chatClient = chatClientBuilder.build();
40-
this.mcpClientRepo = mcpClientRepository;
42+
this.mcpToolCallbacks = SyncMcpToolCallbackProvider.builder().mcpClients(clients).build();
43+
this.clients = clients;
4144
}
4245

4346
@GetMapping("/")
@@ -47,9 +50,9 @@ String index(String query) {
4750
var chatResponse = chatClient.prompt("""
4851
What is the weather in %s right now?
4952
Compare to historical data over the past 5 years.
50-
Tell me if it is within the usual range.
53+
Concisely tell me if it is within the usual range.
5154
Format the output in plain HTML, no CSS.""".formatted(query))
52-
.toolCallbacks(new SyncMcpToolCallbackProvider(mcpClientRepo.getClients()))
55+
.toolCallbacks(mcpToolCallbacks)
5356
.call()
5457
.content();
5558

@@ -62,8 +65,9 @@ String index(String query) {
6265
""".formatted(query, chatResponse);
6366
}
6467

65-
var currentMcpServersBlock = this.mcpClientRepo.getClientNames()
66-
.stream()
68+
var currentMcpServersBlock = this.clients.stream()
69+
.map(McpSyncClient::getClientInfo)
70+
.map(McpSchema.Implementation::name)
6771
.map(" <li>%s</li>"::formatted)
6872
.collect(Collectors.joining("\n"));
6973

@@ -86,20 +90,7 @@ String index(String query) {
8690
<ul>
8791
%s
8892
</ul>
89-
<form action="/mcp/add" method="GET">
90-
<input type="text" name="name" placeholder="My MCP server" value="weather data history" />
91-
<input type="text" name="url" placeholder="http://localhost:8090" value="http://localhost:8090" />
92-
<button type="submit">Add</button>
93-
</form>
9493
""".formatted(currentWeatherBlock, currentMcpServersBlock);
9594
}
9695

97-
// TODO: this should be a POST but that won't work with Spring Security
98-
@GetMapping("/mcp/add")
99-
void addMcpServer(@RequestParam String url, @RequestParam String name, HttpServletResponse response)
100-
throws IOException {
101-
this.mcpClientRepo.addClient(url, name);
102-
response.sendRedirect("/");
103-
}
104-
10596
}

samples/sample-mcp-client/src/main/java/org/springaicommunity/mcp/security/sample/client/InMemoryMcpClientRepository.java

Lines changed: 0 additions & 87 deletions
This file was deleted.

0 commit comments

Comments
 (0)