mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-15 21:08:01 +02:00
Add files via upload
This commit is contained in:
+14
-12
@@ -465,24 +465,26 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
// 在循环外部统一更新,避免重复调用
|
||||
h.externalMCPMgr.LoadConfigs(&h.config.ExternalMCP)
|
||||
|
||||
// 处理MCP连接状态
|
||||
// 处理MCP连接状态(异步启动,避免阻塞)
|
||||
for mcpName := range externalMCPToolMap {
|
||||
cfg := h.config.ExternalMCP.Servers[mcpName]
|
||||
// 如果MCP需要启用,确保客户端已启动
|
||||
if cfg.ExternalMCPEnable {
|
||||
// 启动外部MCP(如果未启动)
|
||||
// 启动外部MCP(如果未启动)- 异步执行,避免阻塞
|
||||
client, exists := h.externalMCPMgr.GetClient(mcpName)
|
||||
if !exists || !client.IsConnected() {
|
||||
if err := h.externalMCPMgr.StartClient(mcpName); err != nil {
|
||||
h.logger.Warn("启动外部MCP失败",
|
||||
zap.String("mcp", mcpName),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
h.logger.Info("启动外部MCP",
|
||||
zap.String("mcp", mcpName),
|
||||
)
|
||||
}
|
||||
go func(name string) {
|
||||
if err := h.externalMCPMgr.StartClient(name); err != nil {
|
||||
h.logger.Warn("启动外部MCP失败",
|
||||
zap.String("mcp", name),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
h.logger.Info("启动外部MCP",
|
||||
zap.String("mcp", name),
|
||||
)
|
||||
}
|
||||
}(mcpName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,11 +56,16 @@ func (h *ExternalMCPHandler) GetExternalMCPs(c *gin.Context) {
|
||||
}
|
||||
|
||||
toolCount := toolCounts[name]
|
||||
errorMsg := ""
|
||||
if status == "error" {
|
||||
errorMsg = h.manager.GetError(name)
|
||||
}
|
||||
|
||||
result[name] = ExternalMCPResponse{
|
||||
Config: cfg,
|
||||
Status: status,
|
||||
ToolCount: toolCount,
|
||||
Error: errorMsg,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,10 +107,17 @@ func (h *ExternalMCPHandler) GetExternalMCP(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取错误信息
|
||||
errorMsg := ""
|
||||
if status == "error" {
|
||||
errorMsg = h.manager.GetError(name)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, ExternalMCPResponse{
|
||||
Config: cfg,
|
||||
Status: status,
|
||||
ToolCount: toolCount,
|
||||
Error: errorMsg,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -238,7 +250,7 @@ func (h *ExternalMCPHandler) StartExternalMCP(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 启动客户端(这可能会花费一些时间)
|
||||
// 启动客户端(立即创建客户端并设置状态为connecting,实际连接在后台进行)
|
||||
h.logger.Info("开始启动外部MCP", zap.String("name", name))
|
||||
if err := h.manager.StartClient(name); err != nil {
|
||||
h.logger.Error("启动外部MCP失败", zap.String("name", name), zap.Error(err))
|
||||
@@ -249,16 +261,17 @@ func (h *ExternalMCPHandler) StartExternalMCP(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取连接状态
|
||||
// 获取客户端状态(应该是connecting)
|
||||
client, exists := h.manager.GetClient(name)
|
||||
status := "disconnected"
|
||||
status := "connecting"
|
||||
if exists {
|
||||
status = client.GetStatus()
|
||||
}
|
||||
|
||||
h.logger.Info("外部MCP启动完成", zap.String("name", name), zap.String("status", status))
|
||||
// 立即返回,不等待连接完成
|
||||
// 客户端会在后台异步连接,用户可以通过状态查询接口查看连接状态
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "外部MCP启动完成",
|
||||
"message": "外部MCP启动请求已提交,正在后台连接中",
|
||||
"status": status,
|
||||
})
|
||||
}
|
||||
@@ -504,7 +517,8 @@ type AddOrUpdateExternalMCPRequest struct {
|
||||
// ExternalMCPResponse 外部MCP响应
|
||||
type ExternalMCPResponse struct {
|
||||
Config config.ExternalMCPServerConfig `json:"config"`
|
||||
Status string `json:"status"` // "connected", "disconnected", "disabled", "error"
|
||||
Status string `json:"status"` // "connected", "disconnected", "disabled", "error", "connecting"
|
||||
ToolCount int `json:"tool_count"` // 工具数量
|
||||
Error string `json:"error,omitempty"` // 错误信息(仅在status为error时存在)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ type ExternalMCPManager struct {
|
||||
storage MonitorStorage // 可选的持久化存储
|
||||
executions map[string]*ToolExecution // 执行记录
|
||||
stats map[string]*ToolStats // 工具统计信息
|
||||
errors map[string]string // 错误信息
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
@@ -39,6 +40,7 @@ func NewExternalMCPManagerWithStorage(logger *zap.Logger, storage MonitorStorage
|
||||
storage: storage,
|
||||
executions: make(map[string]*ToolExecution),
|
||||
stats: make(map[string]*ToolStats),
|
||||
errors: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,12 +119,12 @@ func (m *ExternalMCPManager) StartClient(name string) error {
|
||||
|
||||
// 检查是否已经有连接的客户端
|
||||
m.mu.RLock()
|
||||
_, hasClient := m.clients[name]
|
||||
existingClient, hasClient := m.clients[name]
|
||||
m.mu.RUnlock()
|
||||
|
||||
if hasClient {
|
||||
// 检查客户端是否已连接
|
||||
if client, ok := m.GetClient(name); ok && client.IsConnected() {
|
||||
if existingClient.IsConnected() {
|
||||
// 客户端已连接,直接返回成功(目标状态已达成)
|
||||
// 更新配置为启用(确保配置一致)
|
||||
m.mu.Lock()
|
||||
@@ -132,22 +134,55 @@ func (m *ExternalMCPManager) StartClient(name string) error {
|
||||
return nil
|
||||
}
|
||||
// 如果有客户端但未连接,先关闭
|
||||
if client, ok := m.GetClient(name); ok {
|
||||
client.Close()
|
||||
m.mu.Lock()
|
||||
delete(m.clients, name)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
existingClient.Close()
|
||||
m.mu.Lock()
|
||||
delete(m.clients, name)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
// 更新配置为启用
|
||||
m.mu.Lock()
|
||||
serverCfg.ExternalMCPEnable = true
|
||||
m.configs[name] = serverCfg
|
||||
// 清除之前的错误信息(重新启动时)
|
||||
delete(m.errors, name)
|
||||
m.mu.Unlock()
|
||||
|
||||
// 连接客户端
|
||||
return m.connectClient(name, serverCfg)
|
||||
// 立即创建客户端并设置为"connecting"状态,这样前端可以立即看到状态
|
||||
client := m.createClient(serverCfg)
|
||||
if client == nil {
|
||||
return fmt.Errorf("无法创建客户端:不支持的传输模式")
|
||||
}
|
||||
|
||||
// 设置状态为connecting
|
||||
m.setClientStatus(client, "connecting")
|
||||
|
||||
// 立即保存客户端,这样前端查询时就能看到"connecting"状态
|
||||
m.mu.Lock()
|
||||
m.clients[name] = client
|
||||
m.mu.Unlock()
|
||||
|
||||
// 在后台异步进行实际连接
|
||||
go func() {
|
||||
if err := m.doConnect(name, serverCfg, client); err != nil {
|
||||
m.logger.Error("连接外部MCP客户端失败",
|
||||
zap.String("name", name),
|
||||
zap.Error(err),
|
||||
)
|
||||
// 连接失败,设置状态为error并保存错误信息
|
||||
m.setClientStatus(client, "error")
|
||||
m.mu.Lock()
|
||||
m.errors[name] = err.Error()
|
||||
m.mu.Unlock()
|
||||
} else {
|
||||
// 连接成功,清除错误信息
|
||||
m.mu.Lock()
|
||||
delete(m.errors, name)
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopClient 停止客户端
|
||||
@@ -166,6 +201,9 @@ func (m *ExternalMCPManager) StopClient(name string) error {
|
||||
delete(m.clients, name)
|
||||
}
|
||||
|
||||
// 清除错误信息
|
||||
delete(m.errors, name)
|
||||
|
||||
// 更新配置为禁用
|
||||
serverCfg.ExternalMCPEnable = false
|
||||
m.configs[name] = serverCfg
|
||||
@@ -182,6 +220,14 @@ func (m *ExternalMCPManager) GetClient(name string) (ExternalMCPClient, bool) {
|
||||
return client, exists
|
||||
}
|
||||
|
||||
// GetError 获取错误信息
|
||||
func (m *ExternalMCPManager) GetError(name string) string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
return m.errors[name]
|
||||
}
|
||||
|
||||
// GetAllTools 获取所有外部MCP的工具
|
||||
func (m *ExternalMCPManager) GetAllTools(ctx context.Context) ([]Tool, error) {
|
||||
m.mu.RLock()
|
||||
@@ -543,10 +589,8 @@ func (m *ExternalMCPManager) GetToolCounts() map[string]int {
|
||||
return result
|
||||
}
|
||||
|
||||
// connectClient 连接客户端(异步)
|
||||
func (m *ExternalMCPManager) connectClient(name string, serverCfg config.ExternalMCPServerConfig) error {
|
||||
var client ExternalMCPClient
|
||||
|
||||
// createClient 创建客户端(不连接)
|
||||
func (m *ExternalMCPManager) createClient(serverCfg config.ExternalMCPServerConfig) ExternalMCPClient {
|
||||
timeout := time.Duration(serverCfg.Timeout) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
@@ -561,29 +605,77 @@ func (m *ExternalMCPManager) connectClient(name string, serverCfg config.Externa
|
||||
} else if serverCfg.URL != "" {
|
||||
transport = "http"
|
||||
} else {
|
||||
return fmt.Errorf("无法确定传输模式: 需要指定command或url")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
switch transport {
|
||||
case "http":
|
||||
if serverCfg.URL == "" {
|
||||
return fmt.Errorf("HTTP模式需要URL")
|
||||
return nil
|
||||
}
|
||||
client = NewHTTPMCPClient(serverCfg.URL, timeout, m.logger)
|
||||
return NewHTTPMCPClient(serverCfg.URL, timeout, m.logger)
|
||||
case "stdio":
|
||||
if serverCfg.Command == "" {
|
||||
return fmt.Errorf("stdio模式需要command")
|
||||
return nil
|
||||
}
|
||||
client = NewStdioMCPClient(serverCfg.Command, serverCfg.Args, timeout, m.logger)
|
||||
return NewStdioMCPClient(serverCfg.Command, serverCfg.Args, timeout, m.logger)
|
||||
default:
|
||||
return fmt.Errorf("不支持的传输模式: %s", transport)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// doConnect 执行实际连接
|
||||
func (m *ExternalMCPManager) doConnect(name string, serverCfg config.ExternalMCPServerConfig, client ExternalMCPClient) error {
|
||||
timeout := time.Duration(serverCfg.Timeout) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
// 初始化连接
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.logger.Info("外部MCP客户端已连接",
|
||||
zap.String("name", name),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setClientStatus 设置客户端状态(通过类型断言)
|
||||
func (m *ExternalMCPManager) setClientStatus(client ExternalMCPClient, status string) {
|
||||
switch c := client.(type) {
|
||||
case *HTTPMCPClient:
|
||||
c.setStatus(status)
|
||||
case *StdioMCPClient:
|
||||
c.setStatus(status)
|
||||
}
|
||||
}
|
||||
|
||||
// connectClient 连接客户端(异步)- 保留用于向后兼容
|
||||
func (m *ExternalMCPManager) connectClient(name string, serverCfg config.ExternalMCPServerConfig) error {
|
||||
client := m.createClient(serverCfg)
|
||||
if client == nil {
|
||||
return fmt.Errorf("无法创建客户端:不支持的传输模式")
|
||||
}
|
||||
|
||||
// 设置状态为connecting
|
||||
m.setClientStatus(client, "connecting")
|
||||
|
||||
// 初始化连接
|
||||
timeout := time.Duration(serverCfg.Timeout) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Initialize(ctx); err != nil {
|
||||
m.logger.Error("初始化外部MCP客户端失败",
|
||||
zap.String("name", name),
|
||||
@@ -599,7 +691,6 @@ func (m *ExternalMCPManager) connectClient(name string, serverCfg config.Externa
|
||||
|
||||
m.logger.Info("外部MCP客户端已连接",
|
||||
zap.String("name", name),
|
||||
zap.String("transport", transport),
|
||||
)
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2331,6 +2331,18 @@ header {
|
||||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||||
}
|
||||
|
||||
.external-mcp-status.status-error {
|
||||
background: rgba(220, 53, 69, 0.12);
|
||||
color: var(--error-color);
|
||||
border: 1px solid rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.external-mcp-status.status-error::before {
|
||||
content: '❌';
|
||||
display: inline-block;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.external-mcp-item-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -2652,9 +2652,11 @@ function renderExternalMCPList(servers) {
|
||||
const status = server.status || 'disconnected';
|
||||
const statusClass = status === 'connected' ? 'status-connected' :
|
||||
status === 'connecting' ? 'status-connecting' :
|
||||
status === 'error' ? 'status-error' :
|
||||
status === 'disabled' ? 'status-disabled' : 'status-disconnected';
|
||||
const statusText = status === 'connected' ? '已连接' :
|
||||
status === 'connecting' ? '连接中...' :
|
||||
status === 'error' ? '连接失败' :
|
||||
status === 'disabled' ? '已禁用' : '未连接';
|
||||
const transport = server.config.transport || (server.config.command ? 'stdio' : 'http');
|
||||
const transportIcon = transport === 'stdio' ? '⚙️' : '🌐';
|
||||
@@ -2667,7 +2669,7 @@ function renderExternalMCPList(servers) {
|
||||
<span class="external-mcp-status ${statusClass}">${statusText}</span>
|
||||
</div>
|
||||
<div class="external-mcp-item-actions">
|
||||
${status === 'connected' || status === 'disconnected' ?
|
||||
${status === 'connected' || status === 'disconnected' || status === 'error' ?
|
||||
`<button class="btn-small" id="btn-toggle-${escapeHtml(name)}" onclick="toggleExternalMCP('${escapeHtml(name)}', '${status}')" title="${status === 'connected' ? '停止连接' : '启动连接'}">
|
||||
${status === 'connected' ? '⏸ 停止' : '▶ 启动'}
|
||||
</button>` :
|
||||
@@ -2679,6 +2681,10 @@ function renderExternalMCPList(servers) {
|
||||
<button class="btn-small btn-danger" onclick="deleteExternalMCP('${escapeHtml(name)}')" title="删除配置" ${status === 'connecting' ? 'disabled' : ''}>🗑 删除</button>
|
||||
</div>
|
||||
</div>
|
||||
${status === 'error' && server.error ? `
|
||||
<div class="external-mcp-error" style="margin: 12px 0; padding: 12px; background: #fee; border-left: 3px solid #f44; border-radius: 4px; color: #c33; font-size: 0.875rem;">
|
||||
<strong>❌ 连接错误:</strong>${escapeHtml(server.error)}
|
||||
</div>` : ''}
|
||||
<div class="external-mcp-item-details">
|
||||
<div>
|
||||
<strong>传输模式</strong>
|
||||
|
||||
Reference in New Issue
Block a user