handler.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5// Package timeline provides the MCP tool for adding timeline notes to people.
  6package timeline
  7
  8import (
  9	"context"
 10
 11	"git.secluded.site/go-lunatask"
 12	"git.secluded.site/lune/internal/dateutil"
 13	"git.secluded.site/lune/internal/mcp/shared"
 14	"github.com/modelcontextprotocol/go-sdk/mcp"
 15)
 16
 17// ToolName is the name of the add timeline note tool.
 18const ToolName = "add_timeline_note"
 19
 20// ToolDescription describes the add timeline note tool for LLMs.
 21const ToolDescription = `Adds a timeline note to a person's memory timeline in Lunatask.
 22
 23Required:
 24- person_id: Person UUID or lunatask://person/... deep link
 25
 26Optional:
 27- content: Markdown content describing the interaction
 28- date: Date of interaction (YYYY-MM-DD or natural language, default: today)
 29
 30This is append-only — adds to the person's memory timeline.
 31Great for tracking when you last interacted with someone.`
 32
 33// Input is the input schema for adding a timeline note.
 34type Input struct {
 35	PersonID string  `json:"person_id"         jsonschema:"required"`
 36	Content  *string `json:"content,omitempty"`
 37	Date     *string `json:"date,omitempty"`
 38}
 39
 40// Output is the output schema for adding a timeline note.
 41type Output struct {
 42	Success        bool   `json:"success"`
 43	PersonDeepLink string `json:"person_deep_link"`
 44	NoteID         string `json:"note_id"`
 45}
 46
 47// Handler handles timeline note MCP tool requests.
 48type Handler struct {
 49	client *lunatask.Client
 50}
 51
 52// NewHandler creates a new timeline handler.
 53func NewHandler(accessToken string) *Handler {
 54	return &Handler{
 55		client: lunatask.NewClient(accessToken, lunatask.UserAgent("lune-mcp/1.0")),
 56	}
 57}
 58
 59// Handle adds a timeline note to a person.
 60func (h *Handler) Handle(
 61	ctx context.Context,
 62	_ *mcp.CallToolRequest,
 63	input Input,
 64) (*mcp.CallToolResult, Output, error) {
 65	_, personID, err := lunatask.ParseReference(input.PersonID)
 66	if err != nil {
 67		return shared.ErrorResult("invalid person_id: expected UUID or lunatask:// deep link"),
 68			Output{}, nil
 69	}
 70
 71	builder := h.client.NewTimelineNote(personID)
 72
 73	if input.Content != nil {
 74		builder.WithContent(*input.Content)
 75	}
 76
 77	if input.Date != nil {
 78		date, err := dateutil.Parse(*input.Date)
 79		if err != nil {
 80			return shared.ErrorResult(err.Error()), Output{}, nil
 81		}
 82
 83		builder.OnDate(date)
 84	}
 85
 86	note, err := builder.Create(ctx)
 87	if err != nil {
 88		return shared.ErrorResult(err.Error()), Output{}, nil
 89	}
 90
 91	personDeepLink, _ := lunatask.BuildDeepLink(lunatask.ResourcePerson, personID)
 92
 93	dateStr := "today"
 94
 95	if input.Date != nil {
 96		date, _ := dateutil.Parse(*input.Date)
 97		dateStr = date.Format("2006-01-02")
 98	}
 99
100	return &mcp.CallToolResult{
101			Content: []mcp.Content{&mcp.TextContent{
102				Text: "Timeline note added for " + personDeepLink + " on " + dateStr,
103			}},
104		}, Output{
105			Success:        true,
106			PersonDeepLink: personDeepLink,
107			NoteID:         note.ID,
108		}, nil
109}