show.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	"strings"
 11	"time"
 12
 13	"git.secluded.site/go-lunatask"
 14	"git.secluded.site/lune/internal/mcp/shared"
 15	"github.com/modelcontextprotocol/go-sdk/mcp"
 16)
 17
 18// ShowToolName is the name of the show note tool.
 19const ShowToolName = "show_note"
 20
 21// ShowToolDescription describes the show note tool for LLMs.
 22const ShowToolDescription = `Shows metadata for a specific note from Lunatask.
 23
 24Required:
 25- id: Note UUID or lunatask:// deep link
 26
 27Note: Due to end-to-end encryption, note title and content are not available.
 28Only metadata (notebook, date, pinned status, sources) is returned.`
 29
 30// ShowInput is the input schema for showing a note.
 31type ShowInput struct {
 32	ID string `json:"id" jsonschema:"required"`
 33}
 34
 35// ShowSource represents a source reference in the output.
 36type ShowSource struct {
 37	Source   string `json:"source"`
 38	SourceID string `json:"source_id"`
 39}
 40
 41// ShowOutput is the output schema for showing a note.
 42type ShowOutput struct {
 43	DeepLink   string       `json:"deep_link"`
 44	NotebookID *string      `json:"notebook_id,omitempty"`
 45	DateOn     *string      `json:"date_on,omitempty"`
 46	Pinned     bool         `json:"pinned"`
 47	Sources    []ShowSource `json:"sources,omitempty"`
 48	CreatedAt  string       `json:"created_at"`
 49	UpdatedAt  string       `json:"updated_at"`
 50}
 51
 52// HandleShow shows a note's details.
 53func (h *Handler) HandleShow(
 54	ctx context.Context,
 55	_ *mcp.CallToolRequest,
 56	input ShowInput,
 57) (*mcp.CallToolResult, ShowOutput, error) {
 58	_, id, err := lunatask.ParseReference(input.ID)
 59	if err != nil {
 60		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"), ShowOutput{}, nil
 61	}
 62
 63	note, err := h.client.GetNote(ctx, id)
 64	if err != nil {
 65		return shared.ErrorResult(err.Error()), ShowOutput{}, nil
 66	}
 67
 68	output := buildShowOutput(note)
 69	text := formatNoteShowText(output, h.notebooks)
 70
 71	return &mcp.CallToolResult{
 72		Content: []mcp.Content{&mcp.TextContent{Text: text}},
 73	}, output, nil
 74}
 75
 76func buildShowOutput(note *lunatask.Note) ShowOutput {
 77	output := ShowOutput{
 78		NotebookID: note.NotebookID,
 79		Pinned:     note.Pinned,
 80		CreatedAt:  note.CreatedAt.Format(time.RFC3339),
 81		UpdatedAt:  note.UpdatedAt.Format(time.RFC3339),
 82	}
 83
 84	output.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourceNote, note.ID)
 85
 86	if note.DateOn != nil {
 87		s := note.DateOn.Format("2006-01-02")
 88		output.DateOn = &s
 89	}
 90
 91	if len(note.Sources) > 0 {
 92		output.Sources = make([]ShowSource, 0, len(note.Sources))
 93		for _, src := range note.Sources {
 94			output.Sources = append(output.Sources, ShowSource{
 95				Source:   src.Source,
 96				SourceID: src.SourceID,
 97			})
 98		}
 99	}
100
101	return output
102}
103
104func formatNoteShowText(output ShowOutput, notebooks []shared.NotebookProvider) string {
105	var builder strings.Builder
106
107	builder.WriteString(fmt.Sprintf("Note: %s\n", output.DeepLink))
108
109	if output.NotebookID != nil {
110		nbName := *output.NotebookID
111		for _, nb := range notebooks {
112			if nb.ID == *output.NotebookID {
113				nbName = nb.Key
114
115				break
116			}
117		}
118
119		builder.WriteString(fmt.Sprintf("Notebook: %s\n", nbName))
120	}
121
122	if output.DateOn != nil {
123		builder.WriteString(fmt.Sprintf("Date: %s\n", *output.DateOn))
124	}
125
126	if output.Pinned {
127		builder.WriteString("Pinned: yes\n")
128	}
129
130	if len(output.Sources) > 0 {
131		builder.WriteString("Sources:\n")
132
133		for _, src := range output.Sources {
134			builder.WriteString(fmt.Sprintf("  - %s: %s\n", src.Source, src.SourceID))
135		}
136	}
137
138	builder.WriteString(fmt.Sprintf("Created: %s\n", output.CreatedAt))
139	builder.WriteString("Updated: " + output.UpdatedAt)
140
141	return builder.String()
142}