plane_api_handlers.go 9.2 KB

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