Skip to content

Commit e086c9a

Browse files
committed
feat(mcp): support multi-source read of MCP service
1 parent e2dd037 commit e086c9a

File tree

12 files changed

+406
-38
lines changed

12 files changed

+406
-38
lines changed

auto-configurations/spring-ai-alibaba-autoconfigure-mcp-router/src/main/java/com/alibaba/cloud/ai/autoconfigure/mcp/router/DbMcpRouterAutoConfiguration.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,61 @@
2020
import com.alibaba.cloud.ai.mcp.router.config.DbMcpProperties;
2121
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscovery;
2222
import com.alibaba.cloud.ai.mcp.router.core.discovery.DbMcpServiceDiscovery;
23+
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscoveryFactory;
24+
import jakarta.annotation.PostConstruct;
2325
import org.slf4j.Logger;
2426
import org.slf4j.LoggerFactory;
27+
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
2528
import org.springframework.boot.autoconfigure.AutoConfiguration;
26-
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
29+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
30+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2731
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2832
import org.springframework.context.annotation.Bean;
2933

3034
/**
35+
* Register DbMcpServiceDiscovery to McpServiceDiscoveryFactory.
36+
*
3137
* @author digitzh
3238
*/
3339
@AutoConfiguration
34-
@EnableConfigurationProperties({ McpRouterProperties.class, DbMcpProperties.class })
35-
@ConditionalOnExpression("$" + "{" + McpRouterProperties.CONFIG_PREFIX + ".enabled:true} == true " + "and '$" + "{"
36-
+ McpRouterProperties.CONFIG_PREFIX + ".discovery-type}' == 'database'")
40+
@AutoConfigureAfter(McpServiceDiscoveryAutoConfiguration.class)
41+
@EnableConfigurationProperties({ McpRouterProperties.class, DbMcpProperties.class, McpServerProperties.class })
42+
@ConditionalOnProperty(prefix = McpRouterProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
43+
matchIfMissing = true)
3744
public class DbMcpRouterAutoConfiguration {
3845

3946
private static final Logger log = LoggerFactory.getLogger(DbMcpRouterAutoConfiguration.class);
4047

4148
@Bean
42-
public McpServiceDiscovery dbMcpServiceDiscovery(DbMcpProperties dbMcpProperties) {
43-
log.info("Creating DB MCP service discovery with configuration: {}", dbMcpProperties);
44-
return new DbMcpServiceDiscovery(dbMcpProperties);
49+
@ConditionalOnProperty(prefix = DbMcpProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true")
50+
public DbMcpServiceDiscoveryRegistrar dbMcpServiceDiscoveryRegistrar(McpServiceDiscoveryFactory discoveryFactory,
51+
DbMcpProperties dbMcpProperties) {
52+
log.info("Creating database MCP service discovery registrar with properties: {}", dbMcpProperties);
53+
return new DbMcpServiceDiscoveryRegistrar(discoveryFactory, dbMcpProperties);
54+
}
55+
56+
public static class DbMcpServiceDiscoveryRegistrar {
57+
58+
private final McpServiceDiscoveryFactory discoveryFactory;
59+
60+
private final DbMcpProperties dbMcpProperties;
61+
62+
public DbMcpServiceDiscoveryRegistrar(McpServiceDiscoveryFactory discoveryFactory,
63+
DbMcpProperties dbMcpProperties) {
64+
this.discoveryFactory = discoveryFactory;
65+
this.dbMcpProperties = dbMcpProperties;
66+
log.info("Database MCP service discovery registrar constructor called with properties: {}",
67+
dbMcpProperties);
68+
}
69+
70+
@PostConstruct
71+
public void init() {
72+
log.info("Database MCP service discovery registrar initialized with properties: {}", dbMcpProperties);
73+
log.info("Registering DB MCP service discovery with configuration: {}", dbMcpProperties);
74+
McpServiceDiscovery dbDiscovery = new DbMcpServiceDiscovery(dbMcpProperties);
75+
discoveryFactory.registerDiscovery("database", dbDiscovery);
76+
}
77+
4578
}
4679

4780
}

auto-configurations/spring-ai-alibaba-autoconfigure-mcp-router/src/main/java/com/alibaba/cloud/ai/autoconfigure/mcp/router/FileMcpRouterAutoConfiguration.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,60 @@
1919
import com.alibaba.cloud.ai.mcp.router.config.McpRouterProperties;
2020
import com.alibaba.cloud.ai.mcp.router.core.discovery.FileConfigMcpServiceDiscovery;
2121
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscovery;
22+
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscoveryFactory;
23+
import jakarta.annotation.PostConstruct;
2224
import org.slf4j.Logger;
2325
import org.slf4j.LoggerFactory;
26+
import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
2427
import org.springframework.boot.autoconfigure.AutoConfiguration;
25-
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
28+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
2630
import org.springframework.boot.context.properties.EnableConfigurationProperties;
2731
import org.springframework.context.annotation.Bean;
2832

2933
/**
34+
* Register FileConfigMcpServiceDiscovery to McpServiceDiscoveryFactory.
35+
*
3036
* @author digitzh
3137
*/
3238
@AutoConfiguration
33-
@EnableConfigurationProperties(McpRouterProperties.class)
34-
@ConditionalOnExpression("${" + McpRouterProperties.CONFIG_PREFIX + ".enabled:true} == true " + "and '${"
35-
+ McpRouterProperties.CONFIG_PREFIX + ".discovery-type}' == 'file'")
39+
@AutoConfigureAfter(McpServiceDiscoveryAutoConfiguration.class)
40+
@EnableConfigurationProperties({ McpRouterProperties.class, McpServerProperties.class })
41+
@ConditionalOnProperty(prefix = McpRouterProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
42+
matchIfMissing = true)
3643
public class FileMcpRouterAutoConfiguration {
3744

3845
private static final Logger log = LoggerFactory.getLogger(FileMcpRouterAutoConfiguration.class);
3946

4047
@Bean
41-
public McpServiceDiscovery fileConfigMcpServiceDiscovery(McpRouterProperties properties) {
42-
return new FileConfigMcpServiceDiscovery(properties);
48+
public FileMcpServiceDiscoveryRegistrar fileMcpServiceDiscoveryRegistrar(
49+
McpServiceDiscoveryFactory discoveryFactory, McpRouterProperties properties) {
50+
log.info("Creating file MCP service discovery registrar with properties: {}", properties);
51+
return new FileMcpServiceDiscoveryRegistrar(discoveryFactory, properties);
52+
}
53+
54+
public static class FileMcpServiceDiscoveryRegistrar {
55+
56+
private final McpServiceDiscoveryFactory discoveryFactory;
57+
58+
private final McpRouterProperties properties;
59+
60+
public FileMcpServiceDiscoveryRegistrar(McpServiceDiscoveryFactory discoveryFactory,
61+
McpRouterProperties properties) {
62+
this.discoveryFactory = discoveryFactory;
63+
this.properties = properties;
64+
log.info("File MCP service discovery registrar constructor called with properties: {}", properties);
65+
}
66+
67+
@PostConstruct
68+
public void init() {
69+
log.info("File MCP service discovery registrar initialized with properties: {}", properties);
70+
log.info("Registering file config MCP service discovery with {} services",
71+
properties.getServices() != null ? properties.getServices().size() : 0);
72+
McpServiceDiscovery fileDiscovery = new FileConfigMcpServiceDiscovery(properties);
73+
discoveryFactory.registerDiscovery("file", fileDiscovery);
74+
}
75+
4376
}
4477

4578
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2024-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 com.alibaba.cloud.ai.autoconfigure.mcp.router;
18+
19+
import com.alibaba.cloud.ai.mcp.router.config.McpRouterProperties;
20+
import com.alibaba.cloud.ai.mcp.router.core.discovery.CompositeMcpServiceDiscovery;
21+
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscovery;
22+
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscoveryFactory;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
import org.springframework.boot.autoconfigure.AutoConfiguration;
26+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
27+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
28+
import org.springframework.context.annotation.Bean;
29+
import org.springframework.context.annotation.Primary;
30+
31+
import java.util.List;
32+
33+
/**
34+
* Register McpServiceDiscovery to McpServiceDiscoveryFactory.
35+
*
36+
* @author digitzh
37+
*/
38+
@AutoConfiguration
39+
@EnableConfigurationProperties(McpRouterProperties.class)
40+
@ConditionalOnProperty(prefix = McpRouterProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
41+
matchIfMissing = true)
42+
public class McpServiceDiscoveryAutoConfiguration {
43+
44+
private static final Logger log = LoggerFactory.getLogger(McpServiceDiscoveryAutoConfiguration.class);
45+
46+
@Bean
47+
public McpServiceDiscoveryFactory mcpServiceDiscoveryFactory() {
48+
log.info("Creating MCP service discovery factory");
49+
return new McpServiceDiscoveryFactory();
50+
}
51+
52+
@Bean
53+
@Primary
54+
public McpServiceDiscovery compositeMcpServiceDiscovery(McpServiceDiscoveryFactory discoveryFactory,
55+
McpRouterProperties properties) {
56+
57+
List<String> searchOrder = getSearchOrder(properties);
58+
log.info("Creating composite MCP service discovery with search order: {}", searchOrder);
59+
60+
return new CompositeMcpServiceDiscovery(discoveryFactory, searchOrder);
61+
}
62+
63+
private List<String> getSearchOrder(McpRouterProperties properties) {
64+
if (properties.getDiscoveryOrder() != null && !properties.getDiscoveryOrder().isEmpty()) {
65+
return properties.getDiscoveryOrder();
66+
}
67+
68+
return List.of("nacos");
69+
}
70+
71+
}

auto-configurations/spring-ai-alibaba-autoconfigure-mcp-router/src/main/java/com/alibaba/cloud/ai/autoconfigure/mcp/router/NacosMcpRouterAutoConfiguration.java

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,14 @@
2424
import com.alibaba.cloud.ai.mcp.router.config.McpRouterProperties;
2525
import com.alibaba.cloud.ai.mcp.router.core.McpRouterWatcher;
2626
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscovery;
27+
import com.alibaba.cloud.ai.mcp.router.core.discovery.McpServiceDiscoveryFactory;
2728
import com.alibaba.cloud.ai.mcp.router.core.vectorstore.McpServerVectorStore;
2829
import com.alibaba.cloud.ai.mcp.router.core.vectorstore.SimpleMcpServerVectorStore;
2930
import com.alibaba.cloud.ai.mcp.router.nacos.NacosMcpServiceDiscovery;
3031
import com.alibaba.cloud.ai.mcp.router.service.McpProxyService;
3132
import com.alibaba.cloud.ai.mcp.router.service.McpRouterService;
3233
import com.alibaba.nacos.api.exception.NacosException;
34+
import jakarta.annotation.PostConstruct;
3335
import org.slf4j.Logger;
3436
import org.slf4j.LoggerFactory;
3537
import org.springframework.ai.document.MetadataMode;
@@ -38,6 +40,8 @@
3840
import org.springframework.ai.tool.ToolCallbackProvider;
3941
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
4042
import org.springframework.beans.factory.annotation.Value;
43+
import org.springframework.boot.autoconfigure.AutoConfiguration;
44+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
4145
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4246
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4347
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -46,11 +50,15 @@
4650
import java.util.Properties;
4751

4852
/**
53+
* Register NacosMcpServiceDiscovery to McpServiceDiscoveryFactory.
54+
*
4955
* @author aias00
5056
*/
57+
@AutoConfiguration
58+
@AutoConfigureAfter(McpServiceDiscoveryAutoConfiguration.class)
5159
@EnableConfigurationProperties({ McpRouterProperties.class, NacosMcpProperties.class, McpServerProperties.class })
5260
@ConditionalOnProperty(prefix = McpRouterProperties.CONFIG_PREFIX, name = "enabled", havingValue = "true",
53-
matchIfMissing = false)
61+
matchIfMissing = true)
5462
public class NacosMcpRouterAutoConfiguration {
5563

5664
private static final Logger log = LoggerFactory.getLogger(NacosMcpRouterAutoConfiguration.class);
@@ -62,7 +70,7 @@ public class NacosMcpRouterAutoConfiguration {
6270
@ConditionalOnMissingBean
6371
public EmbeddingModel embeddingModel() {
6472
if (apiKey == null || apiKey.isEmpty() || "default_api_key".equals(apiKey)) {
65-
throw new IllegalArgumentException("Environment variable DASHSCOPE_API_KEY is not set.");
73+
throw new IllegalArgumentException("Environment variable AI_DASHSCOPE_API_KEY is not set.");
6674
}
6775
DashScopeApi dashScopeApi = DashScopeApi.builder().apiKey(apiKey).build();
6876

@@ -82,13 +90,11 @@ public NacosMcpOperationService nacosMcpOperationService(NacosMcpProperties naco
8290
}
8391
}
8492

85-
/**
86-
* 配置 MCP 服务发现
87-
*/
8893
@Bean
89-
@ConditionalOnMissingBean
90-
public McpServiceDiscovery mcpServiceDiscovery(NacosMcpOperationService nacosMcpOperationService) {
91-
return new NacosMcpServiceDiscovery(nacosMcpOperationService);
94+
public NacosMcpServiceDiscoveryRegistrar nacosMcpServiceDiscoveryRegistrar(
95+
McpServiceDiscoveryFactory discoveryFactory, NacosMcpOperationService nacosMcpOperationService) {
96+
log.info("Creating Nacos MCP service discovery registrar");
97+
return new NacosMcpServiceDiscoveryRegistrar(discoveryFactory, nacosMcpOperationService);
9298
}
9399

94100
/**
@@ -138,4 +144,30 @@ public McpRouterWatcher mcpRouterWatcher(McpServiceDiscovery mcpServiceDiscovery
138144
return new McpRouterWatcher(mcpServiceDiscovery, mcpServerVectorStore, mcpRouterProperties.getServiceNames());
139145
}
140146

147+
/**
148+
* Nacos MCP服务发现注册器
149+
*/
150+
public static class NacosMcpServiceDiscoveryRegistrar {
151+
152+
private final McpServiceDiscoveryFactory discoveryFactory;
153+
154+
private final NacosMcpOperationService nacosMcpOperationService;
155+
156+
public NacosMcpServiceDiscoveryRegistrar(McpServiceDiscoveryFactory discoveryFactory,
157+
NacosMcpOperationService nacosMcpOperationService) {
158+
this.discoveryFactory = discoveryFactory;
159+
this.nacosMcpOperationService = nacosMcpOperationService;
160+
log.info("Nacos MCP service discovery registrar constructor called");
161+
}
162+
163+
@PostConstruct
164+
public void init() {
165+
log.info("Nacos MCP service discovery registrar initialized");
166+
log.info("Registering Nacos MCP service discovery");
167+
McpServiceDiscovery nacosDiscovery = new NacosMcpServiceDiscovery(nacosMcpOperationService);
168+
discoveryFactory.registerDiscovery("nacos", nacosDiscovery);
169+
}
170+
171+
}
172+
141173
}

auto-configurations/spring-ai-alibaba-autoconfigure-mcp-router/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16-
com.alibaba.cloud.ai.autoconfigure.mcp.router.NacosMcpRouterAutoConfiguration
16+
17+
com.alibaba.cloud.ai.autoconfigure.mcp.router.McpServiceDiscoveryAutoConfiguration
1718
com.alibaba.cloud.ai.autoconfigure.mcp.router.FileMcpRouterAutoConfiguration
1819
com.alibaba.cloud.ai.autoconfigure.mcp.router.DbMcpRouterAutoConfiguration
20+
com.alibaba.cloud.ai.autoconfigure.mcp.router.NacosMcpRouterAutoConfiguration
1921

2022
com.alibaba.cloud.ai.autoconfigure.mcp.gateway.core.McpGatewayServerAutoConfiguration
2123
#com.alibaba.cloud.ai.autoconfigure.mcp.gateway.core.McpGatewaySseServerAutoConfiguration

spring-ai-alibaba-mcp/spring-ai-alibaba-mcp-router/src/main/java/com/alibaba/cloud/ai/mcp/router/config/DbMcpProperties.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class DbMcpProperties {
2323

2424
public static final String CONFIG_PREFIX = "spring.ai.alibaba.mcp.router.database";
2525

26+
private boolean enabled = false;
27+
2628
private String url;
2729

2830
private String username;
@@ -113,4 +115,12 @@ public void setConnectionTimeout(long connectionTimeout) {
113115
this.connectionTimeout = connectionTimeout;
114116
}
115117

118+
public boolean isEnabled() {
119+
return enabled;
120+
}
121+
122+
public void setEnabled(boolean enabled) {
123+
this.enabled = enabled;
124+
}
125+
116126
}

spring-ai-alibaba-mcp/spring-ai-alibaba-mcp-router/src/main/java/com/alibaba/cloud/ai/mcp/router/config/McpRouterProperties.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,17 @@ public void setServices(List<McpServerInfo> services) {
6565
}
6666

6767
/**
68-
* MCP router service discovery type (nacos, file, database)
68+
* MCP router service discovery search order 支持多种发现方式同时使用,按顺序查找服务 例如: ["file",
69+
* "database", "nacos"]
6970
*/
70-
private String discoveryType = "nacos"; // Nacos for default
71+
private List<String> discoveryOrder = List.of("nacos");
7172

72-
public String getDiscoveryType() {
73-
return discoveryType;
73+
public List<String> getDiscoveryOrder() {
74+
return discoveryOrder;
7475
}
7576

76-
public void setDiscoveryType(String discoveryType) {
77-
this.discoveryType = discoveryType;
77+
public void setDiscoveryOrder(List<String> discoveryOrder) {
78+
this.discoveryOrder = discoveryOrder;
7879
}
7980

8081
}

0 commit comments

Comments
 (0)