show.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package person
  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 person tool.
 19const ShowToolName = "show_person"
 20
 21// ShowToolDescription describes the show person tool for LLMs.
 22const ShowToolDescription = `Shows metadata for a specific person from Lunatask.
 23
 24Required:
 25- id: Person UUID or lunatask:// deep link
 26
 27Note: Due to end-to-end encryption, person name is not available.
 28Only metadata (relationship strength, sources) is returned.`
 29
 30// ShowInput is the input schema for showing a person.
 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 person.
 42type ShowOutput struct {
 43	DeepLink             string       `json:"deep_link"`
 44	RelationshipStrength *string      `json:"relationship_strength,omitempty"`
 45	Sources              []ShowSource `json:"sources,omitempty"`
 46	CreatedAt            string       `json:"created_at"`
 47	UpdatedAt            string       `json:"updated_at"`
 48}
 49
 50// HandleShow shows a person's details.
 51func (h *Handler) HandleShow(
 52	ctx context.Context,
 53	_ *mcp.CallToolRequest,
 54	input ShowInput,
 55) (*mcp.CallToolResult, ShowOutput, error) {
 56	_, id, err := lunatask.ParseReference(input.ID)
 57	if err != nil {
 58		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"), ShowOutput{}, nil
 59	}
 60
 61	person, err := h.client.GetPerson(ctx, id)
 62	if err != nil {
 63		return shared.ErrorResult(err.Error()), ShowOutput{}, nil
 64	}
 65
 66	output := buildShowOutput(person)
 67	text := formatPersonShowText(output)
 68
 69	return &mcp.CallToolResult{
 70		Content: []mcp.Content{&mcp.TextContent{Text: text}},
 71	}, output, nil
 72}
 73
 74func buildShowOutput(person *lunatask.Person) ShowOutput {
 75	output := ShowOutput{
 76		CreatedAt: person.CreatedAt.Format(time.RFC3339),
 77		UpdatedAt: person.UpdatedAt.Format(time.RFC3339),
 78	}
 79
 80	output.DeepLink, _ = lunatask.BuildDeepLink(lunatask.ResourcePerson, person.ID)
 81
 82	if person.RelationshipStrength != nil {
 83		s := string(*person.RelationshipStrength)
 84		output.RelationshipStrength = &s
 85	}
 86
 87	if len(person.Sources) > 0 {
 88		output.Sources = make([]ShowSource, 0, len(person.Sources))
 89		for _, src := range person.Sources {
 90			output.Sources = append(output.Sources, ShowSource{
 91				Source:   src.Source,
 92				SourceID: src.SourceID,
 93			})
 94		}
 95	}
 96
 97	return output
 98}
 99
100func formatPersonShowText(output ShowOutput) string {
101	var builder strings.Builder
102
103	builder.WriteString(fmt.Sprintf("Person: %s\n", output.DeepLink))
104
105	if output.RelationshipStrength != nil {
106		builder.WriteString(fmt.Sprintf("Relationship: %s\n", *output.RelationshipStrength))
107	}
108
109	if len(output.Sources) > 0 {
110		builder.WriteString("Sources:\n")
111
112		for _, src := range output.Sources {
113			builder.WriteString(fmt.Sprintf("  - %s: %s\n", src.Source, src.SourceID))
114		}
115	}
116
117	builder.WriteString(fmt.Sprintf("Created: %s\n", output.CreatedAt))
118	builder.WriteString("Updated: " + output.UpdatedAt)
119
120	return builder.String()
121}