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:
1 parent
5c17d32647
commit
0b68c8344d
19 files changed
+1989
-79
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user