handler.go

 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}