Files
opsxdb/server/internal/aitools/policy.go
T
logan 0b68c8344d feat(mcp): P0 只读 MCP tool runtime + opsxdb-mcp stdio
- aitools 共用 runtime:list/describe/schema/query/redis(纯只读,无写确认)
- 白名单 ~/.opsxdb/mcp.json;审计 mcp-audit.jsonl;复用 danger.go
- 桌面 connections 双写 ~/.opsxdb/connections.json(ox1. 解码)
- Cursor 配置示例与任务书验收勾选更新;写操作与 Agent UI 留 P1
2026-10-15 11:14:08 +08:00

113 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}