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 }