首页 > 人工智能 > 自然语言 > 如何开发一个简单的数据库mcp server
#### 📍 `loadConfig()` 函数
// 从环境变量加载配置
func loadConfig() *Config {
config := &Config{
Host: getEnv("MYSQL_HOST", "127.0.0.1"),
Port: getEnv("MYSQL_PORT", "3306"),
User: getEnv("MYSQL_USER", "root"),
Password: getEnv("MYSQL_PASS", ""),
Database: getEnv("MYSQL_DB", "mysql"),
AllowInsertOperation: getEnvBool("ALLOW_INSERT_OPERATION", false),
AllowUpdateOperation: getEnvBool("ALLOW_UPDATE_OPERATION", false),
AllowDeleteOperation: getEnvBool("ALLOW_DELETE_OPERATION", false),
}
return config
}
#### 📍 `getEnv()` 和 `getEnvBool()` 辅助函数
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
func getEnvBool(key string, defaultValue bool) bool {
if value := os.Getenv(key); value != "" {
result, err := strconv.ParseBool(value)
if err != nil {
return defaultValue
}
return result
}
return defaultValue
}
#### 📍 `createMySQLConnection()` 函数
// 创建 MySQL 连接
func createMySQLConnection(config *Config) (*sql.DB, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.User, config.Password, config.Host, config.Port, config.Database)
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, fmt.Errorf("连接数据库失败: %v", err)
}
// 测试连接
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("数据库连接测试失败: %v", err)
}
log.Printf("成功连接到 MySQL 数据库: %s:%s/%s", config.Host, config.Port, config.Database)
return db, nil
}
#### 📍 `isSafeQuery()` 函数
// 检查 SQL 语句是否安全
func isSafeQuery(query string, config *Config) error {
query = strings.TrimSpace(strings.ToUpper(query))
// 检查是否包含危险的操作
dangerousOperations := []string{
"INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER",
"TRUNCATE", "GRANT", "REVOKE", "SET", "LOAD", "OUTFILE",
}
for _, op := range dangerousOperations {
if strings.Contains(query, op) {
switch op {
case "INSERT":
if !config.AllowInsertOperation {
return fmt.Errorf("INSERT 操作被禁用")
}
case "UPDATE":
if !config.AllowUpdateOperation {
return fmt.Errorf("UPDATE 操作被禁用")
}
case "DELETE":
if !config.AllowDeleteOperation {
return fmt.Errorf("DELETE 操作被禁用")
}
default:
return fmt.Errorf("危险操作 '%s' 被禁用", op)
}
}
}
// 确保是 SELECT 语句或被允许的操作
if !strings.HasPrefix(query, "SELECT") &&
!strings.HasPrefix(query, "SHOW") &&
!strings.HasPrefix(query, "DESCRIBE") &&
!strings.HasPrefix(query, "DESC") &&
!strings.HasPrefix(query, "EXPLAIN") {
return fmt.Errorf("只允许执行 SELECT、SHOW、DESCRIBE、EXPLAIN 查询语句")
}
return nil
}
#### 📍 `isValidTableName()` 函数
// 验证表名是否安全
func isValidTableName(tableName string) bool {
// 只允许字母、数字、下划线
matched, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", tableName)
return matched
}
#### 📍 `executeQuery()` 函数
// 执行 MySQL 查询
func executeQuery(ctx context.Context, db *sql.DB, query string, config *Config) ([]map[string]interface{}, error) {
if err := isSafeQuery(query, config); err != nil {
return nil, err
}
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("查询执行失败: %v", err)
}
defer rows.Close()
columns, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("获取列信息失败: %v", err)
}
var results []map[string]interface{}
for rows.Next() {
values := make([]interface{}, len(columns))
valuePtrs := make([]interface{}, len(columns))
for i := range columns {
valuePtrs[i] = &values[i]
}
if err := rows.Scan(valuePtrs...); err != nil {
return nil, fmt.Errorf("扫描行数据失败: %v", err)
}
row := make(map[string]interface{})
for i, col := range columns {
val := values[i]
// 处理 []byte 类型的数据
if b, ok := val.([]byte); ok {
row[col] = string(b)
} else {
row[col] = val
}
}
results = append(results, row)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历结果失败: %v", err)
}
return results, nil
}
#### 📍 `listTables()` 函数
// 获取数据库表列表
func listTables(ctx context.Context, db *sql.DB) ([]string, error) {
query := "SHOW TABLES"
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("获取表列表失败: %v", err)
}
defer rows.Close()
var tables []string
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err != nil {
return nil, fmt.Errorf("扫描表名失败: %v", err)
}
tables = append(tables, tableName)
}
return tables, nil
}
#### 📍 `describeTable()` 函数
// 获取表结构信息
func describeTable(ctx context.Context, db *sql.DB, tableName string, config *Config) ([]map[string]interface{}, error) {
// 验证表名以防止 SQL 注入
if !isValidTableName(tableName) {
return nil, fmt.Errorf("无效的表名: %s", tableName)
}
query := fmt.Sprintf("DESCRIBE `%s`", tableName)
return executeQuery(ctx, db, query, config)
}
##### `mysql_query` 工具
// 定义 mysql_query 工具
queryTool := mcp.NewTool("mysql_query",
mcp.WithDescription("执行MySQL查询语句并返回结果(仅支持SELECT等查询语句)"),
mcp.WithString("sql",
mcp.Required(),
mcp.Description("要执行的SQL查询语句"),
),
)
// 添加 mysql_query 工具处理器
mcpServer.AddTool(queryTool, func(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
sqlQuery, ok := arguments["sql"].(string)
if !ok {
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": "无效的SQL查询参数",
},
},
IsError: true,
}, nil
}
log.Printf("执行查询: %s", sqlQuery)
results, err := executeQuery(context.Background(), db, sqlQuery, config)
if err != nil {
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": fmt.Sprintf("查询执行失败: %v", err),
},
},
IsError: true,
}, nil
}
// 格式化输出结果
output := fmt.Sprintf("查询成功,返回 %d 行数据:\n", len(results))
if len(results) == 0 {
output += "没有找到匹配的数据。"
} else {
// 转换为易读的格式
jsonData, _ := json.MarshalIndent(results, "", " ")
output += string(jsonData)
}
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": output,
},
},
}, nil
})
##### `list_tables` 工具
// 定义 list_tables 工具
listTablesTool := mcp.NewTool("list_tables",
mcp.WithDescription("获取数据库中所有表的列表"),
)
// 添加 list_tables 工具处理器
mcpServer.AddTool(listTablesTool, func(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
log.Printf("获取表列表")
tables, err := listTables(context.Background(), db)
if err != nil {
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": fmt.Sprintf("获取表列表失败: %v", err),
},
},
IsError: true,
}, nil
}
output := fmt.Sprintf("数据库共有 %d 个表:\n", len(tables))
for i, table := range tables {
output += fmt.Sprintf("%d. %s\n", i+1, table)
}
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": output,
},
},
}, nil
})
##### `describe_table` 工具
// 添加 describe_table 工具处理器
mcpServer.AddTool(describeTableTool, func(arguments map[string]interface{}) (*mcp.CallToolResult, error) {
tableName, ok := arguments["table_name"].(string)
if !ok {
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": "无效的表名参数",
},
},
IsError: true,
}, nil
}
log.Printf("获取表结构: %s", tableName)
columns, err := describeTable(context.Background(), db, tableName, config)
if err != nil {
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": fmt.Sprintf("获取表结构失败: %v", err),
},
},
IsError: true,
}, nil
}
output := fmt.Sprintf("表 '%s' 的结构信息:\n", tableName)
jsonData, _ := json.MarshalIndent(columns, "", " ")
output += string(jsonData)
return &mcp.CallToolResult{
Content: []interface{}{
map[string]interface{}{
"type": "text",
"text": output,
},
},
}, nil
})
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/26366838/viewspace-3095527/ ,如需转载,请注明出处,否则将追究法律责任。