package aitools import ( "encoding/json" "fmt" "os" "path/filepath" "strings" ) // Mode P0 仅支持 read_only;read_write 留给 P1(写 + 确认)。 // Logan.Li const ( ModeReadOnly = "read_only" ModeReadWrite = "read_write" // P1 才启用写路径;P0 加载后仍强制只读执行 ) // Policy ~/.opsxdb/mcp.json type Policy struct { AllowedConnectionIDs []string `json:"allowed_connection_ids"` Mode string `json:"mode"` // read_only | read_write DefaultLimit int `json:"default_limit,omitempty"` MaxLimit int `json:"max_limit,omitempty"` } func DefaultPolicy() Policy { return Policy{ AllowedConnectionIDs: nil, Mode: ModeReadOnly, DefaultLimit: 100, MaxLimit: 500, } } func opsxdbDir() (string, error) { home, err := os.UserHomeDir() if err != nil { return "", err } return filepath.Join(home, ".opsxdb"), nil } func PolicyPath() (string, error) { dir, err := opsxdbDir() if err != nil { return "", err } return filepath.Join(dir, "mcp.json"), nil } // LoadPolicy 读 mcp.json;缺省文件返回 DefaultPolicy(白名单空=拒绝全部连接)。 func LoadPolicy() (Policy, error) { p := DefaultPolicy() path, err := PolicyPath() if err != nil { return p, err } b, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return p, nil } return p, err } if err := json.Unmarshal(b, &p); err != nil { return DefaultPolicy(), fmt.Errorf("mcp.json: %w", err) } p.Mode = strings.ToLower(strings.TrimSpace(p.Mode)) if p.Mode == "" { p.Mode = ModeReadOnly } if p.DefaultLimit <= 0 { p.DefaultLimit = 100 } if p.MaxLimit <= 0 { p.MaxLimit = 500 } if p.DefaultLimit > p.MaxLimit { p.DefaultLimit = p.MaxLimit } return p, nil } func (p Policy) IsReadOnly() bool { // P0:即使配置写了 read_write,执行层仍按只读拦截;此 helper 表示「策略意图」。 return p.Mode != ModeReadWrite } func (p Policy) AllowConnection(id string) error { id = strings.TrimSpace(id) if id == "" { return fmt.Errorf("connection_id required") } if len(p.AllowedConnectionIDs) == 0 { return fmt.Errorf("connection not allowed: whitelist empty (configure allowed_connection_ids in ~/.opsxdb/mcp.json)") } for _, a := range p.AllowedConnectionIDs { if a == id { return nil } } return fmt.Errorf("connection not allowed: %s not in allowed_connection_ids", id) } func (p Policy) ClampLimit(n int) int { if n <= 0 { n = p.DefaultLimit } if n > p.MaxLimit { n = p.MaxLimit } return n }