Skip to content

Commit fbce299

Browse files
authored
Fix inconsistent mcp cache. (alibaba#14024)
* refactor(CachedMcpServerIndex, PlainMcpServerIndex, AbstractMcpServerIndex): 修改afterSearch方法参数并优化缓存更新逻辑 此提交调整了`CachedMcpServerIndex`, `PlainMcpServerIndex` 和 `AbstractMcpServerIndex` 中的`afterSearch` 方法参数为单个对象而非列表,并简化了缓存更新逻辑。同时在`AbstractMcpServerIndex` 中增加了新的映射和缓存更新方法。 Change-Id: I200ca3f54ebcc62ac7d3b0eb65b7de28a802aaa0 Co-developed-by: Aone Copilot <noreply@alibaba-inc.com> * refactor(McpServerCacheInvalidateService): 移除未使用的导入语句 移除了几个类中的未使用导入语句,清理了代码。 Change-Id: I709cac2625a974810f5850ea73f8b710e6d11fd0 Co-developed-by: Aone Copilot <noreply@alibaba-inc.com>
1 parent 3007d6f commit fbce299

6 files changed

Lines changed: 480 additions & 13 deletions

File tree

ai/src/main/java/com/alibaba/nacos/ai/index/AbstractMcpServerIndex.java

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,18 @@ public Page<McpServerIndexData> searchMcpServerByNameWithPage(String namespaceId
6969
int pageNo, int limit) {
7070
Page<ConfigInfo> serverInfos = searchMcpServers(namespaceId, name, search, pageNo, limit);
7171
List<McpServerIndexData> indexDataList = serverInfos.getPageItems().stream()
72-
.map(this::mapMcpServerVersionConfigToIndexData).toList();
72+
.map((configInfo) -> {
73+
configInfo.setTenant(namespaceId);
74+
return configInfo;
75+
})
76+
.map(this::mapToMcpServerVersionInfo)
77+
.map(this::mcpToIndexAndUpdateToCache)
78+
.toList();
7379
Page<McpServerIndexData> result = new Page<>();
7480
result.setPageItems(indexDataList);
7581
result.setTotalCount(serverInfos.getTotalCount());
7682
result.setPagesAvailable((int) Math.ceil((double) serverInfos.getTotalCount() / (double) limit));
7783
result.setPageNumber(pageNo);
78-
afterSearch(indexDataList, name);
7984
return result;
8085
}
8186

@@ -85,7 +90,7 @@ public Page<McpServerIndexData> searchMcpServerByNameWithPage(String namespaceId
8590
* @param searchResult the search results
8691
* @param name the search name
8792
*/
88-
protected abstract void afterSearch(List<McpServerIndexData> searchResult, String name);
93+
protected abstract void afterSearch(McpServerIndexData searchResult, String name);
8994

9095
/**
9196
* Search MCP servers.
@@ -110,11 +115,17 @@ protected Page<ConfigInfo> searchMcpServers(String namespace, String serverName,
110115
Constants.MCP_SERVER_VERSIONS_GROUP, namespace, advanceInfo);
111116
}
112117

113-
protected McpServerIndexData mapMcpServerVersionConfigToIndexData(ConfigInfo configInfo) {
118+
protected McpServerVersionInfo mapToMcpServerVersionInfo(ConfigInfo configInfo) {
119+
McpServerVersionInfo obj = JacksonUtils.toObj(configInfo.getContent(), McpServerVersionInfo.class);
120+
obj.setNamespaceId(configInfo.getTenant());
121+
return obj;
122+
}
123+
124+
protected McpServerIndexData mcpToIndexAndUpdateToCache(McpServerVersionInfo versionInfo) {
114125
McpServerIndexData data = new McpServerIndexData();
115-
McpServerVersionInfo versionInfo = JacksonUtils.toObj(configInfo.getContent(), McpServerVersionInfo.class);
116126
data.setId(versionInfo.getId());
117-
data.setNamespaceId(configInfo.getTenant());
127+
data.setNamespaceId(versionInfo.getNamespaceId());
128+
afterSearch(data, versionInfo.getName());
118129
return data;
119130
}
120131
}

ai/src/main/java/com/alibaba/nacos/ai/index/CachedMcpServerIndex.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,10 @@ public McpServerIndexData getMcpServerByName(String namespaceId, String name) {
138138
}
139139

140140
@Override
141-
protected void afterSearch(List<McpServerIndexData> indexDataList, String name) {
141+
protected void afterSearch(McpServerIndexData indexData, String name) {
142142
// Update cache
143143
if (cacheEnabled) {
144-
for (McpServerIndexData indexData : indexDataList) {
145-
cacheIndex.updateIndex(indexData.getNamespaceId(), name, indexData.getId());
146-
}
147-
LOGGER.debug("Updated cache with {} entries from search results", indexDataList.size());
144+
cacheIndex.updateIndex(indexData.getNamespaceId(), name, indexData.getId());
148145
}
149146
}
150147

ai/src/main/java/com/alibaba/nacos/ai/index/PlainMcpServerIndex.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,6 @@ public void removeMcpServerById(String mcpId) {
129129
}
130130

131131
@Override
132-
protected void afterSearch(List<McpServerIndexData> searchResult, String name) {
132+
protected void afterSearch(McpServerIndexData searchResult, String name) {
133133
}
134134
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* Copyright 1999-2025 Alibaba Group Holding Ltd.
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+
* http://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.nacos.ai.service;
18+
19+
import com.alibaba.nacos.ai.constant.Constants;
20+
import com.alibaba.nacos.ai.index.McpServerIndex;
21+
import com.alibaba.nacos.common.notify.Event;
22+
import com.alibaba.nacos.common.notify.NotifyCenter;
23+
import com.alibaba.nacos.common.notify.listener.Subscriber;
24+
import com.alibaba.nacos.common.utils.StringUtils;
25+
import com.alibaba.nacos.config.server.model.event.LocalDataChangeEvent;
26+
import com.alibaba.nacos.config.server.utils.GroupKey;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
import org.springframework.beans.factory.annotation.Autowired;
30+
import org.springframework.stereotype.Service;
31+
32+
/**
33+
* MCP Server cache invalidation service.
34+
*
35+
* <p>This service listens to ConfigDataChangeEvent and invalidates MCP server cache
36+
* when MCP-related configurations are deleted or modified. The implementation follows
37+
* the same pattern as AsyncNotifyService for configuration synchronization.</p>
38+
*
39+
* @author xinluo
40+
*/
41+
@Service
42+
public class McpServerCacheInvalidateService extends Subscriber<LocalDataChangeEvent> {
43+
44+
private static final Logger LOGGER = LoggerFactory.getLogger(McpServerCacheInvalidateService.class);
45+
46+
private final McpServerIndex mcpServerIndex;
47+
48+
@Autowired
49+
public McpServerCacheInvalidateService(McpServerIndex mcpServerIndex) {
50+
this.mcpServerIndex = mcpServerIndex;
51+
NotifyCenter.registerSubscriber(this);
52+
}
53+
54+
/**
55+
* Handle ConfigDataChangeEvent to invalidate MCP server cache.
56+
*
57+
* @param event configuration change event
58+
*/
59+
void handleConfigDataChangeEvent(LocalDataChangeEvent event) {
60+
// Check if the configuration is MCP server related
61+
String groupKey = event.groupKey;
62+
63+
String[] strings = GroupKey.parseKey(groupKey);
64+
String dataId = strings[0];
65+
String group = strings[1];
66+
String tenant = strings.length > 2 ? strings[2] : "";
67+
if (!isMcpServerConfig(group)) {
68+
return;
69+
}
70+
71+
// Extract server ID from dataId
72+
String serverId = extractServerIdFromDataId(group, dataId);
73+
if (StringUtils.isEmpty(serverId)) {
74+
LOGGER.warn("Failed to extract server ID from dataId: {}, group: {}", dataId, group);
75+
return;
76+
}
77+
78+
// Invalidate cache
79+
invalidateCache(tenant, serverId);
80+
81+
LOGGER.info("Handled MCP server config change event: namespaceId={}, group={}, dataId={}, serverId={}",
82+
tenant, group, dataId, serverId);
83+
}
84+
85+
/**
86+
* Check if the configuration group is MCP server related.
87+
*
88+
* @param group configuration group
89+
* @return true if the group is MCP server related
90+
*/
91+
private boolean isMcpServerConfig(String group) {
92+
return Constants.MCP_SERVER_VERSIONS_GROUP.equals(group);
93+
}
94+
95+
/**
96+
* Extract server ID from dataId based on the configuration group.
97+
*
98+
* <p>MCP server configurations follow these naming patterns:</p>
99+
* <ul>
100+
* <li>Version info: {serverId}-mcp-versions.json (group: mcp-server-versions)</li>
101+
* <li>Server spec: {serverId}-{version}-mcp-server.json (group: mcp-server)</li>
102+
* <li>Tool spec: {serverId}-{version}-mcp-tools.json (group: mcp-tools)</li>
103+
* </ul>
104+
*
105+
* @param group configuration group
106+
* @param dataId configuration dataId
107+
* @return extracted server ID, or null if extraction fails
108+
*/
109+
private String extractServerIdFromDataId(String group, String dataId) {
110+
if (Constants.MCP_SERVER_VERSIONS_GROUP.equals(group)) {
111+
// Version info: remove "-mcp-versions.json" suffix
112+
if (StringUtils.isNotEmpty(dataId) && dataId.endsWith(Constants.MCP_SERVER_VERSION_DATA_ID_SUFFIX)) {
113+
return dataId.substring(0, dataId.length() - Constants.MCP_SERVER_VERSION_DATA_ID_SUFFIX.length());
114+
}
115+
}
116+
return null;
117+
}
118+
119+
/**
120+
* Invalidate MCP server cache by server ID.
121+
*
122+
* <p>This method is idempotent - calling it multiple times with the same
123+
* serverId will not cause any side effects.</p>
124+
*
125+
* @param namespaceId namespace ID
126+
* @param serverId MCP server ID
127+
*/
128+
private void invalidateCache(String namespaceId, String serverId) {
129+
try {
130+
// Clear cache by server ID
131+
mcpServerIndex.removeMcpServerById(serverId);
132+
133+
LOGGER.info("MCP Server cache invalidated successfully: namespaceId={}, serverId={}", namespaceId,
134+
serverId);
135+
} catch (Exception e) {
136+
// Cache invalidation failure should not affect configuration deletion
137+
LOGGER.error("Failed to invalidate MCP Server cache: namespaceId={}, serverId={}, error={}", namespaceId,
138+
serverId, e.getMessage(), e);
139+
}
140+
}
141+
142+
@Override
143+
public void onEvent(LocalDataChangeEvent event) {
144+
handleConfigDataChangeEvent(event);
145+
}
146+
147+
@Override
148+
public Class<? extends Event> subscribeType() {
149+
return LocalDataChangeEvent.class;
150+
}
151+
}

ai/src/test/java/com/alibaba/nacos/ai/index/CachedMcpServerIndexTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ void testSearchMcpServerByName() {
252252
eq(Constants.MCP_SERVER_VERSIONS_GROUP), eq(namespaceId), any());
253253

254254
// 验证缓存被更新
255-
verify(cacheIndex).updateIndex(eq(namespaceId), eq(mcpName), eq(mcpId));
255+
verify(cacheIndex).updateIndex(eq(namespaceId), any(), eq(mcpId));
256256
}
257257

258258
@Test

0 commit comments

Comments
 (0)