package mcpserver import ( "log/slog" "net/http" "strings" "github.com/modelcontextprotocol/go-sdk/mcp" "git.warky.dev/wdevs/amcs/internal/config" "git.warky.dev/wdevs/amcs/internal/tools" amcsllm "git.warky.dev/wdevs/amcs/llm" ) const ( serverTitle = "Avalon Memory Crystal Server" serverWebsiteURL = "https://git.warky.dev/wdevs/amcs" ) type ToolSet struct { Version *tools.VersionTool Capture *tools.CaptureTool Search *tools.SearchTool List *tools.ListTool Stats *tools.StatsTool Get *tools.GetTool Update *tools.UpdateTool Delete *tools.DeleteTool Archive *tools.ArchiveTool Projects *tools.ProjectsTool Context *tools.ContextTool Recall *tools.RecallTool Summarize *tools.SummarizeTool Links *tools.LinksTool Files *tools.FilesTool Backfill *tools.BackfillTool Reparse *tools.ReparseMetadataTool RetryMetadata *tools.RetryMetadataTool Household *tools.HouseholdTool Maintenance *tools.MaintenanceTool Calendar *tools.CalendarTool Meals *tools.MealsTool CRM *tools.CRMTool Skills *tools.SkillsTool ChatHistory *tools.ChatHistoryTool Describe *tools.DescribeTool } // Handlers groups the HTTP handlers produced for an MCP server instance. type Handlers struct { // StreamableHTTP is the primary MCP handler (always non-nil). StreamableHTTP http.Handler // SSE is the SSE transport handler; nil when SSEPath is empty. // SSE is the de facto transport for MCP over the internet and is required by most hosted MCP clients. SSE http.Handler } // New builds the StreamableHTTP MCP handler. It is a convenience wrapper // around NewHandlers for callers that only need the primary transport. func New(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onSessionClosed func(string)) (http.Handler, error) { h, err := NewHandlers(cfg, logger, toolSet, onSessionClosed) if err != nil { return nil, err } return h.StreamableHTTP, nil } // NewHandlers builds MCP HTTP handlers for both transports. // SSE is nil when cfg.SSEPath is empty. func NewHandlers(cfg config.MCPConfig, logger *slog.Logger, toolSet ToolSet, onSessionClosed func(string)) (Handlers, error) { instructions := cfg.Instructions if instructions == "" { instructions = string(amcsllm.MemoryInstructions) } server := mcp.NewServer(&mcp.Implementation{ Name: cfg.ServerName, Title: serverTitle, Version: cfg.Version, WebsiteURL: serverWebsiteURL, Icons: buildServerIcons(cfg.PublicURL), }, &mcp.ServerOptions{ Instructions: instructions, }) for _, register := range []func(*mcp.Server, *slog.Logger, ToolSet) error{ registerSystemTools, registerThoughtTools, registerProjectTools, registerFileTools, registerMaintenanceTools, registerHouseholdTools, registerCalendarTools, registerMealTools, registerCRMTools, registerSkillTools, registerChatHistoryTools, registerDescribeTools, } { if err := register(server, logger, toolSet); err != nil { return Handlers{}, err } } opts := &mcp.StreamableHTTPOptions{ JSONResponse: true, SessionTimeout: cfg.SessionTimeout, } if onSessionClosed != nil { opts.EventStore = newCleanupEventStore(mcp.NewMemoryEventStore(nil), onSessionClosed) } h := Handlers{ StreamableHTTP: mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server }, opts), } if strings.TrimSpace(cfg.SSEPath) != "" { h.SSE = mcp.NewSSEHandler(func(*http.Request) *mcp.Server { return server }, nil) } return h, nil } // buildServerIcons returns icon definitions referencing the server's own /images/icon.png endpoint. // Returns nil when publicURL is empty so the icons field is omitted from the MCP identity. func buildServerIcons(publicURL string) []mcp.Icon { if strings.TrimSpace(publicURL) == "" { return nil } base := strings.TrimRight(publicURL, "/") return []mcp.Icon{ {Source: base + "/images/icon.png", MIMEType: "image/png"}, } } func registerSystemTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "get_version_info", Description: "Return the server build version information, including version, tag name, commit, and build date.", }, toolSet.Version.GetInfo); err != nil { return err } return nil } func registerThoughtTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "capture_thought", Description: "Store a thought with generated embeddings and extracted metadata. The thought is saved immediately even if metadata extraction times out; pending thoughts are retried in the background.", }, toolSet.Capture.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "search_thoughts", Description: "Search stored thoughts by semantic similarity. Falls back to Postgres full-text search automatically when no embeddings exist for the active model.", }, toolSet.Search.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_thoughts", Description: "List recent thoughts with optional metadata filters.", }, toolSet.List.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "thought_stats", Description: "Get counts and top metadata buckets across stored thoughts.", }, toolSet.Stats.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_thought", Description: "Retrieve a full thought by id.", }, toolSet.Get.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "update_thought", Description: "Update thought content or merge metadata.", }, toolSet.Update.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "delete_thought", Description: "Hard-delete a thought by id.", }, toolSet.Delete.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "archive_thought", Description: "Archive a thought so it is hidden from default search and listing.", }, toolSet.Archive.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "summarize_thoughts", Description: "Produce an LLM prose summary of a filtered or searched set of thoughts.", }, toolSet.Summarize.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "recall_context", Description: "Recall semantically relevant and recent context for prompt injection. Combines vector similarity with recency. Falls back to full-text search when no embeddings exist.", }, toolSet.Recall.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "link_thoughts", Description: "Create a typed relationship between two thoughts.", }, toolSet.Links.Link); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "related_thoughts", Description: "Retrieve explicit links and semantic neighbours for a thought. Falls back to full-text search when no embeddings exist.", }, toolSet.Links.Related); err != nil { return err } return nil } func registerProjectTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "create_project", Description: "Create a named project container for thoughts.", }, toolSet.Projects.Create); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_projects", Description: "List projects and their current thought counts.", }, toolSet.Projects.List); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "set_active_project", Description: "Set the active project for the current MCP session. Requires a stateful MCP client that reuses the same session across calls. If your client does not preserve sessions, pass project explicitly to each tool instead.", }, toolSet.Projects.SetActive); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_active_project", Description: "Return the active project for the current MCP session. If your client does not preserve MCP sessions, pass project explicitly to project-scoped tools instead of relying on this.", }, toolSet.Projects.GetActive); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_project_context", Description: "Get recent and semantic context for a project. Uses the explicit project when provided, otherwise the active MCP session project. Falls back to full-text search when no embeddings exist.", }, toolSet.Context.Handle); err != nil { return err } return nil } func registerFileTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { server.AddResourceTemplate(&mcp.ResourceTemplate{ Name: "stored_file", URITemplate: "amcs://files/{id}", Description: "A stored file. Read a file's raw binary content by its id. Use load_file for metadata.", }, toolSet.Files.ReadResource) if err := addTool(server, logger, &mcp.Tool{ Name: "upload_file", Description: "Stage a file and get an amcs://files/{id} resource URI. Use content_path (absolute server-side path, no size limit) for large or binary files, or content_base64 (≤10 MB) for small files. Pass thought_id/project to link immediately, or omit and pass the URI to save_file later.", }, toolSet.Files.Upload); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "save_file", Description: "Store a file and optionally link it to a thought. Use content_base64 (≤10 MB) for small files, or content_uri (amcs://files/{id} from a prior upload_file) for previously staged files. For files larger than 10 MB, use upload_file with content_path first. If the goal is to retain the artifact, store the file directly instead of reading or summarising it first.", }, toolSet.Files.Save); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "load_file", Description: "Load a stored file by id. Returns metadata, base64 content, and an embedded MCP binary resource at amcs://files/{id}. Prefer the embedded resource when your client supports it. The id field accepts a bare UUID or full amcs://files/{id} URI.", }, toolSet.Files.Load); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_files", Description: "List stored files, optionally filtered by thought, project, or kind.", }, toolSet.Files.List); err != nil { return err } return nil } func registerMaintenanceTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "backfill_embeddings", Description: "Generate missing embeddings for stored thoughts using the active embedding model. Run this after switching embedding models or importing thoughts that have no vectors.", }, toolSet.Backfill.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "reparse_thought_metadata", Description: "Re-extract and normalize metadata for stored thoughts from their content.", }, toolSet.Reparse.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "retry_failed_metadata", Description: "Retry metadata extraction for thoughts still marked pending or failed.", }, toolSet.RetryMetadata.Handle); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_maintenance_task", Description: "Create a recurring or one-time home maintenance task.", }, toolSet.Maintenance.AddTask); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "log_maintenance", Description: "Log completed maintenance work; automatically updates the task's next due date.", }, toolSet.Maintenance.LogWork); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_upcoming_maintenance", Description: "List maintenance tasks due within the next N days.", }, toolSet.Maintenance.GetUpcoming); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "search_maintenance_history", Description: "Search the maintenance log by task name, category, or date range.", }, toolSet.Maintenance.SearchHistory); err != nil { return err } return nil } func registerHouseholdTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "add_household_item", Description: "Store a household fact (paint color, appliance details, measurement, document, etc.).", }, toolSet.Household.AddItem); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "search_household_items", Description: "Search household items by name, category, or location.", }, toolSet.Household.SearchItems); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_household_item", Description: "Retrieve a household item by id.", }, toolSet.Household.GetItem); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_vendor", Description: "Add a service provider (plumber, electrician, landscaper, etc.).", }, toolSet.Household.AddVendor); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_vendors", Description: "List household service vendors, optionally filtered by service type.", }, toolSet.Household.ListVendors); err != nil { return err } return nil } func registerCalendarTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "add_family_member", Description: "Add a family member to the household.", }, toolSet.Calendar.AddMember); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_family_members", Description: "List all family members.", }, toolSet.Calendar.ListMembers); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_activity", Description: "Schedule a one-time or recurring family activity.", }, toolSet.Calendar.AddActivity); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_week_schedule", Description: "Get all activities scheduled for a given week.", }, toolSet.Calendar.GetWeekSchedule); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "search_activities", Description: "Search activities by title, type, or family member.", }, toolSet.Calendar.SearchActivities); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_important_date", Description: "Track a birthday, anniversary, deadline, or other important date.", }, toolSet.Calendar.AddImportantDate); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_upcoming_dates", Description: "Get important dates coming up in the next N days.", }, toolSet.Calendar.GetUpcomingDates); err != nil { return err } return nil } func registerMealTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "add_recipe", Description: "Save a recipe with ingredients and instructions.", }, toolSet.Meals.AddRecipe); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "search_recipes", Description: "Search recipes by name, cuisine, tags, or ingredient.", }, toolSet.Meals.SearchRecipes); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "update_recipe", Description: "Update an existing recipe.", }, toolSet.Meals.UpdateRecipe); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "create_meal_plan", Description: "Set the meal plan for a week; replaces any existing plan for that week.", }, toolSet.Meals.CreateMealPlan); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_meal_plan", Description: "Get the meal plan for a given week.", }, toolSet.Meals.GetMealPlan); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "generate_shopping_list", Description: "Auto-generate a shopping list from the meal plan for a given week.", }, toolSet.Meals.GenerateShoppingList); err != nil { return err } return nil } func registerCRMTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "add_professional_contact", Description: "Add a professional contact to the CRM.", }, toolSet.CRM.AddContact); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "search_contacts", Description: "Search professional contacts by name, company, title, notes, or tags.", }, toolSet.CRM.SearchContacts); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "log_interaction", Description: "Log an interaction with a professional contact.", }, toolSet.CRM.LogInteraction); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_contact_history", Description: "Get full history (interactions and opportunities) for a contact.", }, toolSet.CRM.GetHistory); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "create_opportunity", Description: "Create a deal, project, or opportunity linked to a contact.", }, toolSet.CRM.CreateOpportunity); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_follow_ups_due", Description: "List contacts with a follow-up date due within the next N days.", }, toolSet.CRM.GetFollowUpsDue); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "link_thought_to_contact", Description: "Append a stored thought to a contact's notes.", }, toolSet.CRM.LinkThought); err != nil { return err } return nil } func registerSkillTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "add_skill", Description: "Store a reusable agent skill (behavioural instruction or capability prompt).", }, toolSet.Skills.AddSkill); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "remove_skill", Description: "Delete an agent skill by id.", }, toolSet.Skills.RemoveSkill); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_skills", Description: "List all agent skills, optionally filtered by tag.", }, toolSet.Skills.ListSkills); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_guardrail", Description: "Store a reusable agent guardrail (constraint or safety rule).", }, toolSet.Skills.AddGuardrail); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "remove_guardrail", Description: "Delete an agent guardrail by id.", }, toolSet.Skills.RemoveGuardrail); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_guardrails", Description: "List all agent guardrails, optionally filtered by tag or severity.", }, toolSet.Skills.ListGuardrails); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_project_skill", Description: "Link an agent skill to a project. Pass project explicitly when your client does not preserve MCP sessions.", }, toolSet.Skills.AddProjectSkill); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "remove_project_skill", Description: "Unlink an agent skill from a project. Pass project explicitly when your client does not preserve MCP sessions.", }, toolSet.Skills.RemoveProjectSkill); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_project_skills", Description: "List all skills linked to a project. Call this at the start of every project session to load agent behaviour instructions before generating new ones. Only create new skills if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", }, toolSet.Skills.ListProjectSkills); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "add_project_guardrail", Description: "Link an agent guardrail to a project. Pass project explicitly when your client does not preserve MCP sessions.", }, toolSet.Skills.AddProjectGuardrail); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "remove_project_guardrail", Description: "Unlink an agent guardrail from a project. Pass project explicitly when your client does not preserve MCP sessions.", }, toolSet.Skills.RemoveProjectGuardrail); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_project_guardrails", Description: "List all guardrails linked to a project. Call this at the start of every project session to load agent constraints before generating new ones. Only create new guardrails if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", }, toolSet.Skills.ListProjectGuardrails); err != nil { return err } return nil } func registerChatHistoryTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "save_chat_history", Description: "Save a chat session's message history for later retrieval. Stores messages with optional title, summary, channel, agent, and project metadata.", }, toolSet.ChatHistory.SaveChatHistory); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "get_chat_history", Description: "Retrieve a saved chat history by its UUID or session_id. Returns the full message list.", }, toolSet.ChatHistory.GetChatHistory); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "list_chat_histories", Description: "List saved chat histories with optional filters: project, channel, agent_id, session_id, or recent days.", }, toolSet.ChatHistory.ListChatHistories); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "delete_chat_history", Description: "Permanently delete a saved chat history by id.", }, toolSet.ChatHistory.DeleteChatHistory); err != nil { return err } return nil } func registerDescribeTools(server *mcp.Server, logger *slog.Logger, toolSet ToolSet) error { if err := addTool(server, logger, &mcp.Tool{ Name: "describe_tools", Description: "Call this first in every session. Returns all available MCP tools with names, descriptions, categories, and your accumulated usage notes. Filter by category to narrow results. Available categories: system, thoughts, projects, files, admin, household, maintenance, calendar, meals, crm, skills, chat, meta.", }, toolSet.Describe.Describe); err != nil { return err } if err := addTool(server, logger, &mcp.Tool{ Name: "annotate_tool", Description: "Persist usage notes, gotchas, or workflow patterns for a specific tool. Notes survive across sessions and are returned by describe_tools. Call this whenever you discover something non-obvious about a tool's behaviour. Pass an empty string to clear notes.", }, toolSet.Describe.Annotate); err != nil { return err } return nil } // BuildToolCatalog returns the static catalog of all registered MCP tools. // Pass this to tools.NewDescribeTool when assembling the ToolSet. func BuildToolCatalog() []tools.ToolEntry { return []tools.ToolEntry{ // system {Name: "get_version_info", Description: "Return the server build version information, including version, tag name, commit, and build date.", Category: "system"}, // thoughts {Name: "capture_thought", Description: "Store a thought with generated embeddings and extracted metadata. The thought is saved immediately even if metadata extraction times out; pending thoughts are retried in the background.", Category: "thoughts"}, {Name: "search_thoughts", Description: "Search stored thoughts by semantic similarity. Falls back to Postgres full-text search automatically when no embeddings exist for the active model.", Category: "thoughts"}, {Name: "list_thoughts", Description: "List recent thoughts with optional metadata filters.", Category: "thoughts"}, {Name: "thought_stats", Description: "Get counts and top metadata buckets across stored thoughts.", Category: "thoughts"}, {Name: "get_thought", Description: "Retrieve a full thought by id.", Category: "thoughts"}, {Name: "update_thought", Description: "Update thought content or merge metadata.", Category: "thoughts"}, {Name: "delete_thought", Description: "Hard-delete a thought by id.", Category: "thoughts"}, {Name: "archive_thought", Description: "Archive a thought so it is hidden from default search and listing.", Category: "thoughts"}, {Name: "summarize_thoughts", Description: "Produce an LLM prose summary of a filtered or searched set of thoughts.", Category: "thoughts"}, {Name: "recall_context", Description: "Recall semantically relevant and recent context for prompt injection. Combines vector similarity with recency. Falls back to full-text search when no embeddings exist.", Category: "thoughts"}, {Name: "link_thoughts", Description: "Create a typed relationship between two thoughts.", Category: "thoughts"}, {Name: "related_thoughts", Description: "Retrieve explicit links and semantic neighbours for a thought. Falls back to full-text search when no embeddings exist.", Category: "thoughts"}, // projects {Name: "create_project", Description: "Create a named project container for thoughts.", Category: "projects"}, {Name: "list_projects", Description: "List projects and their current thought counts.", Category: "projects"}, {Name: "set_active_project", Description: "Set the active project for the current MCP session. Requires a stateful MCP client that reuses the same session across calls. If your client does not preserve sessions, pass project explicitly to each tool instead.", Category: "projects"}, {Name: "get_active_project", Description: "Return the active project for the current MCP session. If your client does not preserve MCP sessions, pass project explicitly to project-scoped tools instead of relying on this.", Category: "projects"}, {Name: "get_project_context", Description: "Get recent and semantic context for a project. Uses the explicit project when provided, otherwise the active MCP session project. Falls back to full-text search when no embeddings exist.", Category: "projects"}, // files {Name: "upload_file", Description: "Stage a file and get an amcs://files/{id} resource URI. Use content_path (absolute server-side path, no size limit) for large or binary files, or content_base64 (≤10 MB) for small files. Pass thought_id/project to link immediately, or omit and pass the URI to save_file later.", Category: "files"}, {Name: "save_file", Description: "Store a file and optionally link it to a thought. Use content_base64 (≤10 MB) for small files, or content_uri (amcs://files/{id} from a prior upload_file) for previously staged files. For files larger than 10 MB, use upload_file with content_path first. If the goal is to retain the artifact, store the file directly instead of reading or summarising it first.", Category: "files"}, {Name: "load_file", Description: "Load a stored file by id. Returns metadata, base64 content, and an embedded MCP binary resource at amcs://files/{id}. Prefer the embedded resource when your client supports it. The id field accepts a bare UUID or full amcs://files/{id} URI.", Category: "files"}, {Name: "list_files", Description: "List stored files, optionally filtered by thought, project, or kind.", Category: "files"}, // admin {Name: "backfill_embeddings", Description: "Generate missing embeddings for stored thoughts using the active embedding model. Run this after switching embedding models or importing thoughts that have no vectors.", Category: "admin"}, {Name: "reparse_thought_metadata", Description: "Re-extract and normalize metadata for stored thoughts from their content.", Category: "admin"}, {Name: "retry_failed_metadata", Description: "Retry metadata extraction for thoughts still marked pending or failed.", Category: "admin"}, // household {Name: "add_household_item", Description: "Store a household fact (paint color, appliance details, measurement, document, etc.).", Category: "household"}, {Name: "search_household_items", Description: "Search household items by name, category, or location.", Category: "household"}, {Name: "get_household_item", Description: "Retrieve a household item by id.", Category: "household"}, {Name: "add_vendor", Description: "Add a service provider (plumber, electrician, landscaper, etc.).", Category: "household"}, {Name: "list_vendors", Description: "List household service vendors, optionally filtered by service type.", Category: "household"}, // maintenance {Name: "add_maintenance_task", Description: "Create a recurring or one-time home maintenance task.", Category: "maintenance"}, {Name: "log_maintenance", Description: "Log completed maintenance work; automatically updates the task's next due date.", Category: "maintenance"}, {Name: "get_upcoming_maintenance", Description: "List maintenance tasks due within the next N days.", Category: "maintenance"}, {Name: "search_maintenance_history", Description: "Search the maintenance log by task name, category, or date range.", Category: "maintenance"}, // calendar {Name: "add_family_member", Description: "Add a family member to the household.", Category: "calendar"}, {Name: "list_family_members", Description: "List all family members.", Category: "calendar"}, {Name: "add_activity", Description: "Schedule a one-time or recurring family activity.", Category: "calendar"}, {Name: "get_week_schedule", Description: "Get all activities scheduled for a given week.", Category: "calendar"}, {Name: "search_activities", Description: "Search activities by title, type, or family member.", Category: "calendar"}, {Name: "add_important_date", Description: "Track a birthday, anniversary, deadline, or other important date.", Category: "calendar"}, {Name: "get_upcoming_dates", Description: "Get important dates coming up in the next N days.", Category: "calendar"}, // meals {Name: "add_recipe", Description: "Save a recipe with ingredients and instructions.", Category: "meals"}, {Name: "search_recipes", Description: "Search recipes by name, cuisine, tags, or ingredient.", Category: "meals"}, {Name: "update_recipe", Description: "Update an existing recipe.", Category: "meals"}, {Name: "create_meal_plan", Description: "Set the meal plan for a week; replaces any existing plan for that week.", Category: "meals"}, {Name: "get_meal_plan", Description: "Get the meal plan for a given week.", Category: "meals"}, {Name: "generate_shopping_list", Description: "Auto-generate a shopping list from the meal plan for a given week.", Category: "meals"}, // crm {Name: "add_professional_contact", Description: "Add a professional contact to the CRM.", Category: "crm"}, {Name: "search_contacts", Description: "Search professional contacts by name, company, title, notes, or tags.", Category: "crm"}, {Name: "log_interaction", Description: "Log an interaction with a professional contact.", Category: "crm"}, {Name: "get_contact_history", Description: "Get full history (interactions and opportunities) for a contact.", Category: "crm"}, {Name: "create_opportunity", Description: "Create a deal, project, or opportunity linked to a contact.", Category: "crm"}, {Name: "get_follow_ups_due", Description: "List contacts with a follow-up date due within the next N days.", Category: "crm"}, {Name: "link_thought_to_contact", Description: "Append a stored thought to a contact's notes.", Category: "crm"}, // skills {Name: "add_skill", Description: "Store a reusable agent skill (behavioural instruction or capability prompt).", Category: "skills"}, {Name: "remove_skill", Description: "Delete an agent skill by id.", Category: "skills"}, {Name: "list_skills", Description: "List all agent skills, optionally filtered by tag.", Category: "skills"}, {Name: "add_guardrail", Description: "Store a reusable agent guardrail (constraint or safety rule).", Category: "skills"}, {Name: "remove_guardrail", Description: "Delete an agent guardrail by id.", Category: "skills"}, {Name: "list_guardrails", Description: "List all agent guardrails, optionally filtered by tag or severity.", Category: "skills"}, {Name: "add_project_skill", Description: "Link an agent skill to a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "remove_project_skill", Description: "Unlink an agent skill from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "list_project_skills", Description: "List all skills linked to a project. Call this at the start of every project session to load agent behaviour instructions before generating new ones. Only create new skills if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "add_project_guardrail", Description: "Link an agent guardrail to a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "remove_project_guardrail", Description: "Unlink an agent guardrail from a project. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, {Name: "list_project_guardrails", Description: "List all guardrails linked to a project. Call this at the start of every project session to load agent constraints before generating new ones. Only create new guardrails if none are returned. Pass project explicitly when your client does not preserve MCP sessions.", Category: "skills"}, // chat {Name: "save_chat_history", Description: "Save a chat session's message history for later retrieval. Stores messages with optional title, summary, channel, agent, and project metadata.", Category: "chat"}, {Name: "get_chat_history", Description: "Retrieve a saved chat history by its UUID or session_id. Returns the full message list.", Category: "chat"}, {Name: "list_chat_histories", Description: "List saved chat histories with optional filters: project, channel, agent_id, session_id, or recent days.", Category: "chat"}, {Name: "delete_chat_history", Description: "Permanently delete a saved chat history by id.", Category: "chat"}, // meta {Name: "describe_tools", Description: "Call this first in every session. Returns all available MCP tools with names, descriptions, categories, and your accumulated usage notes. Filter by category to narrow results. Available categories: system, thoughts, projects, files, admin, household, maintenance, calendar, meals, crm, skills, chat, meta.", Category: "meta"}, {Name: "annotate_tool", Description: "Persist usage notes, gotchas, or workflow patterns for a specific tool. Notes survive across sessions and are returned by describe_tools. Call this whenever you discover something non-obvious about a tool's behaviour. Pass an empty string to clear notes.", Category: "meta"}, } }