1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package habits provides MCP tools for habit tracking in Lunatask.
6package habits
7
8import (
9 "context"
10 "fmt"
11 "strings"
12
13 "git.secluded.site/go-lunatask"
14 "github.com/mark3labs/mcp-go/mcp"
15
16 "git.sr.ht/~amolith/lunatask-mcp-server/tools/shared"
17)
18
19// Handler handles habit-related MCP tool calls.
20type Handler struct {
21 accessToken string
22 habits []shared.HabitProvider
23}
24
25// NewHandler creates a new habits Handler.
26func NewHandler(accessToken string, habits []shared.HabitProvider) *Handler {
27 return &Handler{
28 accessToken: accessToken,
29 habits: habits,
30 }
31}
32
33// HandleList handles the list_habits_and_activities tool call.
34func (h *Handler) HandleList(
35 _ context.Context,
36 _ mcp.CallToolRequest,
37) (*mcp.CallToolResult, error) {
38 var builder strings.Builder
39
40 for _, habit := range h.habits {
41 fmt.Fprintf(&builder, "- %s: %s\n", habit.GetName(), habit.GetID())
42 }
43
44 return &mcp.CallToolResult{
45 Content: []mcp.Content{
46 mcp.TextContent{
47 Type: "text",
48 Text: builder.String(),
49 },
50 },
51 }, nil
52}
53
54// HandleTrack handles the track_habit_activity tool call.
55//
56//nolint:wrapcheck // ReportError returns nil for error
57func (h *Handler) HandleTrack(
58 ctx context.Context,
59 request mcp.CallToolRequest,
60) (*mcp.CallToolResult, error) {
61 habitID, ok := request.Params.Arguments["habit_id"].(string)
62 if !ok || habitID == "" {
63 return shared.ReportError("Missing or invalid required argument: habit_id")
64 }
65
66 performedOnStr, ok := request.Params.Arguments["performed_on"].(string)
67 if !ok || performedOnStr == "" {
68 return shared.ReportError("Missing or invalid required argument: performed_on")
69 }
70
71 performedOn, err := lunatask.ParseDate(performedOnStr)
72 if err != nil {
73 return shared.ReportError(fmt.Sprintf(
74 "Invalid format for performed_on: '%s'. Must be YYYY-MM-DD.",
75 performedOnStr,
76 ))
77 }
78
79 client := lunatask.NewClient(h.accessToken)
80 habitRequest := &lunatask.TrackHabitActivityRequest{
81 PerformedOn: performedOn,
82 }
83
84 resp, err := client.TrackHabitActivity(ctx, habitID, habitRequest)
85 if err != nil {
86 return shared.ReportError(fmt.Sprintf("Failed to track habit activity: %v", err))
87 }
88
89 return &mcp.CallToolResult{
90 Content: []mcp.Content{
91 mcp.TextContent{
92 Type: "text",
93 Text: fmt.Sprintf(
94 "Habit activity tracked successfully. Status: %s, Message: %s",
95 resp.Status,
96 resp.Message,
97 ),
98 },
99 },
100 }, nil
101}