diff --git a/internal/mcp/tools/task/create.go b/internal/mcp/tools/task/create.go index a99a324d62638529a1972ce636d9541ac504cc67..620ac986aea146fcb50c02e0247d3d68f4f3debb 100644 --- a/internal/mcp/tools/task/create.go +++ b/internal/mcp/tools/task/create.go @@ -54,7 +54,6 @@ type CreateInput struct { // CreateOutput is the output schema for creating a task. type CreateOutput struct { - ID string `json:"id"` DeepLink string `json:"deep_link"` } @@ -108,10 +107,11 @@ func (h *Handler) HandleCreate( deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID) - return nil, CreateOutput{ - ID: task.ID, - DeepLink: deepLink, - }, nil + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{ + Text: "Task created: " + deepLink, + }}, + }, CreateOutput{DeepLink: deepLink}, nil } //nolint:cyclop,funlen diff --git a/internal/mcp/tools/task/delete.go b/internal/mcp/tools/task/delete.go index 64502cf2b830b4c373bf5e63bc31d9037cd4b1a7..2a3ce62fd3b290c5d1fc7d518eb1ff3b49301405 100644 --- a/internal/mcp/tools/task/delete.go +++ b/internal/mcp/tools/task/delete.go @@ -30,8 +30,8 @@ type DeleteInput struct { // DeleteOutput is the output schema for deleting a task. type DeleteOutput struct { - Success bool `json:"success"` - ID string `json:"id"` + Success bool `json:"success"` + DeepLink string `json:"deep_link"` } // HandleDelete deletes a task. @@ -45,12 +45,15 @@ func (h *Handler) HandleDelete( return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"), DeleteOutput{}, nil } + deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, id) + if _, err := h.client.DeleteTask(ctx, id); err != nil { return shared.ErrorResult(err.Error()), DeleteOutput{}, nil } - return nil, DeleteOutput{ - Success: true, - ID: id, - }, nil + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{ + Text: "Task deleted: " + deepLink, + }}, + }, DeleteOutput{Success: true, DeepLink: deepLink}, nil } diff --git a/internal/mcp/tools/task/list.go b/internal/mcp/tools/task/list.go index c1c588ef49ce41696e3d344388d78289690fc332..5066e0d5657fd98e4539c43471c418b40f17dfca 100644 --- a/internal/mcp/tools/task/list.go +++ b/internal/mcp/tools/task/list.go @@ -6,6 +6,8 @@ package task import ( "context" + "fmt" + "strings" "time" "git.secluded.site/go-lunatask" @@ -44,7 +46,6 @@ type ListOutput struct { // Summary represents a task in list output. type Summary struct { - ID string `json:"id"` DeepLink string `json:"deep_link"` Status *string `json:"status,omitempty"` Priority *int `json:"priority,omitempty"` @@ -90,11 +91,14 @@ func (h *Handler) HandleList( filtered := lunatask.FilterTasks(tasks, opts) summaries := buildSummaries(filtered) - - return nil, ListOutput{ - Tasks: summaries, - Count: len(summaries), - }, nil + text := formatListText(summaries) + + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{Text: text}}, + }, ListOutput{ + Tasks: summaries, + Count: len(summaries), + }, nil } func buildSummaries(tasks []lunatask.Task) []Summary { @@ -102,7 +106,6 @@ func buildSummaries(tasks []lunatask.Task) []Summary { for _, task := range tasks { summary := Summary{ - ID: task.ID, CreatedAt: task.CreatedAt.Format(time.RFC3339), AreaID: task.AreaID, GoalID: task.GoalID, @@ -130,3 +133,26 @@ func buildSummaries(tasks []lunatask.Task) []Summary { return summaries } + +func formatListText(summaries []Summary) string { + if len(summaries) == 0 { + return "No tasks found." + } + + var builder strings.Builder + + builder.WriteString(fmt.Sprintf("Found %d task(s):\n", len(summaries))) + + for _, summary := range summaries { + status := "unknown" + if summary.Status != nil { + status = *summary.Status + } + + builder.WriteString(fmt.Sprintf("- %s (%s)\n", summary.DeepLink, status)) + } + + builder.WriteString("\nUse show_task for full details.") + + return builder.String() +} diff --git a/internal/mcp/tools/task/show.go b/internal/mcp/tools/task/show.go index 6f90f0090ed67dc8b2f05c825d320ae67639e659..12d4195b5a2bbe7014fb91b8859be1f42eff03db 100644 --- a/internal/mcp/tools/task/show.go +++ b/internal/mcp/tools/task/show.go @@ -6,6 +6,8 @@ package task import ( "context" + "fmt" + "strings" "time" "git.secluded.site/go-lunatask" @@ -32,7 +34,6 @@ type ShowInput struct { // ShowOutput is the output schema for showing a task. type ShowOutput struct { - ID string `json:"id"` DeepLink string `json:"deep_link"` Status *string `json:"status,omitempty"` Priority *int `json:"priority,omitempty"` @@ -64,7 +65,6 @@ func (h *Handler) HandleShow( } output := ShowOutput{ - ID: task.ID, CreatedAt: task.CreatedAt.Format(time.RFC3339), UpdatedAt: task.UpdatedAt.Format(time.RFC3339), AreaID: task.AreaID, @@ -104,5 +104,59 @@ func (h *Handler) HandleShow( output.Urgent = &urgent } - return nil, output, nil + text := formatShowText(output) + + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{Text: text}}, + }, output, nil +} + +func formatShowText(output ShowOutput) string { + var builder strings.Builder + + builder.WriteString(fmt.Sprintf("Task: %s\n", output.DeepLink)) + writeOptionalField(&builder, "Status", output.Status) + writeOptionalIntField(&builder, "Priority", output.Priority) + writeOptionalField(&builder, "Scheduled", output.ScheduledOn) + writeOptionalMinutesField(&builder, "Estimate", output.Estimate) + writeEisenhowerField(&builder, output.Important, output.Urgent) + builder.WriteString(fmt.Sprintf("Created: %s\n", output.CreatedAt)) + builder.WriteString("Updated: " + output.UpdatedAt) + writeOptionalField(&builder, "\nCompleted", output.CompletedAt) + + return builder.String() +} + +func writeOptionalField(builder *strings.Builder, label string, value *string) { + if value != nil { + fmt.Fprintf(builder, "%s: %s\n", label, *value) + } +} + +func writeOptionalIntField(builder *strings.Builder, label string, value *int) { + if value != nil { + fmt.Fprintf(builder, "%s: %d\n", label, *value) + } +} + +func writeOptionalMinutesField(builder *strings.Builder, label string, value *int) { + if value != nil { + fmt.Fprintf(builder, "%s: %d min\n", label, *value) + } +} + +func writeEisenhowerField(builder *strings.Builder, important, urgent *bool) { + var parts []string + + if important != nil && *important { + parts = append(parts, "important") + } + + if urgent != nil && *urgent { + parts = append(parts, "urgent") + } + + if len(parts) > 0 { + fmt.Fprintf(builder, "Eisenhower: %s\n", strings.Join(parts, ", ")) + } } diff --git a/internal/mcp/tools/task/update.go b/internal/mcp/tools/task/update.go index e9fd19e7dcec018268c71907414ecede5e2dfcae..873e7920846eca8df9e04bfa4ae29d2cf045c4a8 100644 --- a/internal/mcp/tools/task/update.go +++ b/internal/mcp/tools/task/update.go @@ -55,7 +55,6 @@ type UpdateInput struct { // UpdateOutput is the output schema for updating a task. type UpdateOutput struct { - ID string `json:"id"` DeepLink string `json:"deep_link"` } @@ -96,10 +95,11 @@ func (h *Handler) HandleUpdate( deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID) - return nil, UpdateOutput{ - ID: task.ID, - DeepLink: deepLink, - }, nil + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{ + Text: "Task updated: " + deepLink, + }}, + }, UpdateOutput{DeepLink: deepLink}, nil } //nolint:cyclop,funlen diff --git a/internal/mcp/tools/timestamp/handler.go b/internal/mcp/tools/timestamp/handler.go index 808a0ba4533de6715cf9ba2fd515a020890c02b3..2102a1df3ce056f88c94f760694775a6967adeda 100644 --- a/internal/mcp/tools/timestamp/handler.go +++ b/internal/mcp/tools/timestamp/handler.go @@ -7,6 +7,7 @@ package timestamp import ( "context" + "fmt" "time" "git.secluded.site/lune/internal/dateutil" @@ -72,9 +73,19 @@ func (h *Handler) Handle( } t := parsed.In(h.timezone) - - return nil, Output{ + output := Output{ Timestamp: t.Format(time.RFC3339), Date: t.Format("2006-01-02"), - }, nil + } + + inputDisplay := input.Date + if inputDisplay == "" { + inputDisplay = "(empty)" + } + + return &mcp.CallToolResult{ + Content: []mcp.Content{&mcp.TextContent{ + Text: fmt.Sprintf("Parsed %q → %s", inputDisplay, output.Timestamp), + }}, + }, output, nil }