1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package areas provides the MCP resource handler for Lunatask areas and goals.
6package areas
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 areas resource.
18const ResourceURI = "lunatask://areas"
19
20// ResourceDescription describes the areas resource for LLMs.
21const ResourceDescription = `Lists all configured Lunatask areas and their goals.
22
23Each area represents a life domain (e.g., Work, Personal, Health) and contains:
24- id: UUID to use when creating tasks in this area
25- name: Human-readable area name
26- key: Short alias for CLI usage
27- goals: List of goals within the area, each with id, name, and key
28
29Use this resource to discover valid area and goal IDs before creating or updating tasks.`
30
31// Handler handles area resource requests.
32type Handler struct {
33 areas []shared.AreaProvider
34}
35
36// NewHandler creates a new areas resource handler.
37func NewHandler(areas []shared.AreaProvider) *Handler {
38 return &Handler{areas: areas}
39}
40
41// areaInfo represents an area in the resource response.
42type areaInfo struct {
43 ID string `json:"id"`
44 Name string `json:"name"`
45 Key string `json:"key"`
46 Goals []goalInfo `json:"goals"`
47}
48
49// goalInfo represents a goal in the resource response.
50type goalInfo struct {
51 ID string `json:"id"`
52 Name string `json:"name"`
53 Key string `json:"key"`
54}
55
56// HandleRead returns the configured areas and goals.
57func (h *Handler) HandleRead(
58 _ context.Context,
59 _ *mcp.ReadResourceRequest,
60) (*mcp.ReadResourceResult, error) {
61 areasInfo := make([]areaInfo, 0, len(h.areas))
62
63 for _, area := range h.areas {
64 goals := make([]goalInfo, 0, len(area.Goals))
65 for _, g := range area.Goals {
66 goals = append(goals, goalInfo{
67 ID: g.ID,
68 Name: g.Name,
69 Key: g.Key,
70 })
71 }
72
73 areasInfo = append(areasInfo, areaInfo{
74 ID: area.ID,
75 Name: area.Name,
76 Key: area.Key,
77 Goals: goals,
78 })
79 }
80
81 data, err := json.MarshalIndent(areasInfo, "", " ")
82 if err != nil {
83 return nil, fmt.Errorf("marshaling areas: %w", err)
84 }
85
86 return &mcp.ReadResourceResult{
87 Contents: []*mcp.ResourceContents{{
88 URI: ResourceURI,
89 MIMEType: "application/json",
90 Text: string(data),
91 }},
92 }, nil
93}