docs(ai): 真库验收记录 + mcp-accept 用例入口

MySQL demo / Redis 真库跑通只读 MCP;补充验收说明与
cmd/opsxdb-mcp-accept。实践长文在 ~/Downloads。
This commit is contained in:
logan committed 2026-10-15 11:16:00 +08:00
1 parent 0b68c8344d
commit b8134c4503
2 files changed
+109

No files matched your search

+5
View File
@@ -191,3 +191,8 @@ go build -o ../bin/opsxdb-mcp ./cmd/opsxdb-mcp
4. **MCP 状态**:设置 → AI/MCP(开关+白名单);有活跃外部会话时标题旁小点「MCP」,悬停摘要,点开最近调用可断开;无活跃不显示。 4. **MCP 状态**:设置 → AI/MCP(开关+白名单);有活跃外部会话时标题旁小点「MCP」,悬停摘要,点开最近调用可断开;无活跃不显示。
落地次序:MCP 开关+白名单 → Ask|Agent Seg → 时间线与插入 → MCP 状态点。 落地次序:MCP 开关+白名单 → Ask|Agent Seg → 时间线与插入 → MCP 状态点。
### 真库验收 2026-10-15
MySQL 3306 demo + Redis 6379:`go run ./cmd/opsxdb-mcp-accept/` 全过。实践文档:`~/Downloads/AI-Agent接入GUI工具开发实践.md`。
+104
View File
@@ -0,0 +1,104 @@
package main
import (
"context"
"encoding/json"
"fmt"
"os"
"strings"
"opsxdb/server/internal/aitools"
)
func must(ok bool, msg string) {
if !ok {
fmt.Println("FAIL:", msg)
os.Exit(1)
}
fmt.Println("OK:", msg)
}
func dump(v any) string {
b, _ := json.MarshalIndent(v, "", " ")
return string(b)
}
func main() {
rt, err := aitools.DefaultRuntime()
if err != nil {
panic(err)
}
mysqlID := "accept-mysql-demo"
redisID := "accept-redis-demo"
ctx := context.Background()
out, err := rt.Call(ctx, aitools.ToolListConnections, map[string]any{})
s := dump(out)
must(err == nil && strings.Contains(s, mysqlID) && !strings.Contains(s, `"password"`), "list_connections")
out, err = rt.Call(ctx, aitools.ToolListDatabases, map[string]any{"connection_id": mysqlID})
s = dump(out)
must(err == nil && strings.Contains(s, "demo"), "list_databases: "+fmt.Sprint(err)+" "+s)
out, err = rt.Call(ctx, aitools.ToolListTables, map[string]any{"connection_id": mysqlID, "database": "demo"})
s = dump(out)
must(err == nil, "list_tables: "+fmt.Sprint(err)+" "+s)
fmt.Println(s[:min(600, len(s))])
var tables []string
if m, ok := out.(map[string]any); ok {
if arr, ok := m["tables"].([]any); ok {
for _, x := range arr {
tables = append(tables, fmt.Sprint(x))
}
}
}
if len(tables) > 0 {
t := tables[0]
_, err = rt.Call(ctx, aitools.ToolDescribeTable, map[string]any{"connection_id": mysqlID, "database": "demo", "table": t})
must(err == nil, "describe_table "+t+": "+fmt.Sprint(err))
out, err = rt.Call(ctx, aitools.ToolGetSchemaContext, map[string]any{"connection_id": mysqlID, "database": "demo"})
must(err == nil && len(dump(out)) > 20, "get_schema_context")
_ = out
}
out, err = rt.Call(ctx, aitools.ToolExecuteQuery, map[string]any{
"connection_id": mysqlID,
"database": "demo",
"sql": "SELECT 1 AS n, DATABASE() AS db",
})
s = dump(out)
must(err == nil && strings.Contains(s, "demo"), "execute_query SELECT: "+fmt.Sprint(err)+" "+s)
fmt.Println(s[:min(500, len(s))])
_, err = rt.Call(ctx, aitools.ToolExecuteQuery, map[string]any{
"connection_id": mysqlID,
"sql": "DROP TABLE IF EXISTS __opsxdb_should_deny__",
})
must(err != nil, "DROP must fail, got nil")
out, err = rt.Call(ctx, aitools.ToolExecuteRedisCommand, map[string]any{
"connection_id": redisID,
"command": "PING",
})
s = dump(out)
must(err == nil && strings.Contains(strings.ToUpper(s), "PONG"), "redis PING: "+fmt.Sprint(err)+" "+s)
_, err = rt.Call(ctx, aitools.ToolExecuteRedisCommand, map[string]any{
"connection_id": redisID,
"command": "FLUSHDB",
})
must(err != nil, "FLUSHDB must fail")
_, err = rt.Call(ctx, aitools.ToolListTables, map[string]any{"connection_id": "not-allowed"})
must(err != nil, "whitelist reject")
fmt.Println("ALL ACCEPTANCE PASSED")
}
func min(a, b int) int {
if a < b {
return a
}
return b
}