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