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

151
internal/tools/household.go Normal file
View File

@@ -0,0 +1,151 @@
package tools
import (
"context"
"strings"
"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 HouseholdTool struct {
store *store.DB
}
func NewHouseholdTool(db *store.DB) *HouseholdTool {
return &HouseholdTool{store: db}
}
// add_household_item
type AddHouseholdItemInput struct {
Name string `json:"name" jsonschema:"name of the item"`
Category string `json:"category,omitempty" jsonschema:"category (e.g. paint, appliance, measurement, document)"`
Location string `json:"location,omitempty" jsonschema:"where in the home this item is"`
Details map[string]any `json:"details,omitempty" jsonschema:"flexible metadata (model numbers, colors, specs, etc.)"`
Notes string `json:"notes,omitempty"`
}
type AddHouseholdItemOutput struct {
Item ext.HouseholdItem `json:"item"`
}
func (t *HouseholdTool) AddItem(ctx context.Context, _ *mcp.CallToolRequest, in AddHouseholdItemInput) (*mcp.CallToolResult, AddHouseholdItemOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, AddHouseholdItemOutput{}, errInvalidInput("name is required")
}
if in.Details == nil {
in.Details = map[string]any{}
}
item, err := t.store.AddHouseholdItem(ctx, ext.HouseholdItem{
Name: strings.TrimSpace(in.Name),
Category: strings.TrimSpace(in.Category),
Location: strings.TrimSpace(in.Location),
Details: in.Details,
Notes: strings.TrimSpace(in.Notes),
})
if err != nil {
return nil, AddHouseholdItemOutput{}, err
}
return nil, AddHouseholdItemOutput{Item: item}, nil
}
// search_household_items
type SearchHouseholdItemsInput struct {
Query string `json:"query,omitempty" jsonschema:"search text matching name or notes"`
Category string `json:"category,omitempty" jsonschema:"filter by category"`
Location string `json:"location,omitempty" jsonschema:"filter by location"`
}
type SearchHouseholdItemsOutput struct {
Items []ext.HouseholdItem `json:"items"`
}
func (t *HouseholdTool) SearchItems(ctx context.Context, _ *mcp.CallToolRequest, in SearchHouseholdItemsInput) (*mcp.CallToolResult, SearchHouseholdItemsOutput, error) {
items, err := t.store.SearchHouseholdItems(ctx, in.Query, in.Category, in.Location)
if err != nil {
return nil, SearchHouseholdItemsOutput{}, err
}
if items == nil {
items = []ext.HouseholdItem{}
}
return nil, SearchHouseholdItemsOutput{Items: items}, nil
}
// get_household_item
type GetHouseholdItemInput struct {
ID uuid.UUID `json:"id" jsonschema:"item id"`
}
type GetHouseholdItemOutput struct {
Item ext.HouseholdItem `json:"item"`
}
func (t *HouseholdTool) GetItem(ctx context.Context, _ *mcp.CallToolRequest, in GetHouseholdItemInput) (*mcp.CallToolResult, GetHouseholdItemOutput, error) {
item, err := t.store.GetHouseholdItem(ctx, in.ID)
if err != nil {
return nil, GetHouseholdItemOutput{}, err
}
return nil, GetHouseholdItemOutput{Item: item}, nil
}
// add_vendor
type AddVendorInput struct {
Name string `json:"name" jsonschema:"vendor name"`
ServiceType string `json:"service_type,omitempty" jsonschema:"type of service (e.g. plumber, electrician, landscaper)"`
Phone string `json:"phone,omitempty"`
Email string `json:"email,omitempty"`
Website string `json:"website,omitempty"`
Notes string `json:"notes,omitempty"`
Rating *int `json:"rating,omitempty" jsonschema:"1-5 rating"`
}
type AddVendorOutput struct {
Vendor ext.HouseholdVendor `json:"vendor"`
}
func (t *HouseholdTool) AddVendor(ctx context.Context, _ *mcp.CallToolRequest, in AddVendorInput) (*mcp.CallToolResult, AddVendorOutput, error) {
if strings.TrimSpace(in.Name) == "" {
return nil, AddVendorOutput{}, errInvalidInput("name is required")
}
vendor, err := t.store.AddVendor(ctx, ext.HouseholdVendor{
Name: strings.TrimSpace(in.Name),
ServiceType: strings.TrimSpace(in.ServiceType),
Phone: strings.TrimSpace(in.Phone),
Email: strings.TrimSpace(in.Email),
Website: strings.TrimSpace(in.Website),
Notes: strings.TrimSpace(in.Notes),
Rating: in.Rating,
})
if err != nil {
return nil, AddVendorOutput{}, err
}
return nil, AddVendorOutput{Vendor: vendor}, nil
}
// list_vendors
type ListVendorsInput struct {
ServiceType string `json:"service_type,omitempty" jsonschema:"filter by service type"`
}
type ListVendorsOutput struct {
Vendors []ext.HouseholdVendor `json:"vendors"`
}
func (t *HouseholdTool) ListVendors(ctx context.Context, _ *mcp.CallToolRequest, in ListVendorsInput) (*mcp.CallToolResult, ListVendorsOutput, error) {
vendors, err := t.store.ListVendors(ctx, in.ServiceType)
if err != nil {
return nil, ListVendorsOutput{}, err
}
if vendors == nil {
vendors = []ext.HouseholdVendor{}
}
return nil, ListVendorsOutput{Vendors: vendors}, nil
}