package tools import ( "context" "strconv" "strings" "github.com/modelcontextprotocol/go-sdk/mcp" "git.warky.dev/wdevs/amcs/internal/store" thoughttypes "git.warky.dev/wdevs/amcs/internal/types" ) type ThoughtLearningLinksTool struct { store *store.DB } type LinkThoughtLearningInput struct { ThoughtID string `json:"thought_id" jsonschema:"UUID of the thought to link"` LearningID int64 `json:"learning_id" jsonschema:"numeric id of the learning to link"` Relation string `json:"relation,omitempty" jsonschema:"relationship label, e.g. source, derived_from, related; defaults to source"` } type LinkThoughtLearningOutput struct { Linked bool `json:"linked"` } type UnlinkThoughtLearningInput struct { ThoughtID string `json:"thought_id" jsonschema:"UUID of the thought"` LearningID int64 `json:"learning_id" jsonschema:"numeric id of the learning"` } type UnlinkThoughtLearningOutput struct { Unlinked bool `json:"unlinked"` } type GetThoughtLearningsInput struct { ThoughtID string `json:"thought_id" jsonschema:"UUID of the thought"` } type GetThoughtLearningsOutput struct { Learnings []thoughttypes.LinkedLearning `json:"learnings"` } type GetLearningThoughtsInput struct { LearningID int64 `json:"learning_id" jsonschema:"numeric id of the learning"` } type GetLearningThoughtsOutput struct { Thoughts []thoughttypes.LinkedLearningThought `json:"thoughts"` } func NewThoughtLearningLinksTool(db *store.DB) *ThoughtLearningLinksTool { return &ThoughtLearningLinksTool{store: db} } func (t *ThoughtLearningLinksTool) Link(ctx context.Context, _ *mcp.CallToolRequest, in LinkThoughtLearningInput) (*mcp.CallToolResult, LinkThoughtLearningOutput, error) { if err := t.ensureConfigured(); err != nil { return nil, LinkThoughtLearningOutput{}, err } thoughtGUID, err := parseUUID(in.ThoughtID) if err != nil { return nil, LinkThoughtLearningOutput{}, err } if in.LearningID <= 0 { return nil, LinkThoughtLearningOutput{}, errRequiredField("learning_id") } relation := strings.TrimSpace(in.Relation) if relation == "" { relation = "source" } if _, err := t.store.GetThought(ctx, thoughtGUID); err != nil { return nil, LinkThoughtLearningOutput{}, errEntityNotFound("thought", "thought_id", in.ThoughtID) } if _, err := t.store.GetLearning(ctx, in.LearningID); err != nil { return nil, LinkThoughtLearningOutput{}, errEntityNotFound("learning", "learning_id", strconv.FormatInt(in.LearningID, 10)) } if err := t.store.LinkThoughtLearning(ctx, thoughtGUID, in.LearningID, relation); err != nil { return nil, LinkThoughtLearningOutput{}, err } return nil, LinkThoughtLearningOutput{Linked: true}, nil } func (t *ThoughtLearningLinksTool) Unlink(ctx context.Context, _ *mcp.CallToolRequest, in UnlinkThoughtLearningInput) (*mcp.CallToolResult, UnlinkThoughtLearningOutput, error) { if err := t.ensureConfigured(); err != nil { return nil, UnlinkThoughtLearningOutput{}, err } thoughtGUID, err := parseUUID(in.ThoughtID) if err != nil { return nil, UnlinkThoughtLearningOutput{}, err } if in.LearningID <= 0 { return nil, UnlinkThoughtLearningOutput{}, errRequiredField("learning_id") } if err := t.store.UnlinkThoughtLearning(ctx, thoughtGUID, in.LearningID); err != nil { return nil, UnlinkThoughtLearningOutput{}, err } return nil, UnlinkThoughtLearningOutput{Unlinked: true}, nil } func (t *ThoughtLearningLinksTool) GetThoughtLearnings(ctx context.Context, _ *mcp.CallToolRequest, in GetThoughtLearningsInput) (*mcp.CallToolResult, GetThoughtLearningsOutput, error) { if err := t.ensureConfigured(); err != nil { return nil, GetThoughtLearningsOutput{}, err } thoughtGUID, err := parseUUID(in.ThoughtID) if err != nil { return nil, GetThoughtLearningsOutput{}, err } if _, err := t.store.GetThought(ctx, thoughtGUID); err != nil { return nil, GetThoughtLearningsOutput{}, errEntityNotFound("thought", "thought_id", in.ThoughtID) } learnings, err := t.store.GetLinkedLearnings(ctx, thoughtGUID) if err != nil { return nil, GetThoughtLearningsOutput{}, err } return nil, GetThoughtLearningsOutput{Learnings: learnings}, nil } func (t *ThoughtLearningLinksTool) GetLearningThoughts(ctx context.Context, _ *mcp.CallToolRequest, in GetLearningThoughtsInput) (*mcp.CallToolResult, GetLearningThoughtsOutput, error) { if err := t.ensureConfigured(); err != nil { return nil, GetLearningThoughtsOutput{}, err } if in.LearningID <= 0 { return nil, GetLearningThoughtsOutput{}, errRequiredField("learning_id") } if _, err := t.store.GetLearning(ctx, in.LearningID); err != nil { return nil, GetLearningThoughtsOutput{}, errEntityNotFound("learning", "learning_id", strconv.FormatInt(in.LearningID, 10)) } thoughts, err := t.store.GetLinkedThoughtsForLearning(ctx, in.LearningID) if err != nil { return nil, GetLearningThoughtsOutput{}, err } return nil, GetLearningThoughtsOutput{Thoughts: thoughts}, nil } func (t *ThoughtLearningLinksTool) ensureConfigured() error { if t == nil || t.store == nil { return errInvalidInput("thought learning links tool is not configured") } return nil }