diff --git a/backend/mcp.go b/backend/mcp.go index a07f394..66ec55a 100644 --- a/backend/mcp.go +++ b/backend/mcp.go @@ -254,6 +254,31 @@ func mcpToolDefs() []infra.MCPToolDef { "required": []string{"id"}, }), }, + { + Name: "add_comment", + Description: "Anade un comentario (card_message) a una tarjeta. Requiere card_id, body y autor (author_id o author_username). Devuelve el CardMessage creado.", + InputSchema: rawSchema(map[string]any{ + "type": "object", + "properties": map[string]any{ + "card_id": map[string]any{"type": "string"}, + "body": map[string]any{"type": "string"}, + "author_id": map[string]any{"type": "string"}, + "author_username": map[string]any{"type": "string"}, + }, + "required": []string{"card_id", "body"}, + }), + }, + { + Name: "list_comments", + Description: "Lista los comentarios (card_messages) de una tarjeta en orden cronologico.", + InputSchema: rawSchema(map[string]any{ + "type": "object", + "properties": map[string]any{ + "card_id": map[string]any{"type": "string"}, + }, + "required": []string{"card_id"}, + }), + }, } } diff --git a/backend/tools.go b/backend/tools.go index 68d6896..d5f957b 100644 --- a/backend/tools.go +++ b/backend/tools.go @@ -50,6 +50,10 @@ func executeTool(db *DB, name string, input json.RawMessage) ToolResult { return toolListUsers(db) case "assign_card": return toolAssignCard(db, input) + case "add_comment": + return toolAddComment(db, input) + case "list_comments": + return toolListComments(db, input) default: return errMsg("unknown tool: " + name) } @@ -59,7 +63,8 @@ func executeTool(db *DB, name string, input json.RawMessage) ToolResult { func toolMutates(name string) bool { switch name { case "create_column", "update_column", "rename_column", "delete_column", "reorder_columns", - "create_card", "update_card", "delete_card", "move_card", "assign_card": + "create_card", "update_card", "delete_card", "move_card", "assign_card", + "add_comment": return true } return false @@ -347,9 +352,66 @@ func validateToolName(name string) error { "update_card": true, "delete_card": true, "move_card": true, "card_history": true, "find_cards": true, "list_users": true, "assign_card": true, + "add_comment": true, "list_comments": true, } if !known[name] { return fmt.Errorf("unknown tool: %s", name) } return nil } + +// toolAddComment appends a comment (card_message) to a card. Accepts either +// {card_id, body, author_id} or {card_id, body, author_username}. Resolves +// the username to an id when needed. +func toolAddComment(db *DB, input json.RawMessage) ToolResult { + var in struct { + CardID string `json:"card_id"` + Body string `json:"body"` + AuthorID string `json:"author_id"` + AuthorUsername string `json:"author_username"` + } + if err := json.Unmarshal(input, &in); err != nil { + return errResult(err) + } + if in.CardID == "" { + return errMsg("card_id required") + } + if strings.TrimSpace(in.Body) == "" { + return errMsg("body required") + } + authorID := strings.TrimSpace(in.AuthorID) + if authorID == "" { + if in.AuthorUsername == "" { + return errMsg("author_id or author_username required") + } + u, _, err := db.GetUserByUsername(in.AuthorUsername) + if err != nil { + return errResult(fmt.Errorf("author_username: %w", err)) + } + authorID = u.ID + } + m, err := db.CreateCardMessage(in.CardID, authorID, in.Body) + if err != nil { + return errResult(err) + } + return okResult(m) +} + +// toolListComments returns every comment (card_message) attached to a card +// sorted by created_at ascending. +func toolListComments(db *DB, input json.RawMessage) ToolResult { + var in struct { + CardID string `json:"card_id"` + } + if err := json.Unmarshal(input, &in); err != nil { + return errResult(err) + } + if in.CardID == "" { + return errMsg("card_id required") + } + msgs, err := db.ListCardMessages(in.CardID) + if err != nil { + return errResult(err) + } + return okResult(msgs) +}