handler.go

  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 = `Configured Lunatask areas and goals.
 22
 23Each area contains: id, name, key, workflow info, and goals.
 24Use area key or id in resource templates like lunatask://area/{area}/today.`
 25
 26// Handler handles area resource requests.
 27type Handler struct {
 28	areas []shared.AreaProvider
 29}
 30
 31// NewHandler creates a new areas resource handler.
 32func NewHandler(areas []shared.AreaProvider) *Handler {
 33	return &Handler{areas: areas}
 34}
 35
 36// areaInfo represents an area in the resource response.
 37type areaInfo struct {
 38	ID                  string     `json:"id"`
 39	Name                string     `json:"name"`
 40	Key                 string     `json:"key"`
 41	Workflow            string     `json:"workflow"`
 42	WorkflowDescription string     `json:"workflow_description"`
 43	ValidStatuses       []string   `json:"valid_statuses"`
 44	UsesMotivation      bool       `json:"uses_motivation"`
 45	UsesEisenhower      bool       `json:"uses_eisenhower"`
 46	UsesScheduling      bool       `json:"uses_scheduling"`
 47	UsesPriority        bool       `json:"uses_priority"`
 48	Goals               []goalInfo `json:"goals"`
 49}
 50
 51// goalInfo represents a goal in the resource response.
 52type goalInfo struct {
 53	ID   string `json:"id"`
 54	Name string `json:"name"`
 55	Key  string `json:"key"`
 56}
 57
 58// HandleRead returns the configured areas and goals.
 59func (h *Handler) HandleRead(
 60	_ context.Context,
 61	_ *mcp.ReadResourceRequest,
 62) (*mcp.ReadResourceResult, error) {
 63	areasInfo := make([]areaInfo, 0, len(h.areas))
 64
 65	for _, area := range h.areas {
 66		goals := make([]goalInfo, 0, len(area.Goals))
 67		for _, g := range area.Goals {
 68			goals = append(goals, goalInfo{
 69				ID:   g.ID,
 70				Name: g.Name,
 71				Key:  g.Key,
 72			})
 73		}
 74
 75		validStatuses := make([]string, 0, len(area.Workflow.ValidStatuses()))
 76		for _, s := range area.Workflow.ValidStatuses() {
 77			validStatuses = append(validStatuses, string(s))
 78		}
 79
 80		areasInfo = append(areasInfo, areaInfo{
 81			ID:                  area.ID,
 82			Name:                area.Name,
 83			Key:                 area.Key,
 84			Workflow:            string(area.Workflow),
 85			WorkflowDescription: area.Workflow.Description(),
 86			ValidStatuses:       validStatuses,
 87			UsesMotivation:      area.Workflow.UsesMotivation(),
 88			UsesEisenhower:      area.Workflow.UsesEisenhower(),
 89			UsesScheduling:      area.Workflow.UsesScheduling(),
 90			UsesPriority:        area.Workflow.UsesPriority(),
 91			Goals:               goals,
 92		})
 93	}
 94
 95	data, err := json.MarshalIndent(areasInfo, "", "  ")
 96	if err != nil {
 97		return nil, fmt.Errorf("marshaling areas: %w", err)
 98	}
 99
100	return &mcp.ReadResourceResult{
101		Contents: []*mcp.ResourceContents{{
102			URI:      ResourceURI,
103			MIMEType: "application/json",
104			Text:     string(data),
105		}},
106	}, nil
107}