package data import ( "context" "github.com/mark3labs/mcp-go/mcp" "fwh-toolbox-server/cmd/mcp/tools/helper" ) // planeV2RawResult 将 Plane V2 API 返回的原始 JSON bytes 转为 MCP 结果。 // 原始字节先经流式 PII 脱敏(优化 B),脱敏失败(含非法 JSON)则返回错误、拒绝输出。 // 结果较大时自动落盘到本地文件(/tmp/fwh-toolbox-agent/results/), // 返回 localPath + 预览,避免大数据占用 LLM 上下文。 func planeV2RawResult(data []byte, err error, action string) (*mcp.CallToolResult, error) { if err != nil { return helper.ServiceError(action, err), nil } return helper.BytesResultOrFileMasked(data, "plane_v2_"+action), nil } // requireSlugProject 校验并返回必填的 workspace_slug 与 project_id。 // 缺失时返回对应的 ParamError(errResult 非 nil,调用方应直接返回它)。 func requireSlugProject(req mcp.CallToolRequest) (slug, projectId string, errResult *mcp.CallToolResult) { slug, err := req.RequireString("workspace_slug") if err != nil { return "", "", helper.ParamError("workspace_slug") } projectId, err = req.RequireString("project_id") if err != nil { return "", "", helper.ParamError("project_id") } return slug, projectId, nil } // === Workspace Handlers === func handlePlaneV2ListWorkspaces(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { data, err := planeV2Svc.ListWorkspaces() return planeV2RawResult(data, err, "列出工作空间") } func handlePlaneV2GetWorkspace(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, err := req.RequireString("workspace_slug") if err != nil { return helper.ParamError("workspace_slug"), nil } data, apiErr := planeV2Svc.GetWorkspace(slug) return planeV2RawResult(data, apiErr, "获取工作空间详情") } // === Project Handlers === func handlePlaneV2ListProjects(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, err := req.RequireString("workspace_slug") if err != nil { return helper.ParamError("workspace_slug"), nil } data, apiErr := planeV2Svc.ListProjects(slug) return planeV2RawResult(data, apiErr, "列出项目") } func handlePlaneV2GetProject(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, apiErr := planeV2Svc.GetProject(slug, projectId) return planeV2RawResult(data, apiErr, "获取项目详情") } // === Issue Handlers === func handlePlaneV2ListIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListIssues(slug, projectId) return planeV2RawResult(data, err, "列出工作项") } func handlePlaneV2GetIssue(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } issueId, err := req.RequireString("issue_id") if err != nil { return helper.ParamError("issue_id"), nil } data, apiErr := planeV2Svc.GetIssue(slug, projectId, issueId) return planeV2RawResult(data, apiErr, "获取工作项详情") } func handlePlaneV2GetSubIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } issueId, err := req.RequireString("issue_id") if err != nil { return helper.ParamError("issue_id"), nil } data, apiErr := planeV2Svc.GetSubIssues(slug, projectId, issueId) return planeV2RawResult(data, apiErr, "获取子工作项") } func handlePlaneV2GetIssueActivity(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } issueId, err := req.RequireString("issue_id") if err != nil { return helper.ParamError("issue_id"), nil } data, apiErr := planeV2Svc.GetIssueActivity(slug, projectId, issueId) return planeV2RawResult(data, apiErr, "获取工作项活动历史") } // === Intake Handlers === func handlePlaneV2ListIntakeIssues(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } status := req.GetString("status", "") data, err := planeV2Svc.ListIntakeIssues(slug, projectId, status) return planeV2RawResult(data, err, "列出收件箱工作项") } func handlePlaneV2GetIntakeIssue(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } issueId, err := req.RequireString("issue_id") if err != nil { return helper.ParamError("issue_id"), nil } data, apiErr := planeV2Svc.GetIntakeIssue(slug, projectId, issueId) return planeV2RawResult(data, apiErr, "获取收件箱工作项详情") } // === Cycle Handlers === func handlePlaneV2ListCycles(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListCycles(slug, projectId) return planeV2RawResult(data, err, "列出周期") } func handlePlaneV2GetCycle(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } cycleId, err := req.RequireString("cycle_id") if err != nil { return helper.ParamError("cycle_id"), nil } data, apiErr := planeV2Svc.GetCycle(slug, projectId, cycleId) return planeV2RawResult(data, apiErr, "获取周期详情") } // === Module Handlers === func handlePlaneV2ListModules(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListModules(slug, projectId) return planeV2RawResult(data, err, "列出模块") } func handlePlaneV2GetModule(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } moduleId, err := req.RequireString("module_id") if err != nil { return helper.ParamError("module_id"), nil } data, apiErr := planeV2Svc.GetModule(slug, projectId, moduleId) return planeV2RawResult(data, apiErr, "获取模块详情") } // === Label Handlers === func handlePlaneV2ListLabels(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListLabels(slug, projectId) return planeV2RawResult(data, err, "列出标签") } // === State Handlers === func handlePlaneV2ListStates(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListStates(slug, projectId) return planeV2RawResult(data, err, "列出状态") } // === Page Handlers === func handlePlaneV2ListPages(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListPages(slug, projectId) return planeV2RawResult(data, err, "列出页面") } func handlePlaneV2GetPage(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } pageId, err := req.RequireString("page_id") if err != nil { return helper.ParamError("page_id"), nil } data, apiErr := planeV2Svc.GetPage(slug, projectId, pageId) return planeV2RawResult(data, apiErr, "获取页面详情") } func handlePlaneV2GetPageDescription(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } pageId, err := req.RequireString("page_id") if err != nil { return helper.ParamError("page_id"), nil } data, apiErr := planeV2Svc.GetPageDescription(slug, projectId, pageId) return planeV2RawResult(data, apiErr, "获取页面描述内容") } // === Member Handlers === func handlePlaneV2ListWorkspaceMembers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, err := req.RequireString("workspace_slug") if err != nil { return helper.ParamError("workspace_slug"), nil } data, apiErr := planeV2Svc.ListWorkspaceMembers(slug) return planeV2RawResult(data, apiErr, "列出工作空间成员") } func handlePlaneV2ListProjectMembers(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, projectId, errRes := requireSlugProject(req) if errRes != nil { return errRes, nil } data, err := planeV2Svc.ListProjectMembers(slug, projectId) return planeV2RawResult(data, err, "列出项目成员") } // === Sticky Handlers === func handlePlaneV2ListStickies(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { slug, err := req.RequireString("workspace_slug") if err != nil { return helper.ParamError("workspace_slug"), nil } data, apiErr := planeV2Svc.ListStickies(slug) return planeV2RawResult(data, apiErr, "列出便签") }