借鉴 DBX:安装检测(Node/npm/包/Go 二进制)、只读与 allow_dangerous、 连接白名单、多客户端配置片段;GET/PUT /api/ai/mcp-policy、/mcp-install; 文档 /docs/mcp 与落地页 AI/MCP 块;对照 docs/16 勾选设置项、列仍未完清单。
101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
package aitools
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
const mcpActiveWindow = 5 * time.Minute
|
|
|
|
// MCPStatus 供侧栏 MCP 状态点 / 设置页轻量指示。
|
|
type MCPStatus struct {
|
|
Configured bool `json:"configured"`
|
|
Mode string `json:"mode"`
|
|
WhitelistCount int `json:"whitelistCount"`
|
|
WritesEnabled bool `json:"writesEnabled"`
|
|
AllowDangerous bool `json:"allowDangerous"`
|
|
Active bool `json:"active"` // 近 5 分钟有 source=mcp 审计
|
|
Recent []AuditEntry `json:"recent,omitempty"`
|
|
PolicyPath string `json:"policyPath,omitempty"`
|
|
AuditPath string `json:"auditPath,omitempty"`
|
|
}
|
|
|
|
// GetMCPStatus 读 mcp.json + 尾部审计;不探测外部 Cursor 进程。
|
|
func GetMCPStatus() MCPStatus {
|
|
st := MCPStatus{Mode: ModeReadOnly}
|
|
if path, err := PolicyPath(); err == nil {
|
|
st.PolicyPath = path
|
|
if _, err := os.Stat(path); err == nil {
|
|
st.Configured = true
|
|
}
|
|
}
|
|
if p, err := LoadPolicy(); err == nil {
|
|
st.Mode = p.Mode
|
|
if st.Mode == "" {
|
|
st.Mode = ModeReadOnly
|
|
}
|
|
st.WhitelistCount = len(p.AllowedConnectionIDs)
|
|
st.WritesEnabled = p.WritesEnabled()
|
|
st.AllowDangerous = p.AllowDangerous
|
|
st.Configured = st.Configured || st.WhitelistCount > 0
|
|
}
|
|
if ap, err := AuditPath(); err == nil {
|
|
st.AuditPath = ap
|
|
recent := tailAudit(ap, 20)
|
|
st.Recent = filterSource(recent, "mcp")
|
|
if len(st.Recent) == 0 {
|
|
// 兼容旧审计无 source:仍展示最近条目,但不标 active
|
|
st.Recent = recent
|
|
} else {
|
|
cutoff := time.Now().Add(-mcpActiveWindow)
|
|
for _, e := range st.Recent {
|
|
if t, err := time.Parse(time.RFC3339, e.Time); err == nil && t.After(cutoff) {
|
|
st.Active = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return st
|
|
}
|
|
|
|
func filterSource(in []AuditEntry, source string) []AuditEntry {
|
|
var out []AuditEntry
|
|
for _, e := range in {
|
|
if e.Source == source {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func tailAudit(path string, n int) []AuditEntry {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer f.Close()
|
|
var lines []string
|
|
sc := bufio.NewScanner(f)
|
|
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
|
for sc.Scan() {
|
|
lines = append(lines, sc.Text())
|
|
if len(lines) > n*4 { // 粗裁
|
|
lines = lines[len(lines)-n*2:]
|
|
}
|
|
}
|
|
if len(lines) > n {
|
|
lines = lines[len(lines)-n:]
|
|
}
|
|
out := make([]AuditEntry, 0, len(lines))
|
|
for _, line := range lines {
|
|
var e AuditEntry
|
|
if json.Unmarshal([]byte(line), &e) == nil {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
return out
|
|
}
|