1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5// Package journal provides MCP tools for Lunatask journal operations.
6package journal
7
8import (
9 "context"
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// CreateToolName is the name of the create journal entry tool.
18const CreateToolName = "add_journal_entry"
19
20// CreateToolDescription describes the create journal entry tool for LLMs.
21const CreateToolDescription = `Creates a journal entry in Lunatask for daily reflection.
22
23Optional:
24- content: Markdown content for the entry
25- name: Entry title (defaults to weekday name if omitted)
26- date: Entry date (YYYY-MM-DD or natural language, default: today)
27
28Entries are date-keyed. If no date is provided, uses today.
29Content supports Markdown formatting.
30
31Common uses:
32- End-of-day summaries
33- Reflection on completed work
34- Recording thoughts or learnings`
35
36// CreateInput is the input schema for creating a journal entry.
37type CreateInput struct {
38 Content *string `json:"content,omitempty"`
39 Name *string `json:"name,omitempty"`
40 Date *string `json:"date,omitempty"`
41}
42
43// CreateOutput is the output schema for creating a journal entry.
44type CreateOutput struct {
45 ID string `json:"id"`
46 Date string `json:"date"`
47}
48
49// Handler handles journal-related MCP tool requests.
50type Handler struct {
51 client *lunatask.Client
52}
53
54// NewHandler creates a new journal handler.
55func NewHandler(accessToken string) *Handler {
56 return &Handler{
57 client: lunatask.NewClient(accessToken, lunatask.UserAgent("lune-mcp/1.0")),
58 }
59}
60
61// HandleCreate creates a new journal entry.
62func (h *Handler) HandleCreate(
63 ctx context.Context,
64 _ *mcp.CallToolRequest,
65 input CreateInput,
66) (*mcp.CallToolResult, CreateOutput, error) {
67 dateStr := ""
68 if input.Date != nil {
69 dateStr = *input.Date
70 }
71
72 date, err := dateutil.Parse(dateStr)
73 if err != nil {
74 return shared.ErrorResult(err.Error()), CreateOutput{}, nil
75 }
76
77 builder := h.client.NewJournalEntry(date)
78
79 if input.Content != nil {
80 builder.WithContent(*input.Content)
81 }
82
83 if input.Name != nil {
84 builder.WithName(*input.Name)
85 }
86
87 entry, err := builder.Create(ctx)
88 if err != nil {
89 return shared.ErrorResult(err.Error()), CreateOutput{}, nil
90 }
91
92 formattedDate := date.Format("2006-01-02")
93
94 return &mcp.CallToolResult{
95 Content: []mcp.Content{&mcp.TextContent{
96 Text: "Journal entry created for " + formattedDate,
97 }},
98 }, CreateOutput{
99 ID: entry.ID,
100 Date: formattedDate,
101 }, nil
102}