1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package habits provides the MCP resource handler for Lunatask habits.
6package habits
7
8import (
9 "context"
10 "encoding/json"
11 "fmt"
12
13 "git.secluded.site/lune/internal/mcp/shared"
14 "github.com/modelcontextprotocol/go-sdk/mcp"
15)
16
17// ResourceURI is the URI for the habits resource.
18const ResourceURI = "lunatask://habits"
19
20// ResourceDescription describes the habits resource for LLMs.
21const ResourceDescription = `Lists all configured Lunatask habits.
22
23Each habit contains:
24- id: UUID to use when tracking habit completion
25- name: Human-readable habit name
26- key: Short alias for CLI usage
27
28Use this resource to discover valid habit IDs before tracking habit activities.`
29
30// Handler handles habit resource requests.
31type Handler struct {
32 habits []shared.HabitProvider
33}
34
35// NewHandler creates a new habits resource handler.
36func NewHandler(habits []shared.HabitProvider) *Handler {
37 return &Handler{habits: habits}
38}
39
40// habitInfo represents a habit in the resource response.
41type habitInfo struct {
42 ID string `json:"id"`
43 Name string `json:"name"`
44 Key string `json:"key"`
45}
46
47// HandleRead returns the configured habits.
48func (h *Handler) HandleRead(
49 _ context.Context,
50 _ *mcp.ReadResourceRequest,
51) (*mcp.ReadResourceResult, error) {
52 habitsInfo := make([]habitInfo, 0, len(h.habits))
53
54 for _, hab := range h.habits {
55 habitsInfo = append(habitsInfo, habitInfo{
56 ID: hab.ID,
57 Name: hab.Name,
58 Key: hab.Key,
59 })
60 }
61
62 data, err := json.MarshalIndent(habitsInfo, "", " ")
63 if err != nil {
64 return nil, fmt.Errorf("marshaling habits: %w", err)
65 }
66
67 return &mcp.ReadResourceResult{
68 Contents: []*mcp.ResourceContents{{
69 URI: ResourceURI,
70 MIMEType: "application/json",
71 Text: string(data),
72 }},
73 }, nil
74}