- aitools 共用 runtime:list/describe/schema/query/redis(纯只读,无写确认) - 白名单 ~/.opsxdb/mcp.json;审计 mcp-audit.jsonl;复用 danger.go - 桌面 connections 双写 ~/.opsxdb/connections.json(ox1. 解码) - Cursor 配置示例与任务书验收勾选更新;写操作与 Agent UI 留 P1
113 lines
2.5 KiB
Go
113 lines
2.5 KiB
Go
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
|
||
}
|