Skip to content

Commit 819130d

Browse files
committed
refactor: introduce MCP client customization support
Add McpSyncClientCustomizer interface for customizing MCP sync clients Replace McpClientDefinitions with McpSyncClientConfigurer Refactor MCP client initialization to support customization Remove redundant close() method from McpToolCallback Fix conditional class dependencies in WebMvc/Flux configurations
1 parent c924b3b commit 819130d

File tree

9 files changed

+153
-121
lines changed

9 files changed

+153
-121
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2024 - 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.ai.mcp;
17+
18+
import io.modelcontextprotocol.client.McpClient;
19+
20+
/**
21+
* @author Christian Tzolov
22+
* @since 1.0.0
23+
*/
24+
public interface McpSyncClientCustomizer {
25+
26+
void customize(String name, McpClient.SyncSpec sync);
27+
28+
}

mcp/common/src/main/java/org/springframework/ai/mcp/McpToolCallback.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,4 @@ public String call(String functionInput) {
112112
return ModelOptionsUtils.toJsonString(response.content());
113113
}
114114

115-
public void close() {
116-
this.mcpClient.close();
117-
}
118-
119115
}

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/McpClientDefinitions.java

Lines changed: 0 additions & 83 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.ai.autoconfigure.mcp.client.stdio;
18+
19+
import java.util.List;
20+
21+
import io.modelcontextprotocol.client.McpClient;
22+
23+
import org.springframework.ai.mcp.McpSyncClientCustomizer;
24+
25+
public class McpSyncClientConfigurer {
26+
27+
private List<McpSyncClientCustomizer> customizers;
28+
29+
void setCustomizers(List<McpSyncClientCustomizer> customizers) {
30+
this.customizers = customizers;
31+
}
32+
33+
public McpClient.SyncSpec configure(String name, McpClient.SyncSpec spec) {
34+
applyCustomizers(name, spec);
35+
return spec;
36+
}
37+
38+
private void applyCustomizers(String name, McpClient.SyncSpec spec) {
39+
if (this.customizers != null) {
40+
for (McpSyncClientCustomizer customizer : this.customizers) {
41+
customizer.customize(name, spec);
42+
}
43+
}
44+
}
45+
46+
}

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/client/stdio/MpcStdioClientAutoConfiguration.java

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,26 @@
1717
package org.springframework.ai.autoconfigure.mcp.client.stdio;
1818

1919
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.HashMap;
2220
import java.util.List;
2321
import java.util.Map;
24-
import java.util.function.Consumer;
25-
import java.util.function.Function;
2622

2723
import io.modelcontextprotocol.client.McpClient;
2824
import io.modelcontextprotocol.client.McpSyncClient;
2925
import io.modelcontextprotocol.client.transport.ServerParameters;
3026
import io.modelcontextprotocol.client.transport.StdioClientTransport;
3127
import io.modelcontextprotocol.spec.McpSchema;
32-
import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
33-
import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
34-
import io.modelcontextprotocol.spec.McpSchema.Root;
3528

36-
import org.springframework.ai.autoconfigure.mcp.client.stdio.McpClientDefinitions.McpClientDefinition;
29+
import org.springframework.ai.mcp.McpSyncClientCustomizer;
3730
import org.springframework.ai.mcp.McpToolCallback;
38-
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
31+
import org.springframework.ai.mcp.McpToolUtils;
32+
import org.springframework.ai.tool.ToolCallback;
3933
import org.springframework.beans.factory.ObjectProvider;
4034
import org.springframework.boot.autoconfigure.AutoConfiguration;
4135
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
36+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4237
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4338
import org.springframework.boot.context.properties.EnableConfigurationProperties;
4439
import org.springframework.context.annotation.Bean;
45-
import org.springframework.core.log.LogAccessor;
46-
import org.springframework.util.CollectionUtils;
4740

