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 }