feat: 增加 mcp Tools 自动测试工具
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||||
|
"github.com/flipped-aurora/gin-vue-admin/server/mcp/client"
|
||||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
@@ -32,3 +35,99 @@ func (a *AutoCodeTemplateApi) MCP(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
response.OkWithMessage("创建成功,MCP Tool路径:"+toolFilePath, c)
|
response.OkWithMessage("创建成功,MCP Tool路径:"+toolFilePath, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags mcp
|
||||||
|
// @Summary 自动McpTool
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||||
|
// @Router /autoCode/mcpList [post]
|
||||||
|
func (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {
|
||||||
|
|
||||||
|
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||||
|
|
||||||
|
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||||
|
defer testClient.Close()
|
||||||
|
toolsRequest := mcp.ListToolsRequest{}
|
||||||
|
|
||||||
|
list, err := testClient.ListTools(c.Request.Context(), toolsRequest)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
global.GVA_LOG.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(list, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags mcp
|
||||||
|
// @Summary 测试McpTool
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body object true "调用MCP Tool的参数"
|
||||||
|
// @Success 200 {object} response.Response "{"success":true,"data":{},"msg":"测试成功"}"
|
||||||
|
// @Router /autoCode/mcpTest [post]
|
||||||
|
func (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {
|
||||||
|
// 定义接口请求结构
|
||||||
|
var testRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"` // 工具名称
|
||||||
|
Arguments map[string]interface{} `json:"arguments" binding:"required"` // 工具参数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定JSON请求体
|
||||||
|
if err := c.ShouldBindJSON(&testRequest); err != nil {
|
||||||
|
response.FailWithMessage("参数解析失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建MCP客户端
|
||||||
|
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||||
|
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("创建MCP客户端失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer testClient.Close()
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
// 初始化MCP连接
|
||||||
|
initRequest := mcp.InitializeRequest{}
|
||||||
|
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
|
||||||
|
initRequest.Params.ClientInfo = mcp.Implementation{
|
||||||
|
Name: "testClient",
|
||||||
|
Version: "v1.0.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = testClient.Initialize(ctx, initRequest)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("初始化MCP连接失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建工具调用请求
|
||||||
|
request := mcp.CallToolRequest{}
|
||||||
|
request.Params.Name = testRequest.Name
|
||||||
|
request.Params.Arguments = testRequest.Arguments
|
||||||
|
|
||||||
|
// 调用工具
|
||||||
|
result, err := testClient.CallTool(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("工具调用失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理响应结果
|
||||||
|
if len(result.Content) == 0 {
|
||||||
|
response.FailWithMessage("工具未返回任何内容", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
response.OkWithData(result.Content, c)
|
||||||
|
}
|
||||||
|
@@ -2,6 +2,8 @@ package mcpTool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"github.com/mark3labs/mcp-go/mcp"
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@@ -16,13 +18,26 @@ type CurrentTime struct {
|
|||||||
// 获取当前系统时间
|
// 获取当前系统时间
|
||||||
func (t *CurrentTime) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
func (t *CurrentTime) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
// 获取当前系统时间
|
// 获取当前系统时间
|
||||||
currentTime := time.Now().Format("2006-01-02 15:04:05")
|
|
||||||
|
timezone, ok := request.Params.Arguments["timezone"].(string)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("参数错误:timezone 必须是字符串类型")
|
||||||
|
}
|
||||||
|
// 根据timezone参数加载对应时区
|
||||||
|
loc, err := loadTimeZone(timezone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前时间并转换为指定时区
|
||||||
|
currentTime := time.Now().In(loc).Format("2006-01-02 15:04:05")
|
||||||
//返回
|
//返回
|
||||||
return &mcp.CallToolResult{
|
return &mcp.CallToolResult{
|
||||||
Content: []mcp.Content{
|
Content: []mcp.Content{
|
||||||
mcp.TextContent{
|
mcp.TextContent{
|
||||||
Type: "text",
|
Type: "text",
|
||||||
Text: currentTime,
|
Text: fmt.Sprintf("%s 时区的当前时间是:%s", timezone, currentTime),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
@@ -37,3 +52,30 @@ func (t *CurrentTime) New() mcp.Tool {
|
|||||||
mcp.Enum("UTC", "CST", "PST", "EST", "GMT", "CET", "JST", "MST", "IST", "AST", "HST"),
|
mcp.Enum("UTC", "CST", "PST", "EST", "GMT", "CET", "JST", "MST", "IST", "AST", "HST"),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将简写时区转换为IANA标准时区
|
||||||
|
func loadTimeZone(timezone string) (*time.Location, error) {
|
||||||
|
// 时区映射表
|
||||||
|
timezoneMap := map[string]string{
|
||||||
|
"UTC": "UTC",
|
||||||
|
"CST": "Asia/Shanghai", // 中国标准时间
|
||||||
|
"PST": "America/Los_Angeles",
|
||||||
|
"EST": "America/New_York",
|
||||||
|
"GMT": "GMT",
|
||||||
|
"CET": "Europe/Paris",
|
||||||
|
"JST": "Asia/Tokyo",
|
||||||
|
"MST": "America/Denver",
|
||||||
|
"IST": "Asia/Kolkata",
|
||||||
|
"AST": "Asia/Riyadh", // 阿拉伯标准时间
|
||||||
|
"HST": "Pacific/Honolulu",
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标准时区名称
|
||||||
|
tzName, exists := timezoneMap[timezone]
|
||||||
|
if !exists {
|
||||||
|
return nil, errors.New("不支持的时区: " + timezone)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载时区
|
||||||
|
return time.LoadLocation(tzName)
|
||||||
|
}
|
||||||
|
@@ -31,15 +31,15 @@ func (t *GetNickname) Handle(ctx context.Context, request mcp.CallToolRequest) (
|
|||||||
// 1. 参数验证
|
// 1. 参数验证
|
||||||
username, ok := request.Params.Arguments["username"].(string)
|
username, ok := request.Params.Arguments["username"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("参数错误:username必须是字符串类型")
|
return nil, errors.New("参数错误:username 必须是字符串类型")
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return nil, errors.New("参数错误:username不能为空")
|
return nil, errors.New("参数错误:username 不能为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 记录操作日志
|
// 2. 记录操作日志
|
||||||
global.GVA_LOG.Info("getNickname工具被调用")
|
global.GVA_LOG.Info("getNickname 工具被调用")
|
||||||
|
|
||||||
// 3. 优化查询,只选择需要的字段
|
// 3. 优化查询,只选择需要的字段
|
||||||
var user struct {
|
var user struct {
|
||||||
@@ -54,7 +54,14 @@ func (t *GetNickname) Handle(ctx context.Context, request mcp.CallToolRequest) (
|
|||||||
// 4. 优化错误处理
|
// 4. 优化错误处理
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errors.New("用户不存在")
|
return &mcp.CallToolResult{
|
||||||
|
Content: []mcp.Content{
|
||||||
|
mcp.TextContent{
|
||||||
|
Type: "text",
|
||||||
|
Text: fmt.Sprintf("用户 %s 不存在", username),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
global.GVA_LOG.Error("数据库查询错误")
|
global.GVA_LOG.Error("数据库查询错误")
|
||||||
return nil, errors.New("系统错误,请稍后再试")
|
return nil, errors.New("系统错误,请稍后再试")
|
||||||
|
@@ -21,6 +21,8 @@ func (s *AutoCodeRouter) InitAutoCodeRouter(Router *gin.RouterGroup, RouterPubli
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
autoCodeRouter.POST("mcp", autoCodeTemplateApi.MCP) // 自动创建Mcp Tool模板
|
autoCodeRouter.POST("mcp", autoCodeTemplateApi.MCP) // 自动创建Mcp Tool模板
|
||||||
|
autoCodeRouter.POST("mcpList", autoCodeTemplateApi.MCPList) // 获取MCP ToolList
|
||||||
|
autoCodeRouter.POST("mcpTest", autoCodeTemplateApi.MCPTest) // MCP 工具测试
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
autoCodeRouter.POST("getPackage", autoCodePackageApi.All) // 获取package包
|
autoCodeRouter.POST("getPackage", autoCodePackageApi.All) // 获取package包
|
||||||
|
@@ -118,6 +118,8 @@ func (i *initApi) InitializeData(ctx context.Context) (context.Context, error) {
|
|||||||
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/installPlugin", Description: "安装插件"},
|
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/installPlugin", Description: "安装插件"},
|
||||||
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/pubPlug", Description: "打包插件"},
|
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/pubPlug", Description: "打包插件"},
|
||||||
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcp", Description: "自动生成 MCP Tool 模板"},
|
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcp", Description: "自动生成 MCP Tool 模板"},
|
||||||
|
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcpTest", Description: "MCP Tool 测试"},
|
||||||
|
{ApiGroup: "代码生成器", Method: "POST", Path: "/autoCode/mcpList", Description: "获取 MCP ToolList"},
|
||||||
|
|
||||||
{ApiGroup: "模板配置", Method: "POST", Path: "/autoCode/createPackage", Description: "配置模板"},
|
{ApiGroup: "模板配置", Method: "POST", Path: "/autoCode/createPackage", Description: "配置模板"},
|
||||||
{ApiGroup: "模板配置", Method: "GET", Path: "/autoCode/getTemplates", Description: "获取模板文件"},
|
{ApiGroup: "模板配置", Method: "GET", Path: "/autoCode/getTemplates", Description: "获取模板文件"},
|
||||||
|
@@ -131,6 +131,8 @@ func (i *initCasbin) InitializeData(ctx context.Context) (context.Context, error
|
|||||||
{Ptype: "p", V0: "888", V1: "/autoCode/pubPlug", V2: "POST"},
|
{Ptype: "p", V0: "888", V1: "/autoCode/pubPlug", V2: "POST"},
|
||||||
{Ptype: "p", V0: "888", V1: "/autoCode/addFunc", V2: "POST"},
|
{Ptype: "p", V0: "888", V1: "/autoCode/addFunc", V2: "POST"},
|
||||||
{Ptype: "p", V0: "888", V1: "/autoCode/mcp", V2: "POST"},
|
{Ptype: "p", V0: "888", V1: "/autoCode/mcp", V2: "POST"},
|
||||||
|
{Ptype: "p", V0: "888", V1: "/autoCode/mcpTest", V2: "POST"},
|
||||||
|
{Ptype: "p", V0: "888", V1: "/autoCode/mcpList", V2: "POST"},
|
||||||
|
|
||||||
{Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/findSysDictionaryDetail", V2: "GET"},
|
{Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/findSysDictionaryDetail", V2: "GET"},
|
||||||
{Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/updateSysDictionaryDetail", V2: "PUT"},
|
{Ptype: "p", V0: "888", V1: "/sysDictionaryDetail/updateSysDictionaryDetail", V2: "PUT"},
|
||||||
|
@@ -101,6 +101,7 @@ func (i *initMenu) InitializeData(ctx context.Context) (next context.Context, er
|
|||||||
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "exportTemplate", Name: "exportTemplate", Component: "view/systemTools/exportTemplate/exportTemplate.vue", Sort: 5, Meta: Meta{Title: "导出模板", Icon: "reading"}},
|
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "exportTemplate", Name: "exportTemplate", Component: "view/systemTools/exportTemplate/exportTemplate.vue", Sort: 5, Meta: Meta{Title: "导出模板", Icon: "reading"}},
|
||||||
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "picture", Name: "picture", Component: "view/systemTools/autoCode/picture.vue", Sort: 6, Meta: Meta{Title: "AI页面绘制", Icon: "picture-filled"}},
|
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "picture", Name: "picture", Component: "view/systemTools/autoCode/picture.vue", Sort: 6, Meta: Meta{Title: "AI页面绘制", Icon: "picture-filled"}},
|
||||||
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "mcpTool", Name: "mcpTool", Component: "view/systemTools/autoCode/mcp.vue", Sort: 7, Meta: Meta{Title: "Mcp Tools模板", Icon: "magnet"}},
|
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "mcpTool", Name: "mcpTool", Component: "view/systemTools/autoCode/mcp.vue", Sort: 7, Meta: Meta{Title: "Mcp Tools模板", Icon: "magnet"}},
|
||||||
|
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["systemTools"], Path: "mcpTest", Name: "mcpTest", Component: "view/systemTools/autoCode/mcpTest.vue", Sort: 7, Meta: Meta{Title: "Mcp Tools测试", Icon: "partly-cloudy"}},
|
||||||
|
|
||||||
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "https://plugin.gin-vue-admin.com/", Name: "https://plugin.gin-vue-admin.com/", Component: "https://plugin.gin-vue-admin.com/", Sort: 0, Meta: Meta{Title: "插件市场", Icon: "shop"}},
|
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "https://plugin.gin-vue-admin.com/", Name: "https://plugin.gin-vue-admin.com/", Component: "https://plugin.gin-vue-admin.com/", Sort: 0, Meta: Meta{Title: "插件市场", Icon: "shop"}},
|
||||||
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "installPlugin", Name: "installPlugin", Component: "view/systemTools/installPlugin/index.vue", Sort: 1, Meta: Meta{Title: "插件安装", Icon: "box"}},
|
{MenuLevel: 1, Hidden: false, ParentId: menuNameMap["plugin"], Path: "installPlugin", Name: "installPlugin", Component: "view/systemTools/installPlugin/index.vue", Sort: 1, Meta: Meta{Title: "插件安装", Icon: "box"}},
|
||||||
|
@@ -215,3 +215,21 @@ export const mcp = (data) => {
|
|||||||
data
|
data
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const mcpList = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/autoCode/mcpList',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const mcpTest = (data) => {
|
||||||
|
return service({
|
||||||
|
url: '/autoCode/mcpTest',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -58,6 +58,7 @@
|
|||||||
"/src/view/systemTools/autoCode/component/previewCodeDialog.vue": "PreviewCodeDialog",
|
"/src/view/systemTools/autoCode/component/previewCodeDialog.vue": "PreviewCodeDialog",
|
||||||
"/src/view/systemTools/autoCode/index.vue": "AutoCode",
|
"/src/view/systemTools/autoCode/index.vue": "AutoCode",
|
||||||
"/src/view/systemTools/autoCode/mcp.vue": "Mcp",
|
"/src/view/systemTools/autoCode/mcp.vue": "Mcp",
|
||||||
|
"/src/view/systemTools/autoCode/mcpToolsTest.vue": "McpToolsTest",
|
||||||
"/src/view/systemTools/autoCode/picture.vue": "Picture",
|
"/src/view/systemTools/autoCode/picture.vue": "Picture",
|
||||||
"/src/view/systemTools/autoCodeAdmin/index.vue": "AutoCodeAdmin",
|
"/src/view/systemTools/autoCodeAdmin/index.vue": "AutoCodeAdmin",
|
||||||
"/src/view/systemTools/autoPkg/autoPkg.vue": "AutoPkg",
|
"/src/view/systemTools/autoPkg/autoPkg.vue": "AutoPkg",
|
||||||
|
Reference in New Issue
Block a user