Files
logan 1513f4bc2d feat(mcp): 设置 → MCP 子页 + 策略 API + 官网/文档接入
借鉴 DBX:安装检测(Node/npm/包/Go 二进制)、只读与 allow_dangerous、
连接白名单、多客户端配置片段;GET/PUT /api/ai/mcp-policy、/mcp-install;
文档 /docs/mcp 与落地页 AI/MCP 块;对照 docs/16 勾选设置项、列仍未完清单。
2026-10-15 14:13:20 +08:00

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
}