menu_creator.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package mcpTool
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "github.com/flipped-aurora/gin-vue-admin/server/global"
  8. "github.com/flipped-aurora/gin-vue-admin/server/model/system"
  9. "github.com/flipped-aurora/gin-vue-admin/server/service"
  10. "github.com/mark3labs/mcp-go/mcp"
  11. "go.uber.org/zap"
  12. )
  13. // 注册工具
  14. func init() {
  15. RegisterTool(&MenuCreator{})
  16. }
  17. // MenuCreateRequest 菜单创建请求结构
  18. type MenuCreateRequest struct {
  19. ParentId uint `json:"parentId"` // 父菜单ID,0表示根菜单
  20. Path string `json:"path"` // 路由path
  21. Name string `json:"name"` // 路由name
  22. Hidden bool `json:"hidden"` // 是否在列表隐藏
  23. Component string `json:"component"` // 对应前端文件路径
  24. Sort int `json:"sort"` // 排序标记
  25. Title string `json:"title"` // 菜单名
  26. Icon string `json:"icon"` // 菜单图标
  27. KeepAlive bool `json:"keepAlive"` // 是否缓存
  28. DefaultMenu bool `json:"defaultMenu"` // 是否是基础路由
  29. CloseTab bool `json:"closeTab"` // 自动关闭tab
  30. ActiveName string `json:"activeName"` // 高亮菜单
  31. Parameters []MenuParameterRequest `json:"parameters"` // 路由参数
  32. MenuBtn []MenuButtonRequest `json:"menuBtn"` // 菜单按钮
  33. }
  34. // MenuParameterRequest 菜单参数请求结构
  35. type MenuParameterRequest struct {
  36. Type string `json:"type"` // 参数类型:params或query
  37. Key string `json:"key"` // 参数key
  38. Value string `json:"value"` // 参数值
  39. }
  40. // MenuButtonRequest 菜单按钮请求结构
  41. type MenuButtonRequest struct {
  42. Name string `json:"name"` // 按钮名称
  43. Desc string `json:"desc"` // 按钮描述
  44. }
  45. // MenuCreateResponse 菜单创建响应结构
  46. type MenuCreateResponse struct {
  47. Success bool `json:"success"`
  48. Message string `json:"message"`
  49. MenuID uint `json:"menuId"`
  50. Name string `json:"name"`
  51. Path string `json:"path"`
  52. }
  53. // MenuCreator 菜单创建工具
  54. type MenuCreator struct{}
  55. // New 创建菜单创建工具
  56. func (m *MenuCreator) New() mcp.Tool {
  57. return mcp.NewTool("create_menu",
  58. mcp.WithDescription(`创建前端菜单记录,用于AI编辑器自动添加前端页面时自动创建对应的菜单项。
  59. **重要限制:**
  60. - 当使用gva_auto_generate工具且needCreatedModules=true时,模块创建会自动生成菜单项,不应调用此工具
  61. - 仅在以下情况使用:1) 单独创建菜单(不涉及模块创建);2) AI编辑器自动添加前端页面时`),
  62. mcp.WithNumber("parentId",
  63. mcp.Description("父菜单ID,0表示根菜单"),
  64. mcp.DefaultNumber(0),
  65. ),
  66. mcp.WithString("path",
  67. mcp.Required(),
  68. mcp.Description("路由path,如:userList"),
  69. ),
  70. mcp.WithString("name",
  71. mcp.Required(),
  72. mcp.Description("路由name,用于Vue Router,如:userList"),
  73. ),
  74. mcp.WithBoolean("hidden",
  75. mcp.Description("是否在菜单列表中隐藏"),
  76. ),
  77. mcp.WithString("component",
  78. mcp.Required(),
  79. mcp.Description("对应的前端Vue组件路径,如:view/user/list.vue"),
  80. ),
  81. mcp.WithNumber("sort",
  82. mcp.Description("菜单排序号,数字越小越靠前"),
  83. mcp.DefaultNumber(1),
  84. ),
  85. mcp.WithString("title",
  86. mcp.Required(),
  87. mcp.Description("菜单显示标题"),
  88. ),
  89. mcp.WithString("icon",
  90. mcp.Description("菜单图标名称"),
  91. mcp.DefaultString("menu"),
  92. ),
  93. mcp.WithBoolean("keepAlive",
  94. mcp.Description("是否缓存页面"),
  95. ),
  96. mcp.WithBoolean("defaultMenu",
  97. mcp.Description("是否是基础路由"),
  98. ),
  99. mcp.WithBoolean("closeTab",
  100. mcp.Description("是否自动关闭tab"),
  101. ),
  102. mcp.WithString("activeName",
  103. mcp.Description("高亮菜单名称"),
  104. ),
  105. mcp.WithString("parameters",
  106. mcp.Description("路由参数JSON字符串,格式:[{\"type\":\"params\",\"key\":\"id\",\"value\":\"1\"}]"),
  107. ),
  108. mcp.WithString("menuBtn",
  109. mcp.Description("菜单按钮JSON字符串,格式:[{\"name\":\"add\",\"desc\":\"新增\"}]"),
  110. ),
  111. )
  112. }
  113. // Handle 处理菜单创建请求
  114. func (m *MenuCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  115. // 解析请求参数
  116. args := request.GetArguments()
  117. // 必需参数
  118. path, ok := args["path"].(string)
  119. if !ok || path == "" {
  120. return nil, errors.New("path 参数是必需的")
  121. }
  122. name, ok := args["name"].(string)
  123. if !ok || name == "" {
  124. return nil, errors.New("name 参数是必需的")
  125. }
  126. component, ok := args["component"].(string)
  127. if !ok || component == "" {
  128. return nil, errors.New("component 参数是必需的")
  129. }
  130. title, ok := args["title"].(string)
  131. if !ok || title == "" {
  132. return nil, errors.New("title 参数是必需的")
  133. }
  134. // 可选参数
  135. parentId := uint(0)
  136. if val, ok := args["parentId"].(float64); ok {
  137. parentId = uint(val)
  138. }
  139. hidden := false
  140. if val, ok := args["hidden"].(bool); ok {
  141. hidden = val
  142. }
  143. sort := 1
  144. if val, ok := args["sort"].(float64); ok {
  145. sort = int(val)
  146. }
  147. icon := "menu"
  148. if val, ok := args["icon"].(string); ok && val != "" {
  149. icon = val
  150. }
  151. keepAlive := false
  152. if val, ok := args["keepAlive"].(bool); ok {
  153. keepAlive = val
  154. }
  155. defaultMenu := false
  156. if val, ok := args["defaultMenu"].(bool); ok {
  157. defaultMenu = val
  158. }
  159. closeTab := false
  160. if val, ok := args["closeTab"].(bool); ok {
  161. closeTab = val
  162. }
  163. activeName := ""
  164. if val, ok := args["activeName"].(string); ok {
  165. activeName = val
  166. }
  167. // 解析参数和按钮
  168. var parameters []system.SysBaseMenuParameter
  169. if parametersStr, ok := args["parameters"].(string); ok && parametersStr != "" {
  170. var paramReqs []MenuParameterRequest
  171. if err := json.Unmarshal([]byte(parametersStr), &paramReqs); err != nil {
  172. return nil, fmt.Errorf("parameters 参数格式错误: %v", err)
  173. }
  174. for _, param := range paramReqs {
  175. parameters = append(parameters, system.SysBaseMenuParameter{
  176. Type: param.Type,
  177. Key: param.Key,
  178. Value: param.Value,
  179. })
  180. }
  181. }
  182. var menuBtn []system.SysBaseMenuBtn
  183. if menuBtnStr, ok := args["menuBtn"].(string); ok && menuBtnStr != "" {
  184. var btnReqs []MenuButtonRequest
  185. if err := json.Unmarshal([]byte(menuBtnStr), &btnReqs); err != nil {
  186. return nil, fmt.Errorf("menuBtn 参数格式错误: %v", err)
  187. }
  188. for _, btn := range btnReqs {
  189. menuBtn = append(menuBtn, system.SysBaseMenuBtn{
  190. Name: btn.Name,
  191. Desc: btn.Desc,
  192. })
  193. }
  194. }
  195. // 构建菜单对象
  196. menu := system.SysBaseMenu{
  197. ParentId: parentId,
  198. Path: path,
  199. Name: name,
  200. Hidden: hidden,
  201. Component: component,
  202. Sort: sort,
  203. Meta: system.Meta{
  204. Title: title,
  205. Icon: icon,
  206. KeepAlive: keepAlive,
  207. DefaultMenu: defaultMenu,
  208. CloseTab: closeTab,
  209. ActiveName: activeName,
  210. },
  211. Parameters: parameters,
  212. MenuBtn: menuBtn,
  213. }
  214. // 创建菜单
  215. menuService := service.ServiceGroupApp.SystemServiceGroup.MenuService
  216. err := menuService.AddBaseMenu(menu)
  217. if err != nil {
  218. return nil, fmt.Errorf("创建菜单失败: %v", err)
  219. }
  220. // 获取创建的菜单ID
  221. var createdMenu system.SysBaseMenu
  222. err = global.GVA_DB.Where("name = ? AND path = ?", name, path).First(&createdMenu).Error
  223. if err != nil {
  224. global.GVA_LOG.Warn("获取创建的菜单ID失败", zap.Error(err))
  225. }
  226. // 构建响应
  227. response := &MenuCreateResponse{
  228. Success: true,
  229. Message: fmt.Sprintf("成功创建菜单 %s", title),
  230. MenuID: createdMenu.ID,
  231. Name: name,
  232. Path: path,
  233. }
  234. resultJSON, err := json.MarshalIndent(response, "", " ")
  235. if err != nil {
  236. return nil, fmt.Errorf("序列化结果失败: %v", err)
  237. }
  238. // 添加权限分配提醒
  239. permissionReminder := "\n\n⚠️ 重要提醒:\n" +
  240. "菜单创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的菜单权限," +
  241. "以确保用户能够正常访问新菜单。\n" +
  242. "具体步骤:\n" +
  243. "1. 进入角色管理页面\n" +
  244. "2. 选择需要授权的角色\n" +
  245. "3. 在菜单权限中勾选新创建的菜单项\n" +
  246. "4. 保存权限配置"
  247. return &mcp.CallToolResult{
  248. Content: []mcp.Content{
  249. mcp.TextContent{
  250. Type: "text",
  251. Text: fmt.Sprintf("菜单创建结果:\n\n%s%s", string(resultJSON), permissionReminder),
  252. },
  253. },
  254. }, nil
  255. }