mongo.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package data
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/mark3labs/mcp-go/mcp"
  7. "github.com/mark3labs/mcp-go/server"
  8. "go.mongodb.org/mongo-driver/bson"
  9. "go.mongodb.org/mongo-driver/mongo/options"
  10. "fwh-toolbox-server/cmd/mcp/tools/helper"
  11. "fwh-toolbox-server/database"
  12. )
  13. // mongoAllowedCollections 允许查询的 collection 白名单
  14. var mongoAllowedCollections = []string{
  15. // 站点信息管理
  16. "SiteInfoManageData",
  17. "SiteInfoManageRecord",
  18. // SIM卡管理
  19. "SimCardManage",
  20. "SimCardMonthlyData",
  21. // 批量下载
  22. "BatchDownloadConfig",
  23. // 站点分析
  24. "SiteAnalysisConfig",
  25. // 统计
  26. "StatisticsShutdown",
  27. "StatisticsAlarm",
  28. // 站点监控
  29. "SiteMonitorConfig",
  30. // 自动升级
  31. "AutoUpdateConfigOption",
  32. "AutoUpdateConfig",
  33. // 注意:UserInfo(内部用户身份数据)与 AiChatSessions(历史对话自由文本,
  34. // 字段级脱敏无效)已从白名单移除,禁止通过 data_mongo_* 直查以防隐私泄露。
  35. }
  36. func registerMongoTools(s *server.MCPServer) {
  37. s.AddTool(
  38. mcp.NewTool("data_mongo_find",
  39. mcp.WithDescription("直接查询 MongoDB 集合数据(只读)。用于 service 层无法满足的灵活查询场景。"),
  40. mcp.WithString("collection", mcp.Required(), mcp.Description(
  41. fmt.Sprintf("集合名,可选值: %v", mongoAllowedCollections),
  42. )),
  43. mcp.WithString("filter", mcp.Description("查询条件,JSON 格式的 MongoDB filter(如 {\"sn\": \"xxx\"}),不传则查全部")),
  44. mcp.WithNumber("limit", mcp.Description("返回数量限制,默认10,最大100")),
  45. mcp.WithNumber("skip", mcp.Description("跳过数量(分页偏移),默认0")),
  46. mcp.WithString("sort", mcp.Description("排序条件,JSON 格式(如 {\"createdAt\": -1})")),
  47. ),
  48. handleMongoFind,
  49. )
  50. s.AddTool(
  51. mcp.NewTool("data_mongo_count",
  52. mcp.WithDescription("统计 MongoDB 集合中符合条件的文档数量"),
  53. mcp.WithString("collection", mcp.Required(), mcp.Description(
  54. fmt.Sprintf("集合名,可选值: %v", mongoAllowedCollections),
  55. )),
  56. mcp.WithString("filter", mcp.Description("查询条件,JSON 格式的 MongoDB filter,不传则统计全部")),
  57. ),
  58. handleMongoCount,
  59. )
  60. }
  61. func handleMongoFind(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  62. collection, err := req.RequireString("collection")
  63. if err != nil {
  64. return helper.ParamError("collection"), nil
  65. }
  66. if !isAllowedCollection(collection) {
  67. return helper.ParamInvalidError("collection", fmt.Sprintf("不允许查询此集合,可选: %v", mongoAllowedCollections)), nil
  68. }
  69. // 解析 filter
  70. filterStr := req.GetString("filter", "{}")
  71. var filter bson.M
  72. if err := json.Unmarshal([]byte(filterStr), &filter); err != nil {
  73. return helper.ParamInvalidError("filter", fmt.Sprintf("JSON 解析失败: %v", err)), nil
  74. }
  75. // limit
  76. limit := int64(req.GetFloat("limit", 10))
  77. if limit > 100 {
  78. limit = 100
  79. }
  80. if limit < 1 {
  81. limit = 10
  82. }
  83. // skip
  84. skip := int64(req.GetFloat("skip", 0))
  85. if skip < 0 {
  86. skip = 0
  87. }
  88. // sort
  89. var sort bson.M
  90. sortStr := req.GetString("sort", "")
  91. if sortStr != "" {
  92. if err := json.Unmarshal([]byte(sortStr), &sort); err != nil {
  93. return helper.ParamInvalidError("sort", fmt.Sprintf("JSON 解析失败: %v", err)), nil
  94. }
  95. }
  96. // 查询
  97. coll := database.GetCollection(collection)
  98. findOpts := options.Find().SetLimit(limit).SetSkip(skip)
  99. if sort != nil {
  100. findOpts.SetSort(sort)
  101. }
  102. cursor, err := coll.Find(ctx, filter, findOpts)
  103. if err != nil {
  104. return helper.ServiceError("MongoDB查询", err), nil
  105. }
  106. defer cursor.Close(ctx)
  107. var results []bson.M
  108. if err := cursor.All(ctx, &results); err != nil {
  109. return helper.ServiceError("读取MongoDB结果", err), nil
  110. }
  111. if len(results) == 0 {
  112. return helper.EmptyResult("查询结果为空"), nil
  113. }
  114. return helper.JSONResultOrFileAuto(results), nil
  115. }
  116. func handleMongoCount(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  117. collection, err := req.RequireString("collection")
  118. if err != nil {
  119. return helper.ParamError("collection"), nil
  120. }
  121. if !isAllowedCollection(collection) {
  122. return helper.ParamInvalidError("collection", fmt.Sprintf("不允许查询此集合,可选: %v", mongoAllowedCollections)), nil
  123. }
  124. filterStr := req.GetString("filter", "{}")
  125. var filter bson.M
  126. if err := json.Unmarshal([]byte(filterStr), &filter); err != nil {
  127. return helper.ParamInvalidError("filter", fmt.Sprintf("JSON 解析失败: %v", err)), nil
  128. }
  129. coll := database.GetCollection(collection)
  130. count, err := coll.CountDocuments(ctx, filter)
  131. if err != nil {
  132. return helper.ServiceError("MongoDB统计", err), nil
  133. }
  134. return helper.JSONResultOrFileAuto(map[string]any{
  135. "collection": collection,
  136. "count": count,
  137. }), nil
  138. }
  139. func isAllowedCollection(name string) bool {
  140. for _, c := range mongoAllowedCollections {
  141. if c == name {
  142. return true
  143. }
  144. }
  145. return false
  146. }