1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package note provides MCP tools for Lunatask note operations.
6package note
7
8import (
9 "context"
10
11 "git.secluded.site/go-lunatask"
12 "git.secluded.site/lune/internal/mcp/shared"
13 "github.com/modelcontextprotocol/go-sdk/mcp"
14)
15
16// CreateToolName is the name of the create note tool.
17const CreateToolName = "create_note"
18
19// CreateToolDescription describes the create note tool for LLMs.
20const CreateToolDescription = `Creates a new note in Lunatask.
21
22Optional:
23- name: Note title
24- notebook_id: Notebook UUID (get from lunatask://notebooks resource)
25- content: Markdown content
26- source: Origin identifier for integrations
27- source_id: Source-specific ID (requires source)
28
29All fields are optional — can create an empty note.
30Returns the deep link to the created note.
31
32Note: If a note with the same source/source_id already exists,
33the API returns a duplicate warning instead of creating a new note.`
34
35// CreateInput is the input schema for creating a note.
36type CreateInput struct {
37 Name *string `json:"name,omitempty"`
38 NotebookID *string `json:"notebook_id,omitempty"`
39 Content *string `json:"content,omitempty"`
40 Source *string `json:"source,omitempty"`
41 SourceID *string `json:"source_id,omitempty"`
42}
43
44// CreateOutput is the output schema for creating a note.
45type CreateOutput struct {
46 DeepLink string `json:"deep_link"`
47}
48
49// Handler handles note-related MCP tool requests.
50type Handler struct {
51 client *lunatask.Client
52 notebooks []shared.NotebookProvider
53}
54
55// NewHandler creates a new note handler.
56func NewHandler(accessToken string, notebooks []shared.NotebookProvider) *Handler {
57 return &Handler{
58 client: lunatask.NewClient(accessToken, lunatask.UserAgent("lune-mcp/1.0")),
59 notebooks: notebooks,
60 }
61}
62
63// HandleCreate creates a new note.
64func (h *Handler) HandleCreate(
65 ctx context.Context,
66 _ *mcp.CallToolRequest,
67 input CreateInput,
68) (*mcp.CallToolResult, CreateOutput, error) {
69 if input.NotebookID != nil {
70 if err := lunatask.ValidateUUID(*input.NotebookID); err != nil {
71 return shared.ErrorResult("invalid notebook_id: expected UUID"), CreateOutput{}, nil
72 }
73 }
74
75 builder := h.client.NewNote()
76
77 if input.Name != nil {
78 builder.WithName(*input.Name)
79 }
80
81 if input.NotebookID != nil {
82 builder.InNotebook(*input.NotebookID)
83 }
84
85 if input.Content != nil {
86 builder.WithContent(*input.Content)
87 }
88
89 if input.Source != nil && input.SourceID != nil {
90 builder.FromSource(*input.Source, *input.SourceID)
91 }
92
93 note, err := builder.Create(ctx)
94 if err != nil {
95 return shared.ErrorResult(err.Error()), CreateOutput{}, nil
96 }
97
98 if note == nil {
99 return &mcp.CallToolResult{
100 Content: []mcp.Content{&mcp.TextContent{
101 Text: "Note already exists (duplicate source)",
102 }},
103 }, CreateOutput{}, nil
104 }
105
106 deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceNote, note.ID)
107
108 return &mcp.CallToolResult{
109 Content: []mcp.Content{&mcp.TextContent{
110 Text: "Note created: " + deepLink,
111 }},
112 }, CreateOutput{DeepLink: deepLink}, nil
113}