plane_api_handlers.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package data
  2. import (
  3. "context"
  4. "github.com/mark3labs/mcp-go/mcp"
  5. "fwh-toolbox-server/cmd/mcp/tools/helper"
  6. "fwh-toolbox-server/repositories"
  7. )
  8. // planeRawResult 将 Plane API 返回的原始 JSON bytes 转为 MCP 结果。
  9. // 原始字节先经流式 PII 脱敏(优化 B),脱敏失败(含非法 JSON)则返回错误、拒绝输出。
  10. func planeRawResult(data []byte, err error, action string) (*mcp.CallToolResult, error) {
  11. if err != nil {
  12. return helper.ServiceError(action, err), nil
  13. }
  14. return helper.BytesResultOrFileMasked(data, "plane_"+action), nil
  15. }
  16. // === Project Handlers ===
  17. func handlePlaneListProjects(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  18. data, err := repositories.PlaneListProjects()
  19. return planeRawResult(data, err, "列出项目")
  20. }
  21. func handlePlaneGetProject(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  22. projectID, err := req.RequireString("project_id")
  23. if err != nil {
  24. return helper.ParamError("project_id"), nil
  25. }
  26. data, apiErr := repositories.PlaneGetProject(projectID)
  27. return planeRawResult(data, apiErr, "获取项目详情")
  28. }
  29. func handlePlaneGetProjectSummary(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  30. projectID, err := req.RequireString("project_id")
  31. if err != nil {
  32. return helper.ParamError("project_id"), nil
  33. }
  34. fields := req.GetString("fields", "")
  35. data, apiErr := repositories.PlaneGetProjectSummary(projectID, fields)
  36. return planeRawResult(data, apiErr, "获取项目摘要")
  37. }
  38. // === Work Item Handlers ===
  39. func handlePlaneListWorkItems(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  40. params := make(map[string]string)
  41. if v := req.GetString("state", ""); v != "" {
  42. params["state"] = v
  43. }
  44. if v := req.GetString("priority", ""); v != "" {
  45. params["priority"] = v
  46. }
  47. if v := req.GetString("assignee", ""); v != "" {
  48. params["assignee"] = v
  49. }
  50. if v := req.GetString("label", ""); v != "" {
  51. params["label"] = v
  52. }
  53. data, err := repositories.PlaneListWorkItems(params)
  54. return planeRawResult(data, err, "列出工作项")
  55. }
  56. func handlePlaneGetWorkItem(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  57. id, err := req.RequireString("work_item_id")
  58. if err != nil {
  59. return helper.ParamError("work_item_id"), nil
  60. }
  61. data, apiErr := repositories.PlaneGetWorkItem(id)
  62. return planeRawResult(data, apiErr, "获取工作项详情")
  63. }
  64. func handlePlaneGetWorkItemByIdentifier(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  65. identifier, err := req.RequireString("identifier")
  66. if err != nil {
  67. return helper.ParamError("identifier"), nil
  68. }
  69. seqID := int(req.GetFloat("sequence_id", 0))
  70. if seqID <= 0 {
  71. return helper.ParamError("sequence_id"), nil
  72. }
  73. data, apiErr := repositories.PlaneGetWorkItemByIdentifier(identifier, seqID)
  74. return planeRawResult(data, apiErr, "通过标识符获取工作项")
  75. }
  76. func handlePlaneSearchWorkItems(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  77. query, err := req.RequireString("query")
  78. if err != nil {
  79. return helper.ParamError("query"), nil
  80. }
  81. limit := int(req.GetFloat("limit", 20))
  82. data, apiErr := repositories.PlaneSearchWorkItems(query, limit, "")
  83. return planeRawResult(data, apiErr, "搜索工作项")
  84. }
  85. // === Cycle Handlers ===
  86. func handlePlaneListCycles(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  87. data, err := repositories.PlaneListCycles()
  88. return planeRawResult(data, err, "列出迭代")
  89. }
  90. func handlePlaneGetCycle(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  91. id, err := req.RequireString("cycle_id")
  92. if err != nil {
  93. return helper.ParamError("cycle_id"), nil
  94. }
  95. data, apiErr := repositories.PlaneGetCycle(id)
  96. return planeRawResult(data, apiErr, "获取迭代详情")
  97. }
  98. func handlePlaneListCycleIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  99. id, err := req.RequireString("cycle_id")
  100. if err != nil {
  101. return helper.ParamError("cycle_id"), nil
  102. }
  103. data, apiErr := repositories.PlaneListCycleIssues(id)
  104. return planeRawResult(data, apiErr, "列出迭代工作项")
  105. }
  106. // === Module Handlers ===
  107. func handlePlaneListModules(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  108. data, err := repositories.PlaneListModules()
  109. return planeRawResult(data, err, "列出模块")
  110. }
  111. func handlePlaneGetModule(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  112. id, err := req.RequireString("module_id")
  113. if err != nil {
  114. return helper.ParamError("module_id"), nil
  115. }
  116. data, apiErr := repositories.PlaneGetModule(id)
  117. return planeRawResult(data, apiErr, "获取模块详情")
  118. }
  119. func handlePlaneListModuleIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  120. id, err := req.RequireString("module_id")
  121. if err != nil {
  122. return helper.ParamError("module_id"), nil
  123. }
  124. data, apiErr := repositories.PlaneListModuleIssues(id)
  125. return planeRawResult(data, apiErr, "列出模块工作项")
  126. }
  127. // === State / Label / Member Handlers ===
  128. func handlePlaneListStates(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  129. data, err := repositories.PlaneListStates()
  130. return planeRawResult(data, err, "列出状态")
  131. }
  132. func handlePlaneListLabels(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  133. data, err := repositories.PlaneListLabels()
  134. return planeRawResult(data, err, "列出标签")
  135. }
  136. func handlePlaneListWorkspaceMembers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  137. data, err := repositories.PlaneListWorkspaceMembers()
  138. return planeRawResult(data, err, "列出工作空间成员")
  139. }
  140. func handlePlaneListProjectMembers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  141. data, err := repositories.PlaneListProjectMembers()
  142. return planeRawResult(data, err, "列出项目成员")
  143. }
  144. // === Comment / Activity / Link / Attachment / Relation Handlers ===
  145. func handlePlaneListWorkItemComments(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  146. id, err := req.RequireString("work_item_id")
  147. if err != nil {
  148. return helper.ParamError("work_item_id"), nil
  149. }
  150. data, apiErr := repositories.PlaneListWorkItemComments(id)
  151. return planeRawResult(data, apiErr, "列出评论")
  152. }
  153. func handlePlaneListWorkItemActivities(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  154. id, err := req.RequireString("work_item_id")
  155. if err != nil {
  156. return helper.ParamError("work_item_id"), nil
  157. }
  158. data, apiErr := repositories.PlaneListWorkItemActivities(id)
  159. return planeRawResult(data, apiErr, "列出活动历史")
  160. }
  161. func handlePlaneListWorkItemLinks(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  162. id, err := req.RequireString("work_item_id")
  163. if err != nil {
  164. return helper.ParamError("work_item_id"), nil
  165. }
  166. data, apiErr := repositories.PlaneListWorkItemLinks(id)
  167. return planeRawResult(data, apiErr, "列出链接")
  168. }
  169. func handlePlaneListWorkItemAttachments(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  170. id, err := req.RequireString("work_item_id")
  171. if err != nil {
  172. return helper.ParamError("work_item_id"), nil
  173. }
  174. data, apiErr := repositories.PlaneListWorkItemAttachments(id)
  175. return planeRawResult(data, apiErr, "列出附件")
  176. }
  177. func handlePlaneListWorkItemRelations(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  178. id, err := req.RequireString("work_item_id")
  179. if err != nil {
  180. return helper.ParamError("work_item_id"), nil
  181. }
  182. data, apiErr := repositories.PlaneListWorkItemRelations(id)
  183. return planeRawResult(data, apiErr, "列出关系")
  184. }
  185. // === Intake Handlers ===
  186. func handlePlaneGetIntakeIssueList(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  187. data, err := repositories.PlaneGetIntakeIssueList()
  188. if err != nil {
  189. return helper.ServiceError("查询Intake列表", err), nil
  190. }
  191. return helper.JSONResultOrFileAuto(data), nil
  192. }
  193. func handlePlaneGetIntakeIssueDetail(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  194. id, err := req.RequireString("intake_id")
  195. if err != nil {
  196. return helper.ParamError("intake_id"), nil
  197. }
  198. data, apiErr := repositories.PlaneGetIntakeIssueDetail(id)
  199. return planeRawResult(data, apiErr, "获取Intake详情")
  200. }
  201. // === V2 Web API: Page Handlers (已迁移到 plane_v2_api_handlers.go) ===