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- workflow: Task management style (determines which fields are relevant)
28- workflow_description: Human-readable explanation of the workflow
29- valid_statuses: Task statuses valid for this workflow
30- uses_motivation: Whether motivation field (must/should/want) is relevant
31- uses_eisenhower: Whether eisenhower matrix is relevant
32- uses_scheduling: Whether date scheduling is relevant
33- uses_priority: Whether priority field is relevant
34- goals: List of goals within the area
35
36Use workflow information to determine which task fields to set for each area.`
37
38// Handler handles area resource requests.
39type Handler struct {
40 areas []shared.AreaProvider
41}
42
43// NewHandler creates a new areas resource handler.
44func NewHandler(areas []shared.AreaProvider) *Handler {
45 return &Handler{areas: areas}
46}
47
48// areaInfo represents an area in the resource response.
49type areaInfo struct {
50 ID string `json:"id"`
51 Name string `json:"name"`
52 Key string `json:"key"`
53 Workflow string `json:"workflow"`
54 WorkflowDescription string `json:"workflow_description"`
55 ValidStatuses []string `json:"valid_statuses"`
56 UsesMotivation bool `json:"uses_motivation"`
57 UsesEisenhower bool `json:"uses_eisenhower"`
58 UsesScheduling bool `json:"uses_scheduling"`
59 UsesPriority bool `json:"uses_priority"`
60 Goals []goalInfo `json:"goals"`
61}
62
63// goalInfo represents a goal in the resource response.
64type goalInfo struct {
65 ID string `json:"id"`
66 Name string `json:"name"`
67 Key string `json:"key"`
68}
69
70// HandleRead returns the configured areas and goals.
71func (h *Handler) HandleRead(
72 _ context.Context,
73 _ *mcp.ReadResourceRequest,
74) (*mcp.ReadResourceResult, error) {
75 areasInfo := make([]areaInfo, 0, len(h.areas))
76
77 for _, area := range h.areas {
78 goals := make([]goalInfo, 0, len(area.Goals))
79 for _, g := range area.Goals {
80 goals = append(goals, goalInfo{
81 ID: g.ID,
82 Name: g.Name,
83 Key: g.Key,
84 })
85 }
86
87 validStatuses := make([]string, 0, len(area.Workflow.ValidStatuses()))
88 for _, s := range area.Workflow.ValidStatuses() {
89 validStatuses = append(validStatuses, string(s))
90 }
91
92 areasInfo = append(areasInfo, areaInfo{
93 ID: area.ID,
94 Name: area.Name,
95 Key: area.Key,
96 Workflow: string(area.Workflow),
97 WorkflowDescription: area.Workflow.Description(),
98 ValidStatuses: validStatuses,
99 UsesMotivation: area.Workflow.UsesMotivation(),
100 UsesEisenhower: area.Workflow.UsesEisenhower(),
101 UsesScheduling: area.Workflow.UsesScheduling(),
102 UsesPriority: area.Workflow.UsesPriority(),
103 Goals: goals,
104 })
105 }
106
107 data, err := json.MarshalIndent(areasInfo, "", " ")
108 if err != nil {
109 return nil, fmt.Errorf("marshaling areas: %w", err)
110 }
111
112 return &mcp.ReadResourceResult{
113 Contents: []*mcp.ResourceContents{{
114 URI: ResourceURI,
115 MIMEType: "application/json",
116 Text: string(data),
117 }},
118 }, nil
119}