Files
mulit-agent/controller/internal/controlplane/mcp.go
T

100 lines
5.0 KiB
Go

package controlplane
import (
"errors"
"fmt"
"time"
)
// AgentDeploymentStatus summarizes controller-owned LLM, Skill and MCP application state.
type AgentDeploymentStatus struct {
AgentID string `json:"agentID"`
LLMProfile string `json:"llmProfile"`
LLMStatus string `json:"llmStatus"`
SkillTotal int `json:"skillTotal"`
SkillApplied int `json:"skillApplied"`
SkillFailed int `json:"skillFailed"`
MCPStatus string `json:"mcpStatus"`
MCPError string `json:"mcpError"`
LastUpdatedAt string `json:"lastUpdatedAt"`
}
// EnableDesktopFilesystem deploys the intentionally narrow built-in filesystem MCP.
func (c *ControlPlane) EnableDesktopFilesystem(agentIDs []string) error {
if len(agentIDs) == 0 {
return errors.New("请选择至少一个 Agent")
}
now := time.Now().UTC().Format(time.RFC3339Nano)
for _, agentID := range agentIDs {
var exists int
if err := c.db.QueryRow("SELECT COUNT(*) FROM managed_agents WHERE agent_id=?", agentID).Scan(&exists); err != nil || exists != 1 {
return errors.New("目标 Agent 不存在")
}
if _, err := c.db.Exec("INSERT INTO agent_mcp_assignments(agent_id, tool_id, deployment_version, apply_status, last_error, updated_at) VALUES (?, 'desktop-filesystem', 1, 'pending', '', ?) ON CONFLICT(agent_id, tool_id) DO UPDATE SET deployment_version=agent_mcp_assignments.deployment_version + 1, apply_status='pending', last_error='', updated_at=excluded.updated_at", agentID, now); err != nil {
return err
}
c.recordAudit("mcp.deployment_requested", "controller", "agent:"+agentID+":desktop-filesystem", "pending", "已请求部署受控桌面文件工具。")
c.pushMCP(agentID)
}
return nil
}
func (c *ControlPlane) pushMCP(agentID string) {
client := c.clientFor(agentID)
if client == nil {
return
}
var version int
if err := c.db.QueryRow("SELECT deployment_version FROM agent_mcp_assignments WHERE agent_id=? AND tool_id='desktop-filesystem'", agentID).Scan(&version); err != nil {
return
}
if client.send(map[string]any{"type": "mcp_snapshot", "tools": []map[string]any{{"id": "desktop-filesystem", "deployment_version": version, "enabled": true, "scope": "desktop-directories"}}}) == nil {
_, _ = c.db.Exec("UPDATE agent_mcp_assignments SET apply_status='sent', updated_at=? WHERE agent_id=? AND tool_id='desktop-filesystem' AND deployment_version=?", time.Now().UTC().Format(time.RFC3339Nano), agentID, version)
c.recordAudit("mcp.snapshot_sent", "controller", "agent:"+agentID+":desktop-filesystem", "sent", "已通过认证 WSS 下发受控桌面文件工具。")
}
}
func (c *ControlPlane) applyMCPAck(agentID string, message map[string]any) {
toolID, _ := message["tool_id"].(string)
version, _ := message["deployment_version"].(float64)
status, _ := message["status"].(string)
errorText, _ := message["error"].(string)
if toolID != "desktop-filesystem" || version < 1 || (status != "applied" && status != "failed") {
return
}
result, _ := c.db.Exec("UPDATE agent_mcp_assignments SET apply_status=?, last_error=?, updated_at=? WHERE agent_id=? AND tool_id=? AND deployment_version=?", status, limit(sanitizeDisplayText(errorText), 1000), time.Now().UTC().Format(time.RFC3339Nano), agentID, toolID, int(version))
if changed, _ := result.RowsAffected(); changed == 0 {
return // Stale acknowledgement from an earlier deployment; preserve newer state.
}
c.recordAudit("mcp.deployment_ack", "agent:"+agentID, "agent:"+agentID+":desktop-filesystem", status, errorText)
}
// AgentDeploymentStatuses provides the Agent page with consolidated deployment state.
func (c *ControlPlane) AgentDeploymentStatuses() ([]AgentDeploymentStatus, error) {
rows, err := c.db.Query(`SELECT a.agent_id,
COALESCE(p.name, ''), COALESCE(l.apply_status, '未部署'),
COALESCE((SELECT COUNT(*) FROM agent_skill_assignments s WHERE s.agent_id = a.agent_id), 0),
COALESCE((SELECT COUNT(*) FROM agent_skill_assignments s WHERE s.agent_id = a.agent_id AND s.apply_status = 'applied'), 0),
COALESCE((SELECT COUNT(*) FROM agent_skill_assignments s WHERE s.agent_id = a.agent_id AND s.apply_status = 'failed'), 0),
COALESCE(m.apply_status, '未启用'), COALESCE(m.last_error, ''),
COALESCE(MAX(l.updated_at, m.updated_at), '')
FROM managed_agents a
LEFT JOIN agent_llm_assignments l ON l.agent_id = a.agent_id
LEFT JOIN llm_profiles p ON p.profile_id = l.profile_id
LEFT JOIN agent_mcp_assignments m ON m.agent_id = a.agent_id AND m.tool_id = 'desktop-filesystem'
GROUP BY a.agent_id, p.name, l.apply_status, m.apply_status, m.last_error`)
if err != nil {
return nil, fmt.Errorf("无法读取 Agent 部署状态: %w", err)
}
defer rows.Close()
statuses := make([]AgentDeploymentStatus, 0)
for rows.Next() {
var status AgentDeploymentStatus
if err := rows.Scan(&status.AgentID, &status.LLMProfile, &status.LLMStatus, &status.SkillTotal, &status.SkillApplied, &status.SkillFailed, &status.MCPStatus, &status.MCPError, &status.LastUpdatedAt); err != nil {
return nil, err
}
statuses = append(statuses, status)
}
return statuses, rows.Err()
}