package tools import ( "context" "strings" "github.com/google/uuid" "github.com/modelcontextprotocol/go-sdk/mcp" "git.warky.dev/wdevs/amcs/internal/ai" "git.warky.dev/wdevs/amcs/internal/config" "git.warky.dev/wdevs/amcs/internal/metadata" "git.warky.dev/wdevs/amcs/internal/session" "git.warky.dev/wdevs/amcs/internal/store" thoughttypes "git.warky.dev/wdevs/amcs/internal/types" ) // EmbeddingQueuer queues a thought for background embedding generation. type EmbeddingQueuer interface { QueueThought(ctx context.Context, id uuid.UUID, content string) } // MetadataQueuer queues a thought for background metadata retry. Both // MetadataRetryer and EnrichmentRetryer satisfy this. type MetadataQueuer interface { QueueThought(id uuid.UUID) } type CaptureTool struct { store *store.DB embeddings *ai.EmbeddingRunner capture config.CaptureConfig sessions *session.ActiveProjects retryer MetadataQueuer embedRetryer EmbeddingQueuer } type CaptureInput struct { Content string `json:"content" jsonschema:"the thought or note to capture"` Project string `json:"project,omitempty" jsonschema:"optional project name or id to associate with the thought"` } type CaptureOutput struct { Thought thoughttypes.Thought `json:"thought"` } func NewCaptureTool(db *store.DB, embeddings *ai.EmbeddingRunner, capture config.CaptureConfig, sessions *session.ActiveProjects, retryer MetadataQueuer, embedRetryer EmbeddingQueuer) *CaptureTool { return &CaptureTool{store: db, embeddings: embeddings, capture: capture, sessions: sessions, retryer: retryer, embedRetryer: embedRetryer} } func (t *CaptureTool) Handle(ctx context.Context, req *mcp.CallToolRequest, in CaptureInput) (*mcp.CallToolResult, CaptureOutput, error) { content := strings.TrimSpace(in.Content) if content == "" { return nil, CaptureOutput{}, errRequiredField("content") } project, err := resolveProject(ctx, t.store, t.sessions, req, in.Project, false) if err != nil { return nil, CaptureOutput{}, err } rawMetadata := metadata.Fallback(t.capture) rawMetadata.MetadataStatus = metadata.MetadataStatusPending thought := thoughttypes.Thought{ Content: content, Metadata: rawMetadata, } if project != nil { thought.ProjectID = &project.ID } created, err := t.store.InsertThought(ctx, thought, t.embeddings.PrimaryModel()) if err != nil { return nil, CaptureOutput{}, err } if project != nil { _ = t.store.TouchProject(ctx, project.ID) } if t.retryer != nil { t.retryer.QueueThought(created.ID) } if t.embedRetryer != nil { t.embedRetryer.QueueThought(ctx, created.ID, content) } return nil, CaptureOutput{Thought: created}, nil }