1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package note
6
7import (
8 "context"
9 "fmt"
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// UpdateToolName is the name of the update note tool.
18const UpdateToolName = "update_note"
19
20// UpdateToolDescription describes the update note tool for LLMs.
21const UpdateToolDescription = `Updates an existing note in Lunatask.
22
23Required:
24- id: Note UUID or lunatask://note/... deep link
25
26Optional:
27- name: New note title
28- notebook_id: Move to notebook (UUID from lunatask://notebooks)
29- content: Replace content (Markdown)
30- date: Note date (YYYY-MM-DD or natural language)
31
32Only provided fields are modified; other fields remain unchanged.`
33
34// UpdateInput is the input schema for updating a note.
35type UpdateInput struct {
36 ID string `json:"id" jsonschema:"required"`
37 Name *string `json:"name,omitempty"`
38 NotebookID *string `json:"notebook_id,omitempty"`
39 Content *string `json:"content,omitempty"`
40 Date *string `json:"date,omitempty"`
41}
42
43// UpdateOutput is the output schema for updating a note.
44type UpdateOutput struct {
45 DeepLink string `json:"deep_link"`
46}
47
48// HandleUpdate updates an existing note.
49func (h *Handler) HandleUpdate(
50 ctx context.Context,
51 _ *mcp.CallToolRequest,
52 input UpdateInput,
53) (*mcp.CallToolResult, UpdateOutput, error) {
54 id, err := parseReference(input.ID)
55 if err != nil {
56 return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
57 }
58
59 if input.NotebookID != nil {
60 if err := lunatask.ValidateUUID(*input.NotebookID); err != nil {
61 return shared.ErrorResult("invalid notebook_id: expected UUID"), UpdateOutput{}, nil
62 }
63 }
64
65 builder := h.client.NewNoteUpdate(id)
66
67 if input.Name != nil {
68 builder.WithName(*input.Name)
69 }
70
71 if input.NotebookID != nil {
72 builder.InNotebook(*input.NotebookID)
73 }
74
75 if input.Content != nil {
76 builder.WithContent(*input.Content)
77 }
78
79 if input.Date != nil {
80 date, err := dateutil.Parse(*input.Date)
81 if err != nil {
82 return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
83 }
84
85 builder.OnDate(date)
86 }
87
88 note, err := builder.Update(ctx)
89 if err != nil {
90 return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
91 }
92
93 deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceNote, note.ID)
94
95 return &mcp.CallToolResult{
96 Content: []mcp.Content{&mcp.TextContent{
97 Text: "Note updated: " + deepLink,
98 }},
99 }, UpdateOutput{DeepLink: deepLink}, nil
100}
101
102// parseReference extracts UUID from either a raw UUID or a lunatask:// deep link.
103func parseReference(ref string) (string, error) {
104 _, id, err := lunatask.ParseReference(ref)
105 if err != nil {
106 return "", fmt.Errorf("invalid ID: %w", err)
107 }
108
109 return id, nil
110}