Files
amcs/internal/tools/helpers.go
Hein 66370a7f0e feat(tools): implement CRUD operations for thoughts and projects
* Add tools for creating, retrieving, updating, and deleting thoughts.
* Implement project management tools for creating and listing projects.
* Introduce linking functionality between thoughts.
* Add search and recall capabilities for thoughts based on semantic queries.
* Implement statistics and summarization tools for thought analysis.
* Create database migrations for thoughts, projects, and links.
* Add helper functions for UUID parsing and project resolution.
2026-03-24 15:38:59 +02:00

88 lines
2.3 KiB
Go

package tools
import (
"context"
"fmt"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/session"
"git.warky.dev/wdevs/amcs/internal/store"
thoughttypes "git.warky.dev/wdevs/amcs/internal/types"
)
func parseUUID(id string) (uuid.UUID, error) {
parsed, err := uuid.Parse(strings.TrimSpace(id))
if err != nil {
return uuid.Nil, fmt.Errorf("invalid id %q: %w", id, err)
}
return parsed, nil
}
func sessionID(req *mcp.CallToolRequest) (string, error) {
if req == nil || req.Session == nil || req.Session.ID() == "" {
return "", fmt.Errorf("tool requires an MCP session")
}
return req.Session.ID(), nil
}
func resolveProject(ctx context.Context, db *store.DB, sessions *session.ActiveProjects, req *mcp.CallToolRequest, raw string, required bool) (*thoughttypes.Project, error) {
projectRef := strings.TrimSpace(raw)
if projectRef == "" && sessions != nil && req != nil && req.Session != nil {
if activeID, ok := sessions.Get(req.Session.ID()); ok {
project, err := db.GetProject(ctx, activeID.String())
if err == nil {
return &project, nil
}
if err != pgx.ErrNoRows {
return nil, err
}
}
}
if projectRef == "" {
if required {
return nil, fmt.Errorf("project is required")
}
return nil, nil
}
project, err := db.GetProject(ctx, projectRef)
if err != nil {
if err == pgx.ErrNoRows {
return nil, fmt.Errorf("project %q not found", projectRef)
}
return nil, err
}
return &project, nil
}
func formatContextBlock(header string, lines []string) string {
if len(lines) == 0 {
return header + "\n\nNo matching thoughts."
}
return header + "\n\n" + strings.Join(lines, "\n\n")
}
func thoughtContextLine(index int, content string, metadata thoughttypes.ThoughtMetadata, similarity float64) string {
label := fmt.Sprintf("%d. %s", index+1, strings.TrimSpace(content))
parts := make([]string, 0, 3)
if len(metadata.Topics) > 0 {
parts = append(parts, "topics="+strings.Join(metadata.Topics, ", "))
}
if metadata.Type != "" {
parts = append(parts, "type="+metadata.Type)
}
if similarity > 0 {
parts = append(parts, fmt.Sprintf("similarity=%.3f", similarity))
}
if len(parts) == 0 {
return label
}
return label + "\n[" + strings.Join(parts, " | ") + "]"
}