4841
/**
4942
* Auto-configuration for Model Context Protocol (MCP) STDIO clients.
@@ -81,19 +74,62 @@
8174
@ConditionalOnProperty(prefix = McpStdioClientProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true")
8275
public class MpcStdioClientAutoConfiguration {
8376

84-
private static final LogAccessor logger = new LogAccessor(MpcStdioClientAutoConfiguration.class);
85-
8677
@Bean
87-
public McpClientDefinitions mcpClientDefinitions(McpStdioClientProperties clientProperties) {
78+
public List<McpSyncClient> mcpSyncClients(McpSyncClientConfigurer mcpSyncClientConfigurer,
79+
McpStdioClientProperties clientProperties) {
8880

89-
Map<String, McpClientDefinition> definitions = new HashMap<>();
81+
List<McpSyncClient> clients = new ArrayList<>();
9082

9183
for (Map.Entry<String, ServerParameters> serverParameters : clientProperties.toServerParameters().entrySet()) {
92-
definitions.put(serverParameters.getKey(),
93-
new McpClientDefinition(serverParameters.getKey(), serverParameters.getValue(), clientProperties));
84+
85+
var transport = new StdioClientTransport(serverParameters.getValue());
86+
87+
McpSchema.Implementation clientInfo = new McpSchema.Implementation(serverParameters.getKey(),
88+
clientProperties.getVersion());
89+
90+
McpClient.SyncSpec syncSpec = McpClient.sync(transport)
91+
.clientInfo(clientInfo)
92+
.requestTimeout(clientProperties.getRequestTimeout());
93+
94+
syncSpec = mcpSyncClientConfigurer.configure(serverParameters.getKey(), syncSpec);
95+
96+
var syncClient = syncSpec.build();
97+
98+
if (clientProperties.isInitialize()) {
99+
syncClient.initialize();
100+
}
101+
102+
clients.add(syncClient);
103+
104+
}
105+
106+
return clients;
107+
}
108+
109+
@Bean
110+
public List<ToolCallback> toolCallbacks(List<McpSyncClient> mcpClients) {
111+
return McpToolUtils.getToolCallbacks(mcpClients);
112+
}
113+
114+
public record ClosebleMcpSyncClients(List<McpSyncClient> clients) implements AutoCloseable {
115+
116+
@Override
117+
public void close() {
118+
clients.forEach(McpSyncClient::close);
94119
}
120+
}
95121

96-
return new McpClientDefinitions(definitions);
122+
@Bean
123+
public ClosebleMcpSyncClients makeThemClosable(List<McpSyncClient> clients) {
124+
return new ClosebleMcpSyncClients(clients);
125+
}
126+
127+
@Bean
128+
@ConditionalOnMissingBean
129+
McpSyncClientConfigurer mcpSyncClientConfigurer(ObjectProvider<McpSyncClientCustomizer> customizerProvider) {
130+
McpSyncClientConfigurer configurer = new McpSyncClientConfigurer();
131+
configurer.setCustomizers(customizerProvider.orderedStream().toList());
132+
return configurer;
97133
}
98134

99135
}

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/McpServerProperties.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
/**
2323
* Configuration properties for the Model Context Protocol (MCP) server.
2424
* <p>
25-
* These properties control the behavior and configuration of the MCP server, including:
25+
* These properties control the behavior and configuration of the MCP server,
26+
* including:
2627
* <ul>
2728
* <li>Server identification (name and version)</li>
2829
* <li>Transport type (STDIO, WEBMVC, or WEBFLUX)</li>
@@ -44,7 +45,8 @@ public class McpServerProperties {
4445
/**
4546
* Enable/disable the MCP server.
4647
* <p>
47-
* When set to false, the MCP server and all its components will not be initialized.
48+
* When set to false, the MCP server and all its components will not be
49+
* initialized.
4850
*/
4951
private boolean enabled = false;
5052

