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