track.go

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5// Package habit provides MCP tools for Lunatask habit operations.
 6package habit
 7
 8import (
 9	"context"
10
11	"git.secluded.site/go-lunatask"
12	"git.secluded.site/lune/internal/dateutil"
13	"git.secluded.site/lune/internal/mcp/shared"
14	"github.com/modelcontextprotocol/go-sdk/mcp"
15)
16
17// TrackToolName is the name of the track habit tool.
18const TrackToolName = "track_habit"
19
20// TrackToolDescription describes the track habit tool for LLMs.
21const TrackToolDescription = `Records that a habit was performed on a specific date.
22
23Required:
24- habit_id: Habit UUID (get from lunatask://habits resource)
25
26Optional:
27- performed_on: Date performed (YYYY-MM-DD or natural language, default: today)
28
29Use the lunatask://habits resource to discover valid habit IDs.`
30
31// TrackInput is the input schema for tracking a habit.
32type TrackInput struct {
33	HabitID     string  `json:"habit_id"               jsonschema:"required"`
34	PerformedOn *string `json:"performed_on,omitempty"`
35}
36
37// TrackOutput is the output schema for tracking a habit.
38type TrackOutput struct {
39	Success     bool   `json:"success"`
40	HabitID     string `json:"habit_id"`
41	PerformedOn string `json:"performed_on"`
42}
43
44// Handler handles habit-related MCP tool requests.
45type Handler struct {
46	client *lunatask.Client
47	habits []shared.HabitProvider
48}
49
50// NewHandler creates a new habit handler.
51func NewHandler(accessToken string, habits []shared.HabitProvider) *Handler {
52	return &Handler{
53		client: lunatask.NewClient(accessToken, lunatask.UserAgent("lune-mcp/1.0")),
54		habits: habits,
55	}
56}
57
58// HandleTrack records a habit activity.
59func (h *Handler) HandleTrack(
60	ctx context.Context,
61	_ *mcp.CallToolRequest,
62	input TrackInput,
63) (*mcp.CallToolResult, TrackOutput, error) {
64	if err := lunatask.ValidateUUID(input.HabitID); err != nil {
65		return shared.ErrorResult("invalid habit_id: expected UUID"), TrackOutput{}, nil
66	}
67
68	dateStr := ""
69	if input.PerformedOn != nil {
70		dateStr = *input.PerformedOn
71	}
72
73	performedOn, err := dateutil.Parse(dateStr)
74	if err != nil {
75		return shared.ErrorResult(err.Error()), TrackOutput{}, nil
76	}
77
78	req := &lunatask.TrackHabitActivityRequest{
79		PerformedOn: performedOn,
80	}
81
82	_, err = h.client.TrackHabitActivity(ctx, input.HabitID, req)
83	if err != nil {
84		return shared.ErrorResult(err.Error()), TrackOutput{}, nil
85	}
86
87	return nil, TrackOutput{
88		Success:     true,
89		HabitID:     input.HabitID,
90		PerformedOn: performedOn.Format("2006-01-02"),
91	}, nil
92}