update.go

  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}