feat(tools): add maintenance and meal planning tools with CRUD operations

- Implement maintenance tool for adding, logging, and retrieving tasks
- Create meals tool for managing recipes, meal plans, and shopping lists
- Introduce reparse metadata tool for updating thought metadata
- Add household knowledge, home maintenance, family calendar, meal planning, and professional CRM database migrations
- Grant necessary permissions for new database tables
This commit is contained in:
2026-03-26 23:29:03 +02:00
parent b74d63c543
commit 0eb6ac7ee5
25 changed files with 2910 additions and 10 deletions

212
internal/tools/calendar.go Normal file
View File

@@ -0,0 +1,212 @@
package tools
import (
"context"
"strings"
"time"
"github.com/google/uuid"
"github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/store"
ext "git.warky.dev/wdevs/amcs/internal/types"
)
type CalendarTool struct {
store *store.DB
}
func NewCalendarTool(db *store.DB) *CalendarTool {
return &CalendarTool{store: db}
}
// add_family_member
type AddFamilyMemberInput struct {
Name string `json:"name" jsonschema:"person's name"`
Relationship string `json:"relationship,omitempty" jsonschema:"e.g. self, spouse, child, parent"`
BirthDate *time.Time `json:"birth_date,omitempty"`
Notes string `json:"notes,omitempty"`
}
type AddFamilyMemberOutput struct {
Member ext.FamilyMember `json:"member"`
}
func (t *CalendarTool) AddMember(ctx context.Context, _ *mcp.CallToolRequest, in AddFamilyMemberInput) (*mcp.CallToolResult, AddFamilyMemberOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, AddFamilyMemberOutput{}, errInvalidInput("name is required")
}
member, err := t.store.AddFamilyMember(ctx, ext.FamilyMember{
Name: strings.TrimSpace(in.Name),
Relationship: strings.TrimSpace(in.Relationship),
BirthDate: in.BirthDate,
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddFamilyMemberOutput{}, err
}
return nil, AddFamilyMemberOutput{Member: member}, nil
}
// list_family_members
type ListFamilyMembersInput struct{}
type ListFamilyMembersOutput struct {
Members []ext.FamilyMember `json:"members"`
}
func (t *CalendarTool) ListMembers(ctx context.Context, _ *mcp.CallToolRequest, _ ListFamilyMembersInput) (*mcp.CallToolResult, ListFamilyMembersOutput, error) {
members, err := t.store.ListFamilyMembers(ctx)
if err != nil {
return nil, ListFamilyMembersOutput{}, err
}
if members == nil {
members = []ext.FamilyMember{}
}
return nil, ListFamilyMembersOutput{Members: members}, nil
}
// add_activity
type AddActivityInput struct {
Title string `json:"title" jsonschema:"activity title"`
ActivityType string `json:"activity_type,omitempty" jsonschema:"e.g. sports, medical, school, social"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"leave empty for whole-family activities"`
DayOfWeek string `json:"day_of_week,omitempty" jsonschema:"for recurring: monday, tuesday, etc."`
StartTime string `json:"start_time,omitempty" jsonschema:"HH:MM format"`
EndTime string `json:"end_time,omitempty" jsonschema:"HH:MM format"`
StartDate *time.Time `json:"start_date,omitempty"`
EndDate *time.Time `json:"end_date,omitempty" jsonschema:"for recurring activities, when they end"`
Location string `json:"location,omitempty"`
Notes string `json:"notes,omitempty"`
}
type AddActivityOutput struct {
Activity ext.Activity `json:"activity"`
}
func (t *CalendarTool) AddActivity(ctx context.Context, _ *mcp.CallToolRequest, in AddActivityInput) (*mcp.CallToolResult, AddActivityOutput, error) {
if strings.TrimSpace(in.Title) == "" {
return nil, AddActivityOutput{}, errInvalidInput("title is required")
}
activity, err := t.store.AddActivity(ctx, ext.Activity{
FamilyMemberID: in.FamilyMemberID,
Title: strings.TrimSpace(in.Title),
ActivityType: strings.TrimSpace(in.ActivityType),
DayOfWeek: strings.ToLower(strings.TrimSpace(in.DayOfWeek)),
StartTime: strings.TrimSpace(in.StartTime),
EndTime: strings.TrimSpace(in.EndTime),
StartDate: in.StartDate,
EndDate: in.EndDate,
Location: strings.TrimSpace(in.Location),
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddActivityOutput{}, err
}
return nil, AddActivityOutput{Activity: activity}, nil
}
// get_week_schedule
type GetWeekScheduleInput struct {
WeekStart time.Time `json:"week_start" jsonschema:"start of the week (Monday) to retrieve"`
}
type GetWeekScheduleOutput struct {
Activities []ext.Activity `json:"activities"`
}
func (t *CalendarTool) GetWeekSchedule(ctx context.Context, _ *mcp.CallToolRequest, in GetWeekScheduleInput) (*mcp.CallToolResult, GetWeekScheduleOutput, error) {
activities, err := t.store.GetWeekSchedule(ctx, in.WeekStart)
if err != nil {
return nil, GetWeekScheduleOutput{}, err
}
if activities == nil {
activities = []ext.Activity{}
}
return nil, GetWeekScheduleOutput{Activities: activities}, nil
}
// search_activities
type SearchActivitiesInput struct {
Query string `json:"query,omitempty" jsonschema:"search text matching title or notes"`
ActivityType string `json:"activity_type,omitempty" jsonschema:"filter by type"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty" jsonschema:"filter by family member"`
}
type SearchActivitiesOutput struct {
Activities []ext.Activity `json:"activities"`
}
func (t *CalendarTool) SearchActivities(ctx context.Context, _ *mcp.CallToolRequest, in SearchActivitiesInput) (*mcp.CallToolResult, SearchActivitiesOutput, error) {
activities, err := t.store.SearchActivities(ctx, in.Query, in.ActivityType, in.FamilyMemberID)
if err != nil {
return nil, SearchActivitiesOutput{}, err
}
if activities == nil {
activities = []ext.Activity{}
}
return nil, SearchActivitiesOutput{Activities: activities}, nil
}
// add_important_date
type AddImportantDateInput struct {
Title string `json:"title" jsonschema:"description of the date"`
DateValue time.Time `json:"date_value" jsonschema:"the date"`
FamilyMemberID *uuid.UUID `json:"family_member_id,omitempty"`
RecurringYearly bool `json:"recurring_yearly,omitempty" jsonschema:"if true, reminds every year"`
ReminderDaysBefore int `json:"reminder_days_before,omitempty" jsonschema:"how many days before to remind (default: 7)"`
Notes string `json:"notes,omitempty"`
}
type AddImportantDateOutput struct {
Date ext.ImportantDate `json:"date"`
}
func (t *CalendarTool) AddImportantDate(ctx context.Context, _ *mcp.CallToolRequest, in AddImportantDateInput) (*mcp.CallToolResult, AddImportantDateOutput, error) {
if strings.TrimSpace(in.Title) == "" {
return nil, AddImportantDateOutput{}, errInvalidInput("title is required")
}
reminder := in.ReminderDaysBefore
if reminder <= 0 {
reminder = 7
}
d, err := t.store.AddImportantDate(ctx, ext.ImportantDate{
FamilyMemberID: in.FamilyMemberID,
Title: strings.TrimSpace(in.Title),
DateValue: in.DateValue,
RecurringYearly: in.RecurringYearly,
ReminderDaysBefore: reminder,
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddImportantDateOutput{}, err
}
return nil, AddImportantDateOutput{Date: d}, nil
}
// get_upcoming_dates
type GetUpcomingDatesInput struct {
DaysAhead int `json:"days_ahead,omitempty" jsonschema:"how many days to look ahead (default: 30)"`
}
type GetUpcomingDatesOutput struct {
Dates []ext.ImportantDate `json:"dates"`
}
func (t *CalendarTool) GetUpcomingDates(ctx context.Context, _ *mcp.CallToolRequest, in GetUpcomingDatesInput) (*mcp.CallToolResult, GetUpcomingDatesOutput, error) {
dates, err := t.store.GetUpcomingDates(ctx, in.DaysAhead)
if err != nil {
return nil, GetUpcomingDatesOutput{}, err
}
if dates == nil {
dates = []ext.ImportantDate{}
}
return nil, GetUpcomingDatesOutput{Dates: dates}, nil
}