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
This commit is contained in:
logan committed 2026-10-15 11:14:08 +08:00
1 parent 5c17d32647
commit 0b68c8344d
19 files changed
+1989 -79

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
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
}