From 510439e95313fede94ce15f106d24e2256c9c4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Salavisa?= Date: Sat, 28 Feb 2026 16:16:28 +0000 Subject: [PATCH] fix: align MCP tool parameters and JSON tags with backend (#329) --- .../internal/agentctl/server/mcp/handlers.go | 37 +++++++----- .../internal/agentctl/server/mcp/server.go | 18 +++--- .../backend/internal/mcp/handlers/handlers.go | 59 +++++++------------ 3 files changed, 50 insertions(+), 64 deletions(-) diff --git a/apps/backend/internal/agentctl/server/mcp/handlers.go b/apps/backend/internal/agentctl/server/mcp/handlers.go index 4fb2f4320a..03a4c7ec23 100644 --- a/apps/backend/internal/agentctl/server/mcp/handlers.go +++ b/apps/backend/internal/agentctl/server/mcp/handlers.go @@ -10,9 +10,10 @@ import ( ) // MCP action constants for backend WS requests +// These must match the actions registered in pkg/websocket/actions.go const ( ActionMCPListWorkspaces = "mcp.list_workspaces" - ActionMCPListBoards = "mcp.list_boards" + ActionMCPListWorkflows = "mcp.list_workflows" ActionMCPListWorkflowSteps = "mcp.list_workflow_steps" ActionMCPListTasks = "mcp.list_tasks" ActionMCPCreateTask = "mcp.create_task" @@ -26,7 +27,8 @@ const ( func (s *Server) listWorkspacesHandler() server.ToolHandlerFunc { return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - var result []map[string]interface{} + // Backend returns {workspaces: [...], total: N} + var result map[string]interface{} if err := s.backend.RequestPayload(ctx, ActionMCPListWorkspaces, nil, &result); err != nil { return mcp.NewToolResultError(err.Error()), nil } @@ -35,15 +37,16 @@ func (s *Server) listWorkspacesHandler() server.ToolHandlerFunc { } } -func (s *Server) listBoardsHandler() server.ToolHandlerFunc { +func (s *Server) listWorkflowsHandler() server.ToolHandlerFunc { return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { workspaceID, err := req.RequireString("workspace_id") if err != nil { return mcp.NewToolResultError("workspace_id is required"), nil } payload := map[string]string{"workspace_id": workspaceID} - var result []map[string]interface{} - if err := s.backend.RequestPayload(ctx, ActionMCPListBoards, payload, &result); err != nil { + // Backend returns {workflows: [...], total: N} + var result map[string]interface{} + if err := s.backend.RequestPayload(ctx, ActionMCPListWorkflows, payload, &result); err != nil { return mcp.NewToolResultError(err.Error()), nil } data, _ := json.MarshalIndent(result, "", " ") @@ -53,12 +56,13 @@ func (s *Server) listBoardsHandler() server.ToolHandlerFunc { func (s *Server) listWorkflowStepsHandler() server.ToolHandlerFunc { return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - boardID, err := req.RequireString("board_id") + workflowID, err := req.RequireString("workflow_id") if err != nil { - return mcp.NewToolResultError("board_id is required"), nil + return mcp.NewToolResultError("workflow_id is required"), nil } - payload := map[string]string{"board_id": boardID} - var result []map[string]interface{} + payload := map[string]string{"workflow_id": workflowID} + // Backend returns {workflow_steps: [...], total: N} + var result map[string]interface{} if err := s.backend.RequestPayload(ctx, ActionMCPListWorkflowSteps, payload, &result); err != nil { return mcp.NewToolResultError(err.Error()), nil } @@ -69,12 +73,13 @@ func (s *Server) listWorkflowStepsHandler() server.ToolHandlerFunc { func (s *Server) listTasksHandler() server.ToolHandlerFunc { return func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { - boardID, err := req.RequireString("board_id") + workflowID, err := req.RequireString("workflow_id") if err != nil { - return mcp.NewToolResultError("board_id is required"), nil + return mcp.NewToolResultError("workflow_id is required"), nil } - payload := map[string]string{"board_id": boardID} - var result []map[string]interface{} + payload := map[string]string{"workflow_id": workflowID} + // Backend returns {tasks: [...], total: N} + var result map[string]interface{} if err := s.backend.RequestPayload(ctx, ActionMCPListTasks, payload, &result); err != nil { return mcp.NewToolResultError(err.Error()), nil } @@ -89,9 +94,9 @@ func (s *Server) createTaskHandler() server.ToolHandlerFunc { if err != nil { return mcp.NewToolResultError("workspace_id is required"), nil } - boardID, err := req.RequireString("board_id") + workflowID, err := req.RequireString("workflow_id") if err != nil { - return mcp.NewToolResultError("board_id is required"), nil + return mcp.NewToolResultError("workflow_id is required"), nil } workflowStepID, err := req.RequireString("workflow_step_id") if err != nil { @@ -105,7 +110,7 @@ func (s *Server) createTaskHandler() server.ToolHandlerFunc { payload := map[string]string{ "workspace_id": workspaceID, - "board_id": boardID, + "workflow_id": workflowID, "workflow_step_id": workflowStepID, "title": title, "description": description, diff --git a/apps/backend/internal/agentctl/server/mcp/server.go b/apps/backend/internal/agentctl/server/mcp/server.go index fd6f6b4614..44f90ffeea 100644 --- a/apps/backend/internal/agentctl/server/mcp/server.go +++ b/apps/backend/internal/agentctl/server/mcp/server.go @@ -206,31 +206,31 @@ func (s *Server) registerKanbanTools() { s.wrapHandler("list_workspaces", s.listWorkspacesHandler()), ) s.mcpServer.AddTool( - mcp.NewTool("list_boards", - mcp.WithDescription("List all boards in a workspace."), + mcp.NewTool("list_workflows", + mcp.WithDescription("List all workflows in a workspace."), mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")), ), - s.wrapHandler("list_boards", s.listBoardsHandler()), + s.wrapHandler("list_workflows", s.listWorkflowsHandler()), ) s.mcpServer.AddTool( mcp.NewTool("list_workflow_steps", - mcp.WithDescription("List all workflow steps in a board."), - mcp.WithString("board_id", mcp.Required(), mcp.Description("The board ID")), + mcp.WithDescription("List all workflow steps in a workflow."), + mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")), ), s.wrapHandler("list_workflow_steps", s.listWorkflowStepsHandler()), ) s.mcpServer.AddTool( mcp.NewTool("list_tasks", - mcp.WithDescription("List all tasks on a board."), - mcp.WithString("board_id", mcp.Required(), mcp.Description("The board ID")), + mcp.WithDescription("List all tasks in a workflow."), + mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")), ), s.wrapHandler("list_tasks", s.listTasksHandler()), ) s.mcpServer.AddTool( mcp.NewTool("create_task", - mcp.WithDescription("Create a new task on a board."), + mcp.WithDescription("Create a new task in a workflow."), mcp.WithString("workspace_id", mcp.Required(), mcp.Description("The workspace ID")), - mcp.WithString("board_id", mcp.Required(), mcp.Description("The board ID")), + mcp.WithString("workflow_id", mcp.Required(), mcp.Description("The workflow ID")), mcp.WithString("workflow_step_id", mcp.Required(), mcp.Description("The workflow step ID")), mcp.WithString("title", mcp.Required(), mcp.Description("The task title")), mcp.WithString("description", mcp.Description("The task description")), diff --git a/apps/backend/internal/mcp/handlers/handlers.go b/apps/backend/internal/mcp/handlers/handlers.go index 1754b80188..0b76620af8 100644 --- a/apps/backend/internal/mcp/handlers/handlers.go +++ b/apps/backend/internal/mcp/handlers/handlers.go @@ -188,7 +188,14 @@ func (h *Handlers) handleListTasks(ctx context.Context, msg *ws.Message) (*ws.Me // handleCreateTask creates a new task. func (h *Handlers) handleCreateTask(ctx context.Context, msg *ws.Message) (*ws.Message, error) { - var req dto.CreateTaskRequest + // Use local struct with JSON tags since dto.CreateTaskRequest lacks them + var req struct { + WorkspaceID string `json:"workspace_id"` + WorkflowID string `json:"workflow_id"` + WorkflowStepID string `json:"workflow_step_id"` + Title string `json:"title"` + Description string `json:"description"` + } if err := json.Unmarshal(msg.Payload, &req); err != nil { return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeBadRequest, "Invalid payload: "+err.Error(), nil) } @@ -205,28 +212,12 @@ func (h *Handlers) handleCreateTask(ctx context.Context, msg *ws.Message) (*ws.M return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "title is required", nil) } - var repos []service.TaskRepositoryInput - for _, r := range req.Repositories { - repos = append(repos, service.TaskRepositoryInput{ - RepositoryID: r.RepositoryID, - BaseBranch: r.BaseBranch, - LocalPath: r.LocalPath, - Name: r.Name, - DefaultBranch: r.DefaultBranch, - }) - } - task, err := h.taskSvc.CreateTask(ctx, &service.CreateTaskRequest{ WorkspaceID: req.WorkspaceID, WorkflowID: req.WorkflowID, WorkflowStepID: req.WorkflowStepID, Title: req.Title, Description: req.Description, - Priority: req.Priority, - State: req.State, - Repositories: repos, - Position: req.Position, - Metadata: req.Metadata, }) if err != nil { h.logger.Error("failed to create task", zap.Error(err)) @@ -238,33 +229,23 @@ func (h *Handlers) handleCreateTask(ctx context.Context, msg *ws.Message) (*ws.M // handleUpdateTask updates an existing task. func (h *Handlers) handleUpdateTask(ctx context.Context, msg *ws.Message) (*ws.Message, error) { - var req dto.UpdateTaskRequest + // Use local struct with JSON tags since dto.UpdateTaskRequest lacks them + var req struct { + TaskID string `json:"task_id"` + Title *string `json:"title"` + Description *string `json:"description"` + State *string `json:"state"` + } if err := json.Unmarshal(msg.Payload, &req); err != nil { return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeBadRequest, "Invalid payload: "+err.Error(), nil) } - if req.ID == "" { - return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "id is required", nil) + if req.TaskID == "" { + return ws.NewError(msg.ID, msg.Action, ws.ErrorCodeValidation, "task_id is required", nil) } - var repos []service.TaskRepositoryInput - for _, r := range req.Repositories { - repos = append(repos, service.TaskRepositoryInput{ - RepositoryID: r.RepositoryID, - BaseBranch: r.BaseBranch, - LocalPath: r.LocalPath, - Name: r.Name, - DefaultBranch: r.DefaultBranch, - }) - } - - task, err := h.taskSvc.UpdateTask(ctx, req.ID, &service.UpdateTaskRequest{ - Title: req.Title, - Description: req.Description, - Priority: req.Priority, - State: req.State, - Repositories: repos, - Position: req.Position, - Metadata: req.Metadata, + task, err := h.taskSvc.UpdateTask(ctx, req.TaskID, &service.UpdateTaskRequest{ + Title: req.Title, + Description: req.Description, }) if err != nil { h.logger.Error("failed to update task", zap.Error(err))