commit
16a45de488
13 changed files with 1913 additions and 0 deletions
@ -0,0 +1,4 @@ |
|||||
|
# 忽略 .idea 目录 |
||||
|
.idea/ |
||||
|
/go.sum |
||||
|
/dist/ |
||||
@ -0,0 +1,16 @@ |
|||||
|
project_name: go-markdown |
||||
|
builds: |
||||
|
- env: |
||||
|
- CGO_ENABLED=0 |
||||
|
goos: |
||||
|
- linux |
||||
|
# - darwin |
||||
|
- windows |
||||
|
goarch: |
||||
|
- "amd64" |
||||
|
# - "arm64" |
||||
|
# - "386" |
||||
|
release: |
||||
|
github: |
||||
|
owner: https://github.com/vivimanZhang/go-markdown.git |
||||
|
name: VivimanZhang |
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,102 @@ |
|||||
|
|
||||
|
--- |
||||
|
|
||||
|
# 达梦数据库 MCP 服务 |
||||
|
|
||||
|
## 项目简介 |
||||
|
本项目是一个基于达梦数据库的 MCP(Microservice Communication Protocol)服务,提供了以下功能: |
||||
|
- **列出数据库中的表**:通过 `list_tables` 工具列出指定数据库中的所有表。 |
||||
|
- **执行只读 SQL 查询**:通过 `execute_query` 工具执行只读 SQL 查询(仅支持 `SELECT`、`SHOW`、`DESCRIBE` 和 `EXPLAIN` 语句)。 |
||||
|
- **显示表结构**:通过 `describe_table` 工具显示指定表的结构。 |
||||
|
## 使用方法 |
||||
|
|
||||
|
windows 下可以直接下载 dm-mcp-server 二进制文件,并添加到 PATH 中。 |
||||
|
|
||||
|
### 使用 go install 安装 |
||||
|
```bash |
||||
|
go install github.com/wangzhaobo168/dm-mcp-server |
||||
|
``` |
||||
|
### 达梦MCP Server 配置示例: |
||||
|
``` |
||||
|
{ |
||||
|
"mcpServers": { |
||||
|
"db-mcp-server": { |
||||
|
"command": "dm-mcp-server", |
||||
|
"args": [ |
||||
|
], |
||||
|
"env": { |
||||
|
"DM_PORT": "端口号,默认5236", |
||||
|
"DM_HOST": "主机地址", |
||||
|
"DM_USERNAME": "账号", |
||||
|
"DM_PASSWORD": "密码", |
||||
|
"DM_SCHEMA": "模式名" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
## 检查 dm-mcp-server 版本: |
||||
|
|
||||
|
```bash |
||||
|
dm-mcp-server --version |
||||
|
``` |
||||
|
|
||||
|
## 快速开始 |
||||
|
|
||||
|
### 1. 环境要求 |
||||
|
- **Go 版本**:1.16 或更高版本。 |
||||
|
- **达梦数据库**:已安装并配置好达梦数据库。 |
||||
|
- **依赖库**:确保安装了 `github.com/mark3labs/mcp-go` 库。 |
||||
|
|
||||
|
### 2. 安装依赖 |
||||
|
在项目根目录下运行以下命令初始化 Go Modules 并下载依赖: |
||||
|
|
||||
|
```bash |
||||
|
go mod init dm-mcp-server |
||||
|
go mod tidy |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
### 3. 配置数据库连接 |
||||
|
在运行服务之前,需要配置达梦数据库的连接信息。可以通过以下方式设置: |
||||
|
- **命令行参数**:启动服务时通过命令行参数指定。 |
||||
|
- **环境变量**:通过环境变量设置。 |
||||
|
|
||||
|
#### 命令行参数 |
||||
|
```bash |
||||
|
go run main.go \ |
||||
|
-username <用户名> \ |
||||
|
-password <密码> \ |
||||
|
-host <主机地址> \ |
||||
|
-port <端口> \ |
||||
|
-schema <模式名> |
||||
|
``` |
||||
|
|
||||
|
## 项目结构 |
||||
|
``` |
||||
|
dm-mcp-server/ |
||||
|
├── main.go # 主程序入口 |
||||
|
├── tools/ # 工具实现 |
||||
|
│ ├── list_tables.go # 列出表工具 |
||||
|
│ ├── execute_query.go # 执行查询工具 |
||||
|
│ └── describe_table.go # 显示表结构工具 |
||||
|
├── utils/ # 工具函数 |
||||
|
│ ├── config.go # 配置管理 |
||||
|
│ └── db.go # 数据库连接 |
||||
|
├── go.mod # Go Modules 文件 |
||||
|
└── README.md # 项目文档 |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
## 贡献指南 |
||||
|
欢迎提交 Issue 和 Pull Request 来改进本项目。请确保代码风格一致,并通过测试。 |
||||
|
|
||||
|
## 许可证 |
||||
|
本项目采用 [MIT 许可证](LICENSE)。 |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### 补充说明 |
||||
|
- 如果 `github.com/mark3labs/mcp-go` 库不可用,请替换为其他兼容的 MCP 实现。 |
||||
|
- 如果需要支持更多数据库操作,可以在 `tools` 包中添加新的工具。 |
||||
|
|
||||
|
--- |
||||
@ -0,0 +1 @@ |
|||||
|
rd /s /q dist && goreleaser release --snapshot |
||||
@ -0,0 +1 @@ |
|||||
|
rm -rf dist ; goreleaser release --snapshot |
||||
@ -0,0 +1,16 @@ |
|||||
|
module github.com/wangzhaobo168/dm-mcp-server |
||||
|
|
||||
|
go 1.23.4 |
||||
|
|
||||
|
require ( |
||||
|
github.com/mark3labs/mcp-go v0.23.1 |
||||
|
github.com/wangzhaobo168/dm v0.0.0-20250425092535-276eae067273 |
||||
|
) |
||||
|
|
||||
|
require ( |
||||
|
github.com/golang/snappy v1.0.0 // indirect |
||||
|
github.com/google/uuid v1.6.0 // indirect |
||||
|
github.com/spf13/cast v1.7.1 // indirect |
||||
|
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect |
||||
|
golang.org/x/text v0.24.0 // indirect |
||||
|
) |
||||
@ -0,0 +1,90 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"flag" |
||||
|
"fmt" |
||||
|
"os" |
||||
|
|
||||
|
"github.com/wangzhaobo168/dm-mcp-server/tools" |
||||
|
"github.com/wangzhaobo168/dm-mcp-server/utils" |
||||
|
|
||||
|
"github.com/mark3labs/mcp-go/server" |
||||
|
_ "github.com/wangzhaobo168/dm" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
Version = utils.Version |
||||
|
) |
||||
|
|
||||
|
// 添加工具
|
||||
|
func addTools(s *server.MCPServer) { |
||||
|
// 添加 list_tables 工具
|
||||
|
s.AddTool(tools.ListTablesTool, tools.ListTablesToolHandelFunc) |
||||
|
// 添加 execute_query 工具
|
||||
|
s.AddTool(tools.ExecuteQueryTool, tools.ExecuteQueryToolHandelFunc) |
||||
|
// 添加 describe_table 工具
|
||||
|
s.AddTool(tools.DescribeTableTool, tools.DescribeTableToolHandelFunc) |
||||
|
} |
||||
|
|
||||
|
// 创建一个新的MCP服务器
|
||||
|
func newMCPServer() *server.MCPServer { |
||||
|
return server.NewMCPServer( |
||||
|
"达梦数据库MCP Server", |
||||
|
Version, |
||||
|
server.WithResourceCapabilities(true, true), |
||||
|
server.WithLogging(), |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
func run() error { |
||||
|
s := newMCPServer() |
||||
|
addTools(s) |
||||
|
// 启动服务器
|
||||
|
if err := server.ServeStdio(s); err != nil { |
||||
|
return fmt.Errorf("服务器错误: %v", err) |
||||
|
} |
||||
|
return nil |
||||
|
} |
||||
|
func main() { |
||||
|
username := flag.String("username", "", "用户名") |
||||
|
password := flag.String("password", "", "密码") |
||||
|
host := flag.String("host", "", "主机") |
||||
|
port := flag.String("port", "", "端口") |
||||
|
schema := flag.String("schema", "", "模式名") |
||||
|
showVersion := flag.Bool("version", false, "显示版本号") |
||||
|
flag.Parse() |
||||
|
if *username != "" { |
||||
|
utils.SetUserName(*username) |
||||
|
} else { |
||||
|
*username = utils.GetUserName() |
||||
|
} |
||||
|
|
||||
|
if *password != "" { |
||||
|
utils.SetPassword(*password) |
||||
|
} else { |
||||
|
*password = utils.GetPassWord() |
||||
|
} |
||||
|
if *host != "" { |
||||
|
utils.SetHost(*host) |
||||
|
} else { |
||||
|
*host = utils.GetHost() |
||||
|
} |
||||
|
if *port != "" { |
||||
|
utils.SetPort(*port) |
||||
|
} else { |
||||
|
*port = utils.GetPort() |
||||
|
} |
||||
|
if *schema != "" { |
||||
|
utils.SetSchema(*schema) |
||||
|
} else { |
||||
|
*schema = utils.GetSchema() |
||||
|
} |
||||
|
if *showVersion { |
||||
|
fmt.Printf("Dm MCP Server\n") |
||||
|
fmt.Printf("Version: %s\n", Version) |
||||
|
os.Exit(0) |
||||
|
} |
||||
|
if err := run(); err != nil { |
||||
|
panic(err) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
package tools |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/mark3labs/mcp-go/mcp" |
||||
|
"github.com/wangzhaobo168/dm-mcp-server/utils" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
DescribeTableToolName = "describe_table" |
||||
|
) |
||||
|
|
||||
|
var DescribeTableTool = mcp.NewTool(DescribeTableToolName, |
||||
|
mcp.WithDescription("显示表的结构"), |
||||
|
mcp.WithString( |
||||
|
"table", |
||||
|
mcp.Description("表名称"), |
||||
|
mcp.Required(), |
||||
|
), |
||||
|
mcp.WithString( |
||||
|
"database", |
||||
|
mcp.Description("数据库名称(如果未指定则使用DM_SCHEMA)"), |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
func DescribeTableToolHandelFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
||||
|
dbName, ok := request.Params.Arguments["schema"].(string) |
||||
|
if !ok { |
||||
|
dbName = utils.GetSchema() |
||||
|
} |
||||
|
tableName, ok := request.Params.Arguments["table"].(string) |
||||
|
if !ok { |
||||
|
return mcp.NewToolResultError("表名称不能为空"), nil |
||||
|
} |
||||
|
db, err := utils.ConnectDMDatabase() |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("连接达梦数据库失败: %v", err)), nil |
||||
|
} |
||||
|
defer db.Close() |
||||
|
|
||||
|
// 使用达梦数据库系统表查询表结构
|
||||
|
query := fmt.Sprintf(` |
||||
|
SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, NULLABLE |
||||
|
FROM ALL_TAB_COLUMNS |
||||
|
WHERE OWNER = '%s' AND TABLE_NAME = '%s' |
||||
|
ORDER BY COLUMN_ID`, dbName, tableName) |
||||
|
|
||||
|
rows, err := db.Query(query) |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("获取表结构失败: %v", err)), nil |
||||
|
} |
||||
|
defer rows.Close() |
||||
|
|
||||
|
var schema []string |
||||
|
for rows.Next() { |
||||
|
var columnName, dataType, nullable string |
||||
|
var dataLength int |
||||
|
if err := rows.Scan(&columnName, &dataType, &dataLength, &nullable); err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("扫描列信息失败: %v", err)), nil |
||||
|
} |
||||
|
columnDesc := fmt.Sprintf("%s %s(%d) %s", |
||||
|
columnName, |
||||
|
dataType, |
||||
|
dataLength, |
||||
|
nullable) |
||||
|
schema = append(schema, columnDesc) |
||||
|
} |
||||
|
|
||||
|
return mcp.NewToolResultText(strings.Join(schema, "\n")), nil |
||||
|
} |
||||
@ -0,0 +1,88 @@ |
|||||
|
package tools |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/mark3labs/mcp-go/mcp" |
||||
|
"github.com/wangzhaobo168/dm-mcp-server/utils" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
ExecuteQueryToolName = "execute_query" |
||||
|
) |
||||
|
|
||||
|
var ExecuteQueryTool = mcp.NewTool(ExecuteQueryToolName, |
||||
|
mcp.WithDescription("执行只读SQL查询"), |
||||
|
mcp.WithString( |
||||
|
"query", |
||||
|
mcp.Description("SQL查询(仅允许SELECT、SHOW、DESCRIBE和EXPLAIN语句)"), |
||||
|
mcp.Required(), |
||||
|
), |
||||
|
mcp.WithString( |
||||
|
"database", |
||||
|
mcp.Description("数据库名称(如果未指定则使用配置的DM_SCHEMA)"), |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
func ExecuteQueryToolHandelFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
||||
|
query := request.Params.Arguments["query"].(string) |
||||
|
dbName, ok := request.Params.Arguments["schema"].(string) |
||||
|
if !ok { |
||||
|
dbName = utils.GetSchema() |
||||
|
} |
||||
|
// 验证查询语句是否为只读操作
|
||||
|
queryUpper := strings.ToUpper(strings.TrimSpace(query)) |
||||
|
if !strings.HasPrefix(queryUpper, "SELECT") && |
||||
|
!strings.HasPrefix(queryUpper, "SHOW") && |
||||
|
!strings.HasPrefix(queryUpper, "DESCRIBE") && |
||||
|
!strings.HasPrefix(queryUpper, "EXPLAIN") { |
||||
|
return mcp.NewToolResultError("仅允许SELECT、SHOW、DESCRIBE和EXPLAIN语句"), nil |
||||
|
} |
||||
|
|
||||
|
db, err := utils.ConnectDMDatabase() |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("连接达梦数据库失败: %v", err)), nil |
||||
|
} |
||||
|
defer db.Close() |
||||
|
|
||||
|
// 设置当前schema
|
||||
|
if _, err := db.Exec(fmt.Sprintf("SET SCHEMA %s", dbName)); err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("设置schema失败: %v", err)), nil |
||||
|
} |
||||
|
|
||||
|
rows, err := db.Query(query) |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("执行查询失败: %v", err)), nil |
||||
|
} |
||||
|
defer rows.Close() |
||||
|
|
||||
|
columns, err := rows.Columns() |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("获取列失败: %v", err)), nil |
||||
|
} |
||||
|
|
||||
|
// 构建表头
|
||||
|
var results []string |
||||
|
results = append(results, strings.Join(columns, "\t")) |
||||
|
|
||||
|
// 获取数据行
|
||||
|
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 mcp.NewToolResultError(fmt.Sprintf("扫描行失败: %v", err)), nil |
||||
|
} |
||||
|
var rowValues []string |
||||
|
for _, v := range values { |
||||
|
rowValues = append(rowValues, fmt.Sprintf("%v", v)) |
||||
|
} |
||||
|
results = append(results, strings.Join(rowValues, "\t")) |
||||
|
} |
||||
|
|
||||
|
return mcp.NewToolResultText(strings.Join(results, "\n")), nil |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
package tools |
||||
|
|
||||
|
import ( |
||||
|
"context" |
||||
|
"fmt" |
||||
|
"strings" |
||||
|
|
||||
|
"github.com/mark3labs/mcp-go/mcp" |
||||
|
"github.com/wangzhaobo168/dm-mcp-server/utils" |
||||
|
) |
||||
|
|
||||
|
const ( |
||||
|
ListTablesToolName = "list_tables" |
||||
|
) |
||||
|
|
||||
|
var ListTablesTool = mcp.NewTool(ListTablesToolName, |
||||
|
mcp.WithDescription("列出指定数据库中的所有表"), |
||||
|
mcp.WithString( |
||||
|
"database", |
||||
|
mcp.Description("数据库名称(可选)"), |
||||
|
), |
||||
|
) |
||||
|
|
||||
|
func ListTablesToolHandelFunc(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { |
||||
|
dbName, ok := request.Params.Arguments["schema"].(string) |
||||
|
if !ok { |
||||
|
dbName = utils.GetSchema() |
||||
|
} |
||||
|
db, err := utils.ConnectDMDatabase() |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("连接达梦数据库失败: %v", err)), nil |
||||
|
} |
||||
|
defer db.Close() |
||||
|
|
||||
|
// 使用达梦数据库系统表查询指定schema下的所有表
|
||||
|
query := fmt.Sprintf("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER = '%s'", dbName) |
||||
|
rows, err := db.Query(query) |
||||
|
if err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("获取表失败: %v", err)), nil |
||||
|
} |
||||
|
defer rows.Close() |
||||
|
|
||||
|
var tables []string |
||||
|
for rows.Next() { |
||||
|
var tableName string |
||||
|
if err := rows.Scan(&tableName); err != nil { |
||||
|
return mcp.NewToolResultError(fmt.Sprintf("扫描表名称失败: %v", err)), nil |
||||
|
} |
||||
|
tables = append(tables, tableName) |
||||
|
} |
||||
|
|
||||
|
return mcp.NewToolResultText(strings.Join(tables, ", ")), nil |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
package utils |
||||
|
|
||||
|
import ( |
||||
|
"os" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
Version = "1.0.0" |
||||
|
DefaultPort = "5236" |
||||
|
userName string |
||||
|
password string |
||||
|
host string |
||||
|
port string |
||||
|
schema string |
||||
|
) |
||||
|
|
||||
|
func GetUserName() string { |
||||
|
if userName != "" { |
||||
|
return userName |
||||
|
} |
||||
|
return os.Getenv("DM_USERNAME") |
||||
|
} |
||||
|
|
||||
|
func GetPassWord() string { |
||||
|
if password != "" { |
||||
|
return password |
||||
|
} |
||||
|
return os.Getenv("DM_PASSWORD") |
||||
|
} |
||||
|
|
||||
|
func GetHost() string { |
||||
|
if host != "" { |
||||
|
return host |
||||
|
} |
||||
|
return os.Getenv("DM_HOST") |
||||
|
} |
||||
|
|
||||
|
func GetSchema() string { |
||||
|
if schema != "" { |
||||
|
return schema |
||||
|
} |
||||
|
return os.Getenv("DM_SCHEMA") |
||||
|
} |
||||
|
|
||||
|
func GetPort() string { |
||||
|
if port != "" { |
||||
|
return port |
||||
|
} |
||||
|
if port := os.Getenv("DM_PORT"); port != "" { |
||||
|
return port |
||||
|
} |
||||
|
return DefaultPort |
||||
|
} |
||||
|
|
||||
|
func SetHost(h string) { |
||||
|
host = h |
||||
|
} |
||||
|
|
||||
|
func SetPort(p string) { |
||||
|
port = p |
||||
|
} |
||||
|
|
||||
|
func SetUserName(name string) { |
||||
|
userName = name |
||||
|
} |
||||
|
|
||||
|
func SetPassword(pwd string) { |
||||
|
password = pwd |
||||
|
} |
||||
|
|
||||
|
func SetSchema(s string) { |
||||
|
schema = s |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
// utils/db.go
|
||||
|
package utils |
||||
|
|
||||
|
import ( |
||||
|
"database/sql" |
||||
|
"fmt" |
||||
|
_ "github.com/wangzhaobo168/dm" // 引入达梦数据库驱动
|
||||
|
) |
||||
|
|
||||
|
// ConnectDMDatabase 连接达梦数据库
|
||||
|
func ConnectDMDatabase() (*sql.DB, error) { |
||||
|
username := GetUserName() |
||||
|
password := GetPassWord() |
||||
|
host := GetHost() |
||||
|
port := GetPort() |
||||
|
dataSourceName := fmt.Sprintf("dm://%s:%s@%s:%s", username, password, host, port) |
||||
|
db, err := sql.Open("dm", dataSourceName) |
||||
|
if err != nil { |
||||
|
return nil, fmt.Errorf("打开达梦数据库失败: %w", err) |
||||
|
} |
||||
|
|
||||
|
if err := db.Ping(); err != nil { |
||||
|
return nil, fmt.Errorf("无法连接达梦数据库: %w", err) |
||||
|
} |
||||
|
return db, nil |
||||
|
} |
||||
Loading…
Reference in new issue