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,167 @@
|
||||
package aitools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"opsxdb/server/internal/db"
|
||||
)
|
||||
|
||||
// SavedConnection 与桌面 store SavedConnection JSON 对齐(磁盘形态,密码可为 ox1. 混淆)。 // Logan.Li
|
||||
type SavedConnection struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Driver string `json:"driver"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
User string `json:"user"`
|
||||
Password string `json:"password"`
|
||||
Database string `json:"database"`
|
||||
Service string `json:"service,omitempty"`
|
||||
SSL bool `json:"ssl,omitempty"`
|
||||
SSLMode string `json:"sslMode,omitempty"`
|
||||
SSLCA string `json:"sslCA,omitempty"`
|
||||
SSLCert string `json:"sslCert,omitempty"`
|
||||
SSLKey string `json:"sslKey,omitempty"`
|
||||
RedisMode string `json:"redisMode,omitempty"`
|
||||
RedisNodes string `json:"redisNodes,omitempty"`
|
||||
MasterName string `json:"masterName,omitempty"`
|
||||
SentinelPassword string `json:"sentinelPassword,omitempty"`
|
||||
SSHEnabled bool `json:"sshEnabled,omitempty"`
|
||||
SSHHost string `json:"sshHost,omitempty"`
|
||||
SSHPort int `json:"sshPort,omitempty"`
|
||||
SSHUser string `json:"sshUser,omitempty"`
|
||||
SSHAuth string `json:"sshAuth,omitempty"`
|
||||
SSHPassword string `json:"sshPassword,omitempty"`
|
||||
SSHPrivateKey string `json:"sshPrivateKey,omitempty"`
|
||||
SSHPassphrase string `json:"sshPassphrase,omitempty"`
|
||||
SavePassword *bool `json:"savePassword,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectionSummary 列表对外字段,永不含密码。
|
||||
type ConnectionSummary struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Driver string `json:"driver"`
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Database string `json:"database"`
|
||||
}
|
||||
|
||||
func ConnectionsPath() (string, error) {
|
||||
dir, err := opsxdbDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(dir, "connections.json"), nil
|
||||
}
|
||||
|
||||
// LoadConnections 读 ~/.opsxdb/connections.json。
|
||||
func LoadConnections() ([]SavedConnection, error) {
|
||||
path, err := ConnectionsPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var list []SavedConnection
|
||||
if err := json.Unmarshal(b, &list); err != nil {
|
||||
return nil, fmt.Errorf("connections.json: %w", err)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// SaveConnections 写 ~/.opsxdb/connections.json(0600);供桌面双写 API 调用。
|
||||
func SaveConnections(list []SavedConnection) error {
|
||||
path, err := ConnectionsPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.MarshalIndent(list, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(path, b, 0600)
|
||||
}
|
||||
|
||||
func FindConnection(id string) (*SavedConnection, error) {
|
||||
list, err := LoadConnections()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range list {
|
||||
if list[i].ID == id {
|
||||
c := list[i]
|
||||
return &c, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("connection not found: %s", id)
|
||||
}
|
||||
|
||||
// ToConnReq 映射为 db.ConnReq(解码 ox1. 密码)。
|
||||
func (c SavedConnection) ToConnReq() db.ConnReq {
|
||||
sslOn := c.SSL || (c.SSLMode != "" && c.SSLMode != "disable")
|
||||
mode := c.SSLMode
|
||||
if sslOn {
|
||||
if mode == "" || mode == "disable" {
|
||||
mode = "require"
|
||||
}
|
||||
} else {
|
||||
mode = "disable"
|
||||
}
|
||||
return db.ConnReq{
|
||||
Driver: c.Driver,
|
||||
Host: c.Host,
|
||||
Port: c.Port,
|
||||
User: c.User,
|
||||
Password: DecodeSecret(c.Password),
|
||||
Database: c.Database,
|
||||
Service: c.Service,
|
||||
SSL: sslOn,
|
||||
SSLMode: mode,
|
||||
SSLCA: c.SSLCA,
|
||||
SSLCert: c.SSLCert,
|
||||
SSLKey: c.SSLKey,
|
||||
RedisMode: orDefault(c.RedisMode, "standalone"),
|
||||
RedisNodes: c.RedisNodes,
|
||||
MasterName: c.MasterName,
|
||||
SentinelPassword: DecodeSecret(c.SentinelPassword),
|
||||
SSHEnabled: c.SSHEnabled,
|
||||
SSHHost: c.SSHHost,
|
||||
SSHPort: c.SSHPort,
|
||||
SSHUser: c.SSHUser,
|
||||
SSHAuth: c.SSHAuth,
|
||||
SSHPassword: DecodeSecret(c.SSHPassword),
|
||||
SSHPrivateKey: c.SSHPrivateKey,
|
||||
SSHPassphrase: DecodeSecret(c.SSHPassphrase),
|
||||
}
|
||||
}
|
||||
|
||||
func (c SavedConnection) Summary() ConnectionSummary {
|
||||
return ConnectionSummary{
|
||||
ID: c.ID,
|
||||
Name: c.Name,
|
||||
Driver: c.Driver,
|
||||
Host: c.Host,
|
||||
Port: c.Port,
|
||||
Database: c.Database,
|
||||
}
|
||||
}
|
||||
|
||||
func orDefault(s, def string) string {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return def
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in new issue
Block a user