fix: align MCP tool parameters and JSON tags with backend (#329)

This commit is contained in:
João Salavisa
2026-02-28 16:16:28 +00:00
committed by GitHub
parent 1f3c4c8f4e
commit 510439e953
3 changed files with 50 additions and 64 deletions
@@ -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,
@@ -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")),
+20 -39
View File
@@ -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))