plane_v2_api_handlers.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. package data
  2. import (
  3. "context"
  4. "github.com/mark3labs/mcp-go/mcp"
  5. "fwh-toolbox-server/cmd/mcp/tools/helper"
  6. )
  7. // planeV2RawResult 将 Plane V2 API 返回的原始 JSON bytes 转为 MCP 结果。
  8. // 原始字节先经流式 PII 脱敏(优化 B),脱敏失败(含非法 JSON)则返回错误、拒绝输出。
  9. // 结果较大时自动落盘到本地文件(/tmp/fwh-toolbox-agent/results/),
  10. // 返回 localPath + 预览,避免大数据占用 LLM 上下文。
  11. func planeV2RawResult(data []byte, err error, action string) (*mcp.CallToolResult, error) {
  12. if err != nil {
  13. return helper.ServiceError(action, err), nil
  14. }
  15. return helper.BytesResultOrFileMasked(data, "plane_v2_"+action), nil
  16. }
  17. // requireSlugProject 校验并返回必填的 workspace_slug 与 project_id。
  18. // 缺失时返回对应的 ParamError(errResult 非 nil,调用方应直接返回它)。
  19. func requireSlugProject(req mcp.CallToolRequest) (slug, projectId string, errResult *mcp.CallToolResult) {
  20. slug, err := req.RequireString("workspace_slug")
  21. if err != nil {
  22. return "", "", helper.ParamError("workspace_slug")
  23. }
  24. projectId, err = req.RequireString("project_id")
  25. if err != nil {
  26. return "", "", helper.ParamError("project_id")
  27. }
  28. return slug, projectId, nil
  29. }
  30. // === Workspace Handlers ===
  31. func handlePlaneV2ListWorkspaces(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  32. data, err := planeV2Svc.ListWorkspaces()
  33. return planeV2RawResult(data, err, "列出工作空间")
  34. }
  35. func handlePlaneV2GetWorkspace(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  36. slug, err := req.RequireString("workspace_slug")
  37. if err != nil {
  38. return helper.ParamError("workspace_slug"), nil
  39. }
  40. data, apiErr := planeV2Svc.GetWorkspace(slug)
  41. return planeV2RawResult(data, apiErr, "获取工作空间详情")
  42. }
  43. // === Project Handlers ===
  44. func handlePlaneV2ListProjects(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  45. slug, err := req.RequireString("workspace_slug")
  46. if err != nil {
  47. return helper.ParamError("workspace_slug"), nil
  48. }
  49. data, apiErr := planeV2Svc.ListProjects(slug)
  50. return planeV2RawResult(data, apiErr, "列出项目")
  51. }
  52. func handlePlaneV2GetProject(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  53. slug, projectId, errRes := requireSlugProject(req)
  54. if errRes != nil {
  55. return errRes, nil
  56. }
  57. data, apiErr := planeV2Svc.GetProject(slug, projectId)
  58. return planeV2RawResult(data, apiErr, "获取项目详情")
  59. }
  60. // === Issue Handlers ===
  61. func handlePlaneV2ListIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  62. slug, projectId, errRes := requireSlugProject(req)
  63. if errRes != nil {
  64. return errRes, nil
  65. }
  66. data, err := planeV2Svc.ListIssues(slug, projectId)
  67. return planeV2RawResult(data, err, "列出工作项")
  68. }
  69. func handlePlaneV2GetIssue(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  70. slug, projectId, errRes := requireSlugProject(req)
  71. if errRes != nil {
  72. return errRes, nil
  73. }
  74. issueId, err := req.RequireString("issue_id")
  75. if err != nil {
  76. return helper.ParamError("issue_id"), nil
  77. }
  78. data, apiErr := planeV2Svc.GetIssue(slug, projectId, issueId)
  79. return planeV2RawResult(data, apiErr, "获取工作项详情")
  80. }
  81. func handlePlaneV2GetSubIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  82. slug, projectId, errRes := requireSlugProject(req)
  83. if errRes != nil {
  84. return errRes, nil
  85. }
  86. issueId, err := req.RequireString("issue_id")
  87. if err != nil {
  88. return helper.ParamError("issue_id"), nil
  89. }
  90. data, apiErr := planeV2Svc.GetSubIssues(slug, projectId, issueId)
  91. return planeV2RawResult(data, apiErr, "获取子工作项")
  92. }
  93. func handlePlaneV2GetIssueActivity(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  94. slug, projectId, errRes := requireSlugProject(req)
  95. if errRes != nil {
  96. return errRes, nil
  97. }
  98. issueId, err := req.RequireString("issue_id")
  99. if err != nil {
  100. return helper.ParamError("issue_id"), nil
  101. }
  102. data, apiErr := planeV2Svc.GetIssueActivity(slug, projectId, issueId)
  103. return planeV2RawResult(data, apiErr, "获取工作项活动历史")
  104. }
  105. // === Intake Handlers ===
  106. func handlePlaneV2ListIntakeIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  107. slug, projectId, errRes := requireSlugProject(req)
  108. if errRes != nil {
  109. return errRes, nil
  110. }
  111. status := req.GetString("status", "")
  112. data, err := planeV2Svc.ListIntakeIssues(slug, projectId, status)
  113. return planeV2RawResult(data, err, "列出收件箱工作项")
  114. }
  115. func handlePlaneV2GetIntakeIssue(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  116. slug, projectId, errRes := requireSlugProject(req)
  117. if errRes != nil {
  118. return errRes, nil
  119. }
  120. issueId, err := req.RequireString("issue_id")
  121. if err != nil {
  122. return helper.ParamError("issue_id"), nil
  123. }
  124. data, apiErr := planeV2Svc.GetIntakeIssue(slug, projectId, issueId)
  125. return planeV2RawResult(data, apiErr, "获取收件箱工作项详情")
  126. }
  127. // === Cycle Handlers ===
  128. func handlePlaneV2ListCycles(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  129. slug, projectId, errRes := requireSlugProject(req)
  130. if errRes != nil {
  131. return errRes, nil
  132. }
  133. data, err := planeV2Svc.ListCycles(slug, projectId)
  134. return planeV2RawResult(data, err, "列出周期")
  135. }
  136. func handlePlaneV2GetCycle(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  137. slug, projectId, errRes := requireSlugProject(req)
  138. if errRes != nil {
  139. return errRes, nil
  140. }
  141. cycleId, err := req.RequireString("cycle_id")
  142. if err != nil {
  143. return helper.ParamError("cycle_id"), nil
  144. }
  145. data, apiErr := planeV2Svc.GetCycle(slug, projectId, cycleId)
  146. return planeV2RawResult(data, apiErr, "获取周期详情")
  147. }
  148. // === Module Handlers ===
  149. func handlePlaneV2ListModules(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  150. slug, projectId, errRes := requireSlugProject(req)
  151. if errRes != nil {
  152. return errRes, nil
  153. }
  154. data, err := planeV2Svc.ListModules(slug, projectId)
  155. return planeV2RawResult(data, err, "列出模块")
  156. }
  157. func handlePlaneV2GetModule(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  158. slug, projectId, errRes := requireSlugProject(req)
  159. if errRes != nil {
  160. return errRes, nil
  161. }
  162. moduleId, err := req.RequireString("module_id")
  163. if err != nil {
  164. return helper.ParamError("module_id"), nil
  165. }
  166. data, apiErr := planeV2Svc.GetModule(slug, projectId, moduleId)
  167. return planeV2RawResult(data, apiErr, "获取模块详情")
  168. }
  169. // === Label Handlers ===
  170. func handlePlaneV2ListLabels(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  171. slug, projectId, errRes := requireSlugProject(req)
  172. if errRes != nil {
  173. return errRes, nil
  174. }
  175. data, err := planeV2Svc.ListLabels(slug, projectId)
  176. return planeV2RawResult(data, err, "列出标签")
  177. }
  178. // === State Handlers ===
  179. func handlePlaneV2ListStates(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  180. slug, projectId, errRes := requireSlugProject(req)
  181. if errRes != nil {
  182. return errRes, nil
  183. }
  184. data, err := planeV2Svc.ListStates(slug, projectId)
  185. return planeV2RawResult(data, err, "列出状态")
  186. }
  187. // === Page Handlers ===
  188. func handlePlaneV2ListPages(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  189. slug, projectId, errRes := requireSlugProject(req)
  190. if errRes != nil {
  191. return errRes, nil
  192. }
  193. data, err := planeV2Svc.ListPages(slug, projectId)
  194. return planeV2RawResult(data, err, "列出页面")
  195. }
  196. func handlePlaneV2GetPage(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  197. slug, projectId, errRes := requireSlugProject(req)
  198. if errRes != nil {
  199. return errRes, nil
  200. }
  201. pageId, err := req.RequireString("page_id")
  202. if err != nil {
  203. return helper.ParamError("page_id"), nil
  204. }
  205. data, apiErr := planeV2Svc.GetPage(slug, projectId, pageId)
  206. return planeV2RawResult(data, apiErr, "获取页面详情")
  207. }
  208. func handlePlaneV2GetPageDescription(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  209. slug, projectId, errRes := requireSlugProject(req)
  210. if errRes != nil {
  211. return errRes, nil
  212. }
  213. pageId, err := req.RequireString("page_id")
  214. if err != nil {
  215. return helper.ParamError("page_id"), nil
  216. }
  217. data, apiErr := planeV2Svc.GetPageDescription(slug, projectId, pageId)
  218. return planeV2RawResult(data, apiErr, "获取页面描述内容")
  219. }
  220. // === Member Handlers ===
  221. func handlePlaneV2ListWorkspaceMembers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  222. slug, err := req.RequireString("workspace_slug")
  223. if err != nil {
  224. return helper.ParamError("workspace_slug"), nil
  225. }
  226. data, apiErr := planeV2Svc.ListWorkspaceMembers(slug)
  227. return planeV2RawResult(data, apiErr, "列出工作空间成员")
  228. }
  229. func handlePlaneV2ListProjectMembers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  230. slug, projectId, errRes := requireSlugProject(req)
  231. if errRes != nil {
  232. return errRes, nil
  233. }
  234. data, err := planeV2Svc.ListProjectMembers(slug, projectId)
  235. return planeV2RawResult(data, err, "列出项目成员")
  236. }
  237. // === Sticky Handlers ===
  238. func handlePlaneV2ListStickies(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
  239. slug, err := req.RequireString("workspace_slug")
  240. if err != nil {
  241. return helper.ParamError("workspace_slug"), nil
  242. }
  243. data, apiErr := planeV2Svc.ListStickies(slug)
  244. return planeV2RawResult(data, apiErr, "列出便签")
  245. }