本地 RAG 知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

418 lines
16 KiB

package com.wok.supportbot.controller;
import com.wok.supportbot.app.AssistantApp;
import com.wok.supportbot.config.McpClientManager;
import com.wok.supportbot.entity.McpServerConfig;
import com.wok.supportbot.mcp.McpToolCallbackAdapter;
import com.wok.supportbot.service.McpServerConfigService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* MCP Server 配置管理控制器
* 提供 MCP Server 配置的增删改查、启用/禁用、连接测试、缓存刷新等 API
*/
@RestController
@Slf4j
public class McpServerConfigController {
@Autowired
private McpServerConfigService mcpServerConfigService;
@Autowired
private McpClientManager mcpClientManager;
@Autowired
private AssistantApp assistantApp;
// ==================== 分页列表 ====================
/**
* 获取 MCP Server 配置列表(分页)
*
* @param page 页码(默认1)
* @param size 每页大小(默认10)
* @return 分页配置列表
*/
@GetMapping("/mcp-server/list")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> listConfigs(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String sortField,
@RequestParam(required = false) String sortOrder) {
try {
Map<String, Object> result = mcpServerConfigService.listConfigs(page, size, sortField, sortOrder);
Map<String, Object> data = new LinkedHashMap<>();
data.put("success", true);
data.put("data", result.get("records"));
data.put("total", result.get("total"));
data.put("page", result.get("page"));
data.put("size", result.get("size"));
data.put("pages", result.get("pages"));
return ResponseEntity.ok(data);
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "查询失败:" + e.getMessage()
));
}
}
// ==================== 单条详情 ====================
/**
* 获取单条 MCP Server 配置详情
*
* @param id 配置ID
* @return 配置详情
*/
@GetMapping("/mcp-server/{id}")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> getConfigDetail(@PathVariable("id") Long id) {
try {
Map<String, Object> config = mcpServerConfigService.getConfigDetail(id);
if (config == null) {
return ResponseEntity.status(404).body(Map.of(
"success", false,
"message", "配置不存在"
));
}
return ResponseEntity.ok(Map.of(
"success", true,
"data", config
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "查询失败:" + e.getMessage()
));
}
}
// ==================== 可用工具列表 ====================
/**
* 获取所有已启用 MCP Server 暴露的工具清单(按 Server 分组)。
* 工具名为带 mcp_ 前缀的注册名,与角色授权(allowed_mcp_tools)存储值一致,供「客服角色管理」授权 UI 使用。
*
* @return [{ configId, serverName, transportType, tools: [{ name, rawName, description }] }]
*/
@GetMapping("/mcp-server/tools")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> listAvailableTools() {
try {
List<Map<String, Object>> servers = mcpClientManager.listAvailableTools();
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> server : servers) {
Map<String, Object> out = new LinkedHashMap<>();
out.put("configId", server.get("config_id"));
out.put("serverName", server.get("name"));
out.put("transportType", server.get("transport_type"));
List<Map<String, Object>> tools = new ArrayList<>();
@SuppressWarnings("unchecked")
List<Map<String, Object>> rawTools = (List<Map<String, Object>>) server.get("tools");
if (rawTools != null) {
String configId = (String) server.get("config_id");
for (Map<String, Object> tool : rawTools) {
String rawName = (String) tool.get("name");
Map<String, Object> toolOut = new LinkedHashMap<>();
toolOut.put("name", McpToolCallbackAdapter.buildToolName(configId, rawName));
toolOut.put("rawName", rawName);
toolOut.put("description", tool.get("description"));
tools.add(toolOut);
}
}
out.put("tools", tools);
result.add(out);
}
return ResponseEntity.ok(Map.of("success", true, "data", result));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "获取工具列表失败:" + e.getMessage()
));
}
}
// ==================== 新建配置 ====================
/**
* 新建 MCP Server 配置
*
* @param config 配置对象
* @return 创建结果
*/
@PostMapping("/mcp-server")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> createConfig(@RequestBody McpServerConfig config) {
try {
Map<String, Object> created = mcpServerConfigService.createConfig(config);
// 增量操作:仅为新建配置创建客户端,不影响其他已有连接
Object idObj = created.get("id");
if (idObj != null) {
try {
mcpClientManager.addClient(Long.parseLong(idObj.toString()));
} catch (Exception e) {
log.warn("新建配置后创建客户端失败(不影响配置保存): {}", e.getMessage());
}
// MCP 工具清单可能变化,清空 AssistantApp 的 ChatClient 缓存,避免继续使用旧工具集
assistantApp.clearCache();
}
return ResponseEntity.ok(Map.of(
"success", true,
"data", created,
"message", "配置创建成功"
));
} catch (IllegalArgumentException e) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"message", e.getMessage()
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "创建失败:" + e.getMessage()
));
}
}
// ==================== 更新配置 ====================
/**
* 更新 MCP Server 配置
*
* @param id 配置ID
* @param config 更新内容
* @return 更新结果
*/
@PutMapping("/mcp-server/{id}")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> updateConfig(
@PathVariable("id") Long id,
@RequestBody McpServerConfig config) {
try {
Map<String, Object> updated = mcpServerConfigService.updateConfig(id, config);
// 增量操作:仅重建更新的配置客户端,不影响其他已有连接
mcpClientManager.rebuildClient(id);
// MCP 工具清单可能变化,清空 AssistantApp 的 ChatClient 缓存,避免继续使用旧工具集
assistantApp.clearCache();
return ResponseEntity.ok(Map.of(
"success", true,
"data", updated,
"message", "配置更新成功"
));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"message", e.getMessage()
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "更新失败:" + e.getMessage()
));
}
}
// ==================== 删除配置 ====================
/**
* 删除 MCP Server 配置(逻辑删除)
*
* @param id 配置ID
* @return 删除结果
*/
@DeleteMapping("/mcp-server/{id}")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> deleteConfig(@PathVariable("id") Long id) {
try {
mcpServerConfigService.deleteConfig(id);
// 增量操作:仅移除被删除的配置客户端,不影响其他已有连接
mcpClientManager.removeClient(id);
// MCP 工具清单可能变化,清空 AssistantApp 的 ChatClient 缓存,避免继续使用旧工具集
assistantApp.clearCache();
return ResponseEntity.ok(Map.of(
"success", true,
"message", "配置删除成功"
));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"message", e.getMessage()
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "删除失败:" + e.getMessage()
));
}
}
// ==================== 启用/禁用 ====================
/**
* 切换配置启用/禁用状态
*
* @param id 配置ID
* @param body 请求体(可选):{active: true/false}
* @param active query 兜底参数(可选):兼容不带 body 的旧调用方(active 放 query string)
* @return 操作结果
*/
@PutMapping("/mcp-server/{id}/toggle")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> toggleActive(
@PathVariable("id") Long id,
@RequestBody(required = false) Map<String, Boolean> body,
@RequestParam(value = "active", required = false) Boolean activeParam) {
try {
Boolean active = (body != null) ? body.get("active") : null;
if (active == null) {
active = activeParam;
}
if (active == null) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"message", "active 参数不能为空"
));
}
mcpServerConfigService.toggleActive(id, active);
// 增量操作:启用时重建客户端,禁用时移除客户端
if (active) {
mcpClientManager.rebuildClient(id);
} else {
mcpClientManager.disableClient(id);
}
// MCP 工具清单可能变化,清空 AssistantApp 的 ChatClient 缓存,避免继续使用旧工具集
assistantApp.clearCache();
return ResponseEntity.ok(Map.of(
"success", true,
"message", active ? "配置已启用" : "配置已禁用"
));
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(Map.of(
"success", false,
"message", e.getMessage()
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "操作失败:" + e.getMessage()
));
}
}
// ==================== 测试连接 ====================
/**
* 测试 MCP Server 连接
* 创建临时客户端进行握手 + 工具列表查询,不影响正常缓存
*
* @param id 配置ID
* @return 测试结果(success / latencyMs / toolsCount / errorMessage)
*/
@PostMapping("/mcp-server/{id}/test")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> testConnection(@PathVariable("id") Long id) {
try {
Map<String, Object> result = mcpClientManager.testConnection(id);
return ResponseEntity.ok(result);
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "测试失败:" + e.getMessage()
));
}
}
// ==================== 刷新连接 ====================
/**
* 刷新所有 MCP 客户端连接
* 清空现有缓存并重新建立所有已启用配置的客户端连接
*
* @return 刷新结果
*/
@PostMapping("/mcp-server/refresh")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> refreshAll() {
try {
mcpClientManager.refreshAll();
// MCP 工具清单可能变化,清空 AssistantApp 的 ChatClient 缓存,避免继续使用旧工具集
assistantApp.clearCache();
return ResponseEntity.ok(Map.of(
"success", true,
"message", "MCP 客户端连接已刷新"
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "刷新失败:" + e.getMessage()
));
}
}
// ==================== 健康检查 ====================
/**
* 获取所有 MCP Server 的健康状态
* 返回各配置的在线/离线状态、延迟、最后检查时间等信息
*
* @return 所有 MCP Server 的健康状态
*/
@GetMapping("/mcp-server/health")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> getAllHealthStatus() {
try {
Map<String, McpClientManager.HealthStatus> healthMap = mcpClientManager.getAllHealthStatus();
Map<String, Object> data = new LinkedHashMap<>();
data.put("success", true);
data.put("data", healthMap);
return ResponseEntity.ok(data);
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "获取健康状态失败:" + e.getMessage()
));
}
}
/**
* 手动触发单个 MCP Server 的健康检查
* 对指定配置的客户端执行 listTools 探测,返回最新健康状态
*
* @param id 配置ID
* @return 该配置的健康状态
*/
@PostMapping("/mcp-server/{id}/health-check")
@PreAuthorize("hasRole('admin')")
public ResponseEntity<Map<String, Object>> checkSingleHealth(@PathVariable("id") Long id) {
try {
McpClientManager.HealthStatus status = mcpClientManager.checkHealthForConfig(id);
return ResponseEntity.ok(Map.of(
"success", true,
"data", Map.of(
"configId", id.toString(),
"status", status.status(),
"latencyMs", status.latencyMs(),
"lastCheckTime", status.lastCheckTime().toString(),
"errorMessage", status.errorMessage() != null ? status.errorMessage() : ""
)
));
} catch (Exception e) {
return ResponseEntity.status(500).body(Map.of(
"success", false,
"message", "健康检查失败:" + e.getMessage()
));
}
}
}