Skip to content

Commit 6ef0bc5

Browse files
authored
feat: improve mcp server manage task 6\7 (higress-group#565)
1 parent 2ad568a commit 6ef0bc5

File tree

8 files changed

+185
-22
lines changed

8 files changed

+185
-22
lines changed

README.md

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
Gateway Console for Higress
55
</h1>
66

7-
87
Higress Console 用于管理 Higress 的配置规则及其他开箱即用的能力集成,首个可用版本考虑基于 kubernetes 部署环境,预期包含服务管理、路由管理、域名管理等基础能力。
98
后续规划逐步迭代可观测能力、插件能力、登录管理能力,感兴趣的小伙伴一起 Hi~ gress~
109

@@ -25,7 +24,8 @@ kubectl apply -f deploy/install.yaml
2524
### 前端项目
2625

2726
#### 第一步、配置 Node 环境
28-
注:建议Node版本选择长期稳定支持版本 16.18.1及以上
27+
28+
注:建议 Node 版本选择长期稳定支持版本 16.18.1 及以上
2929

3030
#### 第二步、安装依赖
3131

@@ -60,17 +60,78 @@ cd backend && sh build.sh
6060
```
6161

6262
#### 第三步、部署 & 启动
63+
6364
```bash
6465
sh start.sh --local
6566
```
6667

6768
#### 第四步、访问
6869

6970
主页,默认 8080 端口
71+
7072
```html
7173
http://localhost:8080
7274
```
75+
7376
swagger,访问 swagger 页面了解 API 情况。
77+
7478
```html
7579
http://localhost:8080/swagger-ui/index.html
7680
```
81+
82+
# Higress Console
83+
84+
## 功能说明
85+
86+
### MCP Server Redis 配置验证
87+
88+
在用户新增 OpenAPI 类型的 MCP Server 时,系统会自动验证 `higress-config` 中的 Redis 配置。如果 Redis 地址仍为占位符,系统会提示用户配置正确的 Redis 地址,否则 MCP 功能将不可用。
89+
90+
#### 验证逻辑
91+
92+
1. **检查 ConfigMap 存在性**:验证 `higress-config` ConfigMap 是否存在且可读
93+
2. **检查 higress 配置项**:验证 ConfigMap 中是否包含 `higress` 配置项
94+
3. **检查 mcpServer 配置项**:验证 `higress` 配置中是否包含 `mcpServer` 配置项
95+
4. **检查 Redis 配置**:验证 `mcpServer` 配置中是否包含 Redis 配置
96+
5. **检查占位符**:验证 Redis 地址、用户名、密码是否为占位符值
97+
98+
#### 占位符检测
99+
100+
系统会检测以下占位符值:
101+
102+
- `address`: `REDIS_PLACEHOLDER_ADDRESS` (`"your.redis.host:6379"`)
103+
104+
#### 错误提示
105+
106+
当检测到占位符时,系统会记录相关日志并抛出自定义异常,同时提供详细的错误信息,包括:
107+
108+
- 当前配置的详细状态
109+
- 需要修改的配置项
110+
- 配置错误的后果说明
111+
112+
#### 实现位置
113+
114+
验证逻辑位于 `OpenApiSaveStrategy.validateRedisConfiguration()` 方法中,在保存 OpenAPI 类型的 MCP Server 时自动执行。
115+
116+
## 开发说明
117+
118+
### 代码结构
119+
120+
```
121+
backend/sdk/src/main/java/com/alibaba/higress/sdk/service/mcp/save/
122+
├── OpenApiSaveStrategy.java # OpenAPI MCP Server 保存策略
123+
├── DatabaseSaveStrategy.java # Database MCP Server 保存策略
124+
└── AbstractMcpServerSaveStrategy.java # 抽象保存策略基类
125+
```
126+
127+
### 测试
128+
129+
相关测试位于 `backend/sdk/src/test/java/com/alibaba/higress/sdk/service/McpServerServiceTest.java` 中的 `testRedisConfigurationValidation()` 方法。
130+
131+
## 使用示例
132+
133+
当用户尝试创建 OpenAPI 类型的 MCP Server 时,如果 Redis 配置不正确,会收到类似以下的错误信息:
134+
135+
```
136+
Redis 配置仍为占位符,请配置正确的 Redis 地址。当前配置:address=your.redis.host:6379, username=your_username, password=已配置。请修改 higress-config 中的 Redis 配置,否则 MCP 功能将不可用。
137+
```

backend/console/src/main/java/com/alibaba/higress/console/service/SystemServiceImpl.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.alibaba.higress.console.model.User;
4242
import com.alibaba.higress.console.util.CertificateUtil;
4343
import com.alibaba.higress.sdk.constant.HigressConstants;
44+
import com.alibaba.higress.sdk.constant.KubernetesConstants;
4445
import com.alibaba.higress.sdk.exception.BusinessException;
4546
import com.alibaba.higress.sdk.exception.ResourceConflictException;
4647
import com.alibaba.higress.sdk.exception.ValidationException;
@@ -77,7 +78,6 @@ public class SystemServiceImpl implements SystemService {
7778
private static final String DEFAULT_ROUTE_NAME = "default";
7879
private static final String UNKNOWN = "unknown";
7980
private static final String COMMIT_ID;
80-
private static final String HIGRESS_CONFIG = "higress-config";
8181
private static final Set<String> REQUIRED_HIGRESS_CONFIG_KEYS = Sets.newHashSet("higress", "mesh", "meshNetworks");
8282

8383
static {
@@ -267,9 +267,9 @@ public SystemInfo getSystemInfo() {
267267
public String getHigressConfig() {
268268
V1ConfigMap configMap;
269269
try {
270-
configMap = kubernetesClientService.readConfigMap(HIGRESS_CONFIG);
270+
configMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
271271
} catch (ApiException e) {
272-
throw new BusinessException("Failed to load " + HIGRESS_CONFIG + " config map.", e);
272+
throw new BusinessException("Failed to load " + KubernetesConstants.HIGRESS_CONFIG + " config map.", e);
273273
}
274274
cleanUpConfigMap(configMap);
275275
return kubernetesClientService.saveToYaml(configMap);
@@ -283,9 +283,9 @@ public String setHigressConfig(String config) {
283283

284284
V1ConfigMap currentConfigMap;
285285
try {
286-
currentConfigMap = kubernetesClientService.readConfigMap(HIGRESS_CONFIG);
286+
currentConfigMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
287287
} catch (ApiException e) {
288-
throw new BusinessException("Failed to load " + HIGRESS_CONFIG + " config map.", e);
288+
throw new BusinessException("Failed to load " + KubernetesConstants.HIGRESS_CONFIG + " config map.", e);
289289
}
290290

291291
String resourceVersion = Objects.requireNonNull(newConfigMap.getMetadata()).getResourceVersion();
@@ -299,7 +299,8 @@ public String setHigressConfig(String config) {
299299
if (e.getCode() == HttpStatus.CONFLICT) {
300300
throw new ResourceConflictException();
301301
}
302-
throw new BusinessException("Error occurs when replacing the " + HIGRESS_CONFIG + " ConfigMap.", e);
302+
throw new BusinessException(
303+
"Error occurs when replacing the " + KubernetesConstants.HIGRESS_CONFIG + " ConfigMap.", e);
303304
}
304305

305306
cleanUpConfigMap(updatedConfigMap);

backend/sdk/src/main/java/com/alibaba/higress/sdk/constant/KubernetesConstants.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ public class KubernetesConstants {
2626
public static final String SECRET_TLS_KEY_FIELD = "tls.key";
2727
public static final String YAML_SEPARATOR = "---\n";
2828

29+
public static final String HIGRESS_CONFIG = "higress-config";
30+
2931
public static class Annotation {
3032
public static final String KEY_PREFIX = "higress.io/";
3133
public static final String NGINX_INGRESS_KEY_PREFIX = "nginx.ingress.kubernetes.io/";

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/mcp/McpServerConfigMapHelper.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.commons.collections4.CollectionUtils;
2727
import org.apache.commons.lang3.StringUtils;
2828

29+
import com.alibaba.higress.sdk.constant.KubernetesConstants;
2930
import com.alibaba.higress.sdk.constant.McpConstants;
3031
import com.alibaba.higress.sdk.constant.Separators;
3132
import com.alibaba.higress.sdk.exception.BusinessException;
@@ -49,7 +50,6 @@
4950
*/
5051
public class McpServerConfigMapHelper {
5152

52-
protected static final String HIGRESS_CONFIG = "higress-config";
5353
protected static final String MCP_CONFIG_KEY = "higress";
5454
protected static final String MCP_SERVER_KEY = "mcpServer";
5555
protected static final String MATCH_LIST_KEY = "match_list";
@@ -87,13 +87,13 @@ public McpServerConfigMap.MatchList generateMatchList(McpServer mcpInstance) {
8787

8888
public void updateServerConfig(Consumer<List<McpServerConfigMap.Server>> updateFunction) {
8989
try {
90-
V1ConfigMap configMap = kubernetesClientService.readConfigMap(HIGRESS_CONFIG);
90+
V1ConfigMap configMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
9191
McpServerConfigMap mcpConfig = getMcpConfig(configMap);
9292
updateFunction.accept(mcpConfig.getServers());
9393

9494
updateMcpConfig2ConfigMap(configMap, mcpConfig);
9595
} catch (Exception e) {
96-
throw new BusinessException("Failed to update " + HIGRESS_CONFIG + " config map.", e);
96+
throw new BusinessException("Failed to update " + KubernetesConstants.HIGRESS_CONFIG + " config map.", e);
9797
}
9898
}
9999

@@ -202,7 +202,7 @@ protected void updateMcpConfig2ConfigMap(V1ConfigMap configMap, McpServerConfigM
202202

203203
kubernetesClientService.replaceConfigMap(configMap);
204204
} catch (Exception e) {
205-
throw new BusinessException("Failed to update " + HIGRESS_CONFIG + " config map.", e);
205+
throw new BusinessException("Failed to update " + KubernetesConstants.HIGRESS_CONFIG + " config map.", e);
206206
}
207207
}
208208

@@ -221,7 +221,7 @@ public void addOrUpdateMatchRulePath(McpServerConfigMap.MatchList matchItem) {
221221

222222
public void updateMatchList(Consumer<List<Map<String, Object>>> updateFunction) {
223223
try {
224-
V1ConfigMap configMap = kubernetesClientService.readConfigMap(HIGRESS_CONFIG);
224+
V1ConfigMap configMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
225225
if (configMap != null && configMap.getData() != null) {
226226
String higressConfigYaml = configMap.getData().get(MCP_CONFIG_KEY);
227227
Map<String, Object> higressConfig =
@@ -241,7 +241,7 @@ public void updateMatchList(Consumer<List<Map<String, Object>>> updateFunction)
241241
kubernetesClientService.replaceConfigMap(configMap);
242242
}
243243
} catch (Exception e) {
244-
throw new BusinessException("Failed to update " + HIGRESS_CONFIG + " config map.", e);
244+
throw new BusinessException("Failed to update " + KubernetesConstants.HIGRESS_CONFIG + " config map.", e);
245245
}
246246
}
247247

@@ -260,9 +260,9 @@ private List<Map<String, Object>> getExistMatchListValue(Map<String, Object> mcp
260260

261261
public void initMcpServerConfig() {
262262
try {
263-
V1ConfigMap configMap = kubernetesClientService.readConfigMap(HIGRESS_CONFIG);
263+
V1ConfigMap configMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
264264
if (Objects.isNull(configMap) || Objects.isNull(configMap.getData())) {
265-
throw new NotFoundException("configMap is empty for name = " + HIGRESS_CONFIG);
265+
throw new NotFoundException("configMap is empty for name = " + KubernetesConstants.HIGRESS_CONFIG);
266266
}
267267

268268
String higressConfigYaml = configMap.getData().get(MCP_CONFIG_KEY);
@@ -307,7 +307,7 @@ public void initMcpServerConfig() {
307307
Objects.requireNonNull(configMap.getData()).put(MCP_CONFIG_KEY, updatedHigressConfigYaml);
308308
kubernetesClientService.replaceConfigMap(configMap);
309309
} catch (Exception e) {
310-
throw new BusinessException("Failed to update " + HIGRESS_CONFIG + " config map.", e);
310+
throw new BusinessException("Failed to update " + KubernetesConstants.HIGRESS_CONFIG + " config map.", e);
311311
}
312312
}
313313
}

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/mcp/detail/AbstractMcpServerDetailStrategy.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@
3131
*/
3232
public abstract class AbstractMcpServerDetailStrategy implements McpServerDetailStrategy {
3333

34-
protected static final String HIGRESS_CONFIG = "higress-config";
35-
3634
protected static final ObjectMapper YAML = new ObjectMapper(new YAMLFactory()
3735
.enable(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE).disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
3836

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/mcp/detail/DatabaseDetailStrategy.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import org.apache.commons.lang3.StringUtils;
2121

22+
import com.alibaba.higress.sdk.constant.KubernetesConstants;
2223
import com.alibaba.higress.sdk.model.Route;
2324
import com.alibaba.higress.sdk.model.mcp.McpServer;
2425
import com.alibaba.higress.sdk.model.mcp.McpServerConfigMap;
@@ -57,7 +58,7 @@ protected void completeInfo(Route route, McpServer result) {
5758
private void completeConfigFields(String name, McpServer result) {
5859
V1ConfigMap configMap = null;
5960
try {
60-
configMap = kubernetesClientService.readConfigMap(HIGRESS_CONFIG);
61+
configMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
6162
} catch (Exception e) {
6263
log.error("Failed to get mcp server list", e);
6364
}

backend/sdk/src/main/java/com/alibaba/higress/sdk/service/mcp/save/OpenApiSaveStrategy.java

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,29 @@
2020

2121
import org.apache.commons.lang3.StringUtils;
2222

23+
import com.alibaba.higress.sdk.constant.KubernetesConstants;
2324
import com.alibaba.higress.sdk.exception.BusinessException;
25+
import com.alibaba.higress.sdk.exception.ValidationException;
2426
import com.alibaba.higress.sdk.model.WasmPluginInstance;
2527
import com.alibaba.higress.sdk.model.WasmPluginInstanceScope;
2628
import com.alibaba.higress.sdk.model.mcp.McpServer;
29+
import com.alibaba.higress.sdk.model.mcp.McpServerConfigMap;
2730
import com.alibaba.higress.sdk.model.mcp.McpServerTypeEnum;
2831
import com.alibaba.higress.sdk.service.RouteService;
2932
import com.alibaba.higress.sdk.service.WasmPluginInstanceService;
3033
import com.alibaba.higress.sdk.service.consumer.ConsumerService;
3134
import com.alibaba.higress.sdk.service.kubernetes.KubernetesClientService;
3235
import com.alibaba.higress.sdk.service.kubernetes.KubernetesModelConverter;
36+
import com.alibaba.higress.sdk.service.mcp.McpServerConfigMapHelper;
3337
import com.alibaba.higress.sdk.service.mcp.McpServerHelper;
3438
import com.alibaba.higress.sdk.util.MapUtil;
3539
import com.fasterxml.jackson.core.JsonProcessingException;
40+
import com.fasterxml.jackson.databind.DeserializationFeature;
41+
import com.fasterxml.jackson.databind.ObjectMapper;
42+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
43+
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
3644

45+
import io.kubernetes.client.openapi.models.V1ConfigMap;
3746
import lombok.extern.slf4j.Slf4j;
3847

3948
/**
@@ -44,6 +53,15 @@
4453
@Slf4j
4554
public class OpenApiSaveStrategy extends AbstractMcpServerSaveStrategy {
4655

56+
private static final String REDIS_PLACEHOLDER_ADDRESS = "your.redis.host:6379";
57+
58+
private static final ObjectMapper YAML_MAPPER = new ObjectMapper(new YAMLFactory()
59+
.enable(YAMLGenerator.Feature.LITERAL_BLOCK_STYLE).disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
60+
61+
static {
62+
YAML_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
63+
}
64+
4765
private final WasmPluginInstanceService wasmPluginInstanceService;
4866

4967
public OpenApiSaveStrategy(KubernetesClientService kubernetesClientService,
@@ -60,10 +78,51 @@ public boolean support(McpServer mcpServer) {
6078

6179
@Override
6280
protected void doSaveMcpServerConfig(McpServer mcpInstance) {
81+
// validate Redis config
82+
validateRedisConfiguration();
6383
WasmPluginInstance wasmPluginInstanceRequest = buildWasmPluginInstanceRequest(mcpInstance);
6484
wasmPluginInstanceService.addOrUpdate(wasmPluginInstanceRequest);
6585
}
6686

87+
/**
88+
* Validate Redis configuration in higress-config If Redis address is still a placeholder, prompt user to configure
89+
* correct Redis address
90+
*/
91+
private void validateRedisConfiguration() {
92+
McpServerConfigMap.RedisConfig redisConfig = null;
93+
try {
94+
95+
V1ConfigMap configMap = kubernetesClientService.readConfigMap(KubernetesConstants.HIGRESS_CONFIG);
96+
97+
McpServerConfigMap mcpConfig = McpServerConfigMapHelper.getMcpConfig(configMap);
98+
99+
redisConfig = mcpConfig.getRedis();
100+
101+
} catch (Exception e) {
102+
log.error("Error reading higress-config ConfigMap", e);
103+
throw new ValidationException(
104+
"OpenAI to MCP functionality requires Redis configuration, but Redis configuration is missing in higress-config. Please configure correct Redis address first, otherwise OpenAI to MCP functionality will be unavailable.");
105+
}
106+
107+
if (redisConfig == null) {
108+
throw new ValidationException(
109+
"OpenAI to MCP functionality requires Redis configuration, but Redis configuration is missing in higress-config. Please configure correct Redis address first, otherwise OpenAI to MCP functionality will be unavailable.");
110+
}
111+
String address = redisConfig.getAddress();
112+
113+
// Check if address is null or empty
114+
if (StringUtils.isBlank(address)) {
115+
throw new ValidationException(
116+
"OpenAI to MCP functionality requires Redis configuration, but Redis address is not configured. Please modify Redis configuration in higress-config, otherwise OpenAI to MCP functionality will be unavailable.");
117+
}
118+
// Check if address is the placeholder value
119+
if (REDIS_PLACEHOLDER_ADDRESS.equals(address)) {
120+
throw new ValidationException(
121+
"OpenAI to MCP functionality requires Redis configuration, but Redis address is still a placeholder. Please modify Redis configuration in higress-config, otherwise OpenAI to MCP functionality will be unavailable.");
122+
}
123+
124+
}
125+
67126
private WasmPluginInstance buildWasmPluginInstanceRequest(McpServer mcpInstance) {
68127
WasmPluginInstance pluginInstance =
69128
WasmPluginInstance.builder().pluginName(DEFAULT_MCP_PLUGIN).internal(true).build();
@@ -79,7 +138,7 @@ private WasmPluginInstance buildWasmPluginInstanceRequest(McpServer mcpInstance)
79138
Map<String, Object> rootMap = new HashMap<>();
80139
rootMap.put("server", serverMap);
81140
try {
82-
String yamlString = YAML.writeValueAsString(rootMap);
141+
String yamlString = YAML_MAPPER.writeValueAsString(rootMap);
83142
pluginInstance.setRawConfigurations(yamlString);
84143
pluginInstance.setEnabled(false);
85144
} catch (JsonProcessingException e) {

0 commit comments

Comments
 (0)