@@ -63,16 +65,19 @@ public class McpServerProperties {
6365
private String version = "1.0.0";
6466

6567
/**
66-
* Enable/disable notifications for resource changes. Only relevant for MCP servers
68+
* Enable/disable notifications for resource changes. Only relevant for MCP
69+
* servers
6770
* with resource capabilities.
6871
* <p>
69-
* When enabled, the server will notify clients when resources are added, updated, or
72+
* When enabled, the server will notify clients when resources are added,
73+
* updated, or
7074
* removed.
7175
*/
7276
private boolean resourceChangeNotification = true;
7377

7478
/**
75-
* Enable/disable notifications for tool changes. Only relevant for MCP servers with
79+
* Enable/disable notifications for tool changes. Only relevant for MCP servers
80+
* with
7681
* tool capabilities.
7782
* <p>
7883
* When enabled, the server will notify clients when tools are registered or
@@ -81,10 +86,12 @@ public class McpServerProperties {
8186
private boolean toolChangeNotification = true;
8287

8388
/**
84-
* Enable/disable notifications for prompt changes. Only relevant for MCP servers with
89+
* Enable/disable notifications for prompt changes. Only relevant for MCP
90+
* servers with
8591
* prompt capabilities.
8692
* <p>
87-
* When enabled, the server will notify clients when prompt templates are modified.
93+
* When enabled, the server will notify clients when prompt templates are
94+
* modified.
8895
*/
8996
private boolean promptChangeNotification = true;
9097

@@ -152,6 +159,7 @@ public enum ServerType {
152159
* Synchronous (McpSyncServer) server
153160
*/
154161
SYNC,
162+
155163
/**
156164
* Asynchronous (McpAsyncServer) server
157165
*/

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcServerAutoConfiguration.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -137,37 +137,37 @@ public McpSyncServer mcpSyncServer(ServerMcpTransport transport,
137137
serverProperties.getVersion());
138138

139139
// Create the server with both tool and resource capabilities
140-
SyncSpec serverBilder = McpServer.sync(transport).serverInfo(serverInfo);
140+
SyncSpec serverBuilder = McpServer.sync(transport).serverInfo(serverInfo);
141141

142142
tools.ifAvailable(toolsList -> {
143-
serverBilder.tools(toolsList);
143+
serverBuilder.tools(toolsList);
144144
capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
145145
logger.info("Registered tools" + toolsList.size() + " notification: "
146146
+ serverProperties.isToolChangeNotification());
147147
});
148148

149149
resources.ifAvailable(resourceList -> {
150-
serverBilder.resources(resourceList);
150+
serverBuilder.resources(resourceList);
151151
capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
152152
logger.info("Registered resources" + resourceList.size() + " notification: "
153153
+ serverProperties.isResourceChangeNotification());
154154
});
155155

156156
prompts.ifAvailable(promptList -> {
157-
serverBilder.prompts(promptList);
157+
serverBuilder.prompts(promptList);
158158
capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
159159
logger.info("Registered prompts" + promptList.size() + " notification: "
160160
+ serverProperties.isPromptChangeNotification());
161161
});
162162

163163
rootsChangeConsumers.ifAvailable(consumer -> {
164-
serverBilder.rootsChangeConsumer(consumer);
164+
serverBuilder.rootsChangeConsumer(consumer);
165165
logger.info("Registered roots change consumer");
166166
});
167167

168-
serverBilder.capabilities(capabilitiesBuilder.build());
168+
serverBuilder.capabilities(capabilitiesBuilder.build());
169169

170-
return serverBilder.build();
170+
return serverBuilder.build();
171171
}
172172

173173
@Bean

spring-ai-spring-boot-autoconfigure/src/main/java/org/springframework/ai/autoconfigure/mcp/server/MpcWebFluxServerAutoConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
* @see WebFluxSseServerTransport
6464
*/
6565
@AutoConfiguration
66-
@ConditionalOnClass({ WebFluxSseServerTransport.class, RouterFunction.class })
66+
@ConditionalOnClass({ WebFluxSseServerTransport.class })
6767
@ConditionalOnProperty(prefix = McpServerProperties.CONFIG_PREFIX, name = "transport", havingValue = "WEBFLUX")
6868
public class MpcWebFluxServerAutoConfiguration {
6969

0 commit comments

Comments
 (0)