{"name":"OpenPOS","version":"2.0.0","description":"AI-native restaurant API. Discover restaurants, browse menus, place orders, make reservations.","protocol":"MCP Streamable HTTP (2025-03-26)","tools":[{"name":"search_stores","description":"Search for restaurants by name, cuisine, or location. Returns store summaries with slugs for get_store_context.","inputSchema":{"type":"object","properties":{"search":{"type":"string","description":"Search by name or description"},"cuisine":{"type":"string","description":"Filter by cuisine (e.g., \"italian\", \"pizza\", \"thai\")"},"limit":{"type":"number","description":"Max results (default 20)"}}},"access":"public"},{"name":"get_store_context","description":"Get EVERYTHING about a restaurant in one call: store info, full menu with modifiers and prices, hours, services, AI instructions, guardrails, availability. This is the only call needed to fully understand a restaurant.","inputSchema":{"type":"object","properties":{"storeSlug":{"type":"string","description":"Store slug (from search_stores)"}},"required":["storeSlug"]},"access":"public"},{"name":"create_order","description":"Place an order at a restaurant. Provide items with quantities and modifiers, order type, and customer contact info. Guardrails (max order value, restricted items) are enforced server-side.","inputSchema":{"type":"object","properties":{"storeSlug":{"type":"string","description":"Store slug"},"items":{"type":"array","description":"Items to order","items":{"type":"object","properties":{"itemId":{"type":"string","description":"Menu item ID (from get_store_context)"},"quantity":{"type":"number","description":"Quantity (default 1)"},"modifiers":{"type":"array","description":"Selected modifier options","items":{"type":"object","properties":{"modifierGroupId":{"type":"string"},"optionId":{"type":"string"}},"required":["modifierGroupId","optionId"]}},"notes":{"type":"string","description":"Item-level special requests"}},"required":["itemId"]}},"orderType":{"type":"string","enum":["pickup","delivery","dine_in"],"description":"How the customer wants their order"},"customer":{"type":"object","description":"Customer contact info","properties":{"name":{"type":"string","description":"Customer name"},"phone":{"type":"string","description":"Phone number (may be required by store)"},"email":{"type":"string","description":"Email address"}},"required":["name"]},"scheduledFor":{"type":"string","description":"ISO timestamp for scheduled orders (omit for ASAP)"},"notes":{"type":"string","description":"Order-level notes"}},"required":["storeSlug","items","orderType","customer"]},"access":"action"},{"name":"create_reservation","description":"Make a reservation at a restaurant. Provide date, time, party size, and customer info.","inputSchema":{"type":"object","properties":{"storeSlug":{"type":"string","description":"Store slug"},"date":{"type":"string","description":"Date in YYYY-MM-DD format"},"time":{"type":"string","description":"Time in HH:MM format"},"partySize":{"type":"number","description":"Number of guests"},"customer":{"type":"object","description":"Customer contact info","properties":{"name":{"type":"string","description":"Customer name"},"phone":{"type":"string","description":"Phone number"},"email":{"type":"string","description":"Email address"}},"required":["name"]},"notes":{"type":"string","description":"Special requests or occasion"}},"required":["storeSlug","date","time","partySize","customer"]},"access":"action"},{"name":"join_waitlist","description":"Add a customer to the restaurant waitlist. Returns estimated wait time and position.","inputSchema":{"type":"object","properties":{"storeSlug":{"type":"string","description":"Store slug"},"partySize":{"type":"number","description":"Number of guests"},"customer":{"type":"object","description":"Customer contact info","properties":{"name":{"type":"string","description":"Customer name"},"phone":{"type":"string","description":"Phone number for notification"}},"required":["name","phone"]}},"required":["storeSlug","partySize","customer"]},"access":"action"},{"name":"get_business_summary","description":"Get revenue summary for a date range: gross/net revenue, orders, avg ticket, breakdowns by order type. Defaults to today. Pass `days` for a rolling lookback (e.g. days=7 for the last week) or explicit startDate/endDate.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"days":{"type":"number","description":"Rolling lookback window ending today, e.g. 7 = last 7 days. Ignored if startDate is given."},"startDate":{"type":"string","description":"Start date YYYY-MM-DD (default: today)"},"endDate":{"type":"string","description":"End date YYYY-MM-DD (default: today)"}},"required":["locationId"]},"access":"private"},{"name":"get_top_items","description":"Get top selling menu items by revenue or quantity for a date range.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"startDate":{"type":"string","description":"Start date YYYY-MM-DD (default: 7 days ago)"},"endDate":{"type":"string","description":"End date YYYY-MM-DD (default: today)"},"sortBy":{"type":"string","description":"\"revenue\" or \"quantity\" (default: revenue)"},"limit":{"type":"number","description":"Number of items (default: 10)"}},"required":["locationId"]},"access":"private"},{"name":"compare_periods","description":"Compare sales between two periods (today vs yesterday, this week vs last, this month vs last).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"period":{"type":"string","description":"\"today_vs_yesterday\", \"this_week_vs_last\", or \"this_month_vs_last\""}},"required":["locationId","period"]},"access":"private"},{"name":"get_menu_items","description":"Get menu items with prices, descriptions, and stock status. Can filter by category or search by name.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"categoryId":{"type":"string","description":"Filter by category ID"},"search":{"type":"string","description":"Search items by name"},"inStockOnly":{"type":"boolean","description":"Only show in-stock items (default false)"}},"required":["locationId"]},"access":"private"},{"name":"get_categories","description":"Get all menu categories with item counts.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_86_board","description":"Get all items currently 86'd (out of stock).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"set_item_availability","description":"86 (mark out of stock) or un-86 (mark back in stock) a menu item.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"itemId":{"type":"string","description":"Menu item ID"},"inStock":{"type":"boolean","description":"true = back in stock, false = 86'd"},"reason":{"type":"string","description":"Reason for 86ing (optional)"}},"required":["locationId","itemId","inStock"]},"access":"private"},{"name":"get_recent_orders","description":"Get recent orders. Can filter by status (pending, confirmed, preparing, ready, completed, cancelled).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"status":{"type":"string","description":"Filter by status: pending, confirmed, preparing, ready, completed, cancelled"},"limit":{"type":"number","description":"Number of orders (default 20, max 50)"}},"required":["locationId"]},"access":"private"},{"name":"get_active_orders","description":"Get all active (non-completed, non-cancelled) orders grouped by status. Shows current order pipeline.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"search_customers","description":"Search customers by name, phone, or email. Sort by spending, order count, or last visit.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"query":{"type":"string","description":"Search by name, phone, or email"},"sortBy":{"type":"string","description":"\"totalSpent\", \"orderCount\", \"lastOrder\", or \"name\" (default: lastOrder)"},"limit":{"type":"number","description":"Number of results (default 15)"}},"required":["locationId"]},"access":"private"},{"name":"get_customer_details","description":"Get detailed info about a customer: spending, orders, visits, segments, notes.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"customerId":{"type":"string","description":"Customer ID or public ID (cust_xxx)"}},"required":["locationId","customerId"]},"access":"private"},{"name":"get_top_customers","description":"Get top customers by spending or order count.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"sortBy":{"type":"string","description":"\"totalSpent\" or \"orderCount\" (default: totalSpent)"},"limit":{"type":"number","description":"Number of results (default 10)"}},"required":["locationId"]},"access":"private"},{"name":"get_todays_reservations","description":"Get all reservations for today, grouped by status.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_upcoming_reservations","description":"Get upcoming confirmed/pending reservations (today and future).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"limit":{"type":"number","description":"Number of reservations (default 20, max 50)"}},"required":["locationId"]},"access":"private"},{"name":"get_reservation_analytics","description":"Get reservation analytics (total, confirmed, completed, cancelled, no-show) for a date range.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"startDate":{"type":"string","description":"Start date YYYY-MM-DD (default: 7 days ago)"},"endDate":{"type":"string","description":"End date YYYY-MM-DD (default: today)"}},"required":["locationId"]},"access":"private"},{"name":"get_waitlist_status","description":"Get current waitlist queue status: how many waiting, notified, checked in, seated, and estimated wait time.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_waitlist_queue","description":"Get the active waitlist queue with names, party sizes, and wait times.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_waitlist_analytics","description":"Get waitlist analytics: total, seated, no-shows, conversion rate, avg wait times.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"startDate":{"type":"string","description":"Start date YYYY-MM-DD (default: 7 days ago)"},"endDate":{"type":"string","description":"End date YYYY-MM-DD (default: today)"}},"required":["locationId"]},"access":"private"},{"name":"get_active_deliveries","description":"Get all active delivery orders, grouped by joint fulfillment state (Preparing, Awaiting dispatch, Dispatching, Courier confirmed, On the way, etc.). L1 order status alone does not distinguish kitchen-ready from courier-in-flight; the joint state from the fulfillment display helper does.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_delivery_summary","description":"Get today's delivery summary: total deliveries, completed, in progress, revenue from deliveries.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_order_details","description":"Get full details for a specific order, including items, modifiers, and available status transitions.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"orderId":{"type":"string","description":"The order ID to look up"}},"required":["locationId","orderId"]},"access":"private"},{"name":"get_today_order_count","description":"Get the total number of orders placed today.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_customer_segments","description":"List all customer segments (VIP, regulars, new, at-risk, etc.) with member counts.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_items_missing_images","description":"Get a list of menu items that are missing images or descriptions.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"missingType":{"type":"string","description":"\"images\", \"descriptions\", or \"both\" (default: both)"}},"required":["locationId"]},"access":"private"},{"name":"get_task_runs","description":"Get recent task runs for a location. Returns task execution history with status, summary, and results.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"taskId":{"type":"string","description":"Filter by specific task ID"},"status":{"type":"string","description":"Filter by status: pending, running, completed, failed"},"since":{"type":"string","description":"Only return runs created after this ISO 8601 timestamp (e.g., \"2026-03-24T00:00:00\")"},"limit":{"type":"number","description":"Number of results (default 20, max 50)"}},"required":["locationId"]},"access":"private"},{"name":"create_task_run","description":"Create a new task run record. Used by external agents to log task execution or schedule a task for later processing.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"taskId":{"type":"string","description":"Task ID from the task registry (e.g., \"daily.revenue_check\")"},"status":{"type":"string","description":"Initial status: pending (default) or running"},"triggeredBy":{"type":"string","description":"Who triggered this run (default: \"openpos-agent\")"},"summary":{"type":"string","description":"Summary text (for completed tasks)"},"details":{"type":"object","description":"Structured result data (JSON)"}},"required":["locationId","taskId"]},"access":"private"},{"name":"update_task_run","description":"Update an existing task run record (e.g., mark running → completed or failed). Use this instead of creating a new record.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"runId":{"type":"string","description":"Task run ID to update"},"status":{"type":"string","enum":["running","completed","failed","pending"],"description":"New status"},"summary":{"type":"string","description":"Summary text"},"details":{"type":"object","description":"Structured result data (JSON)"}},"required":["locationId","runId","status"]},"access":"private"},{"name":"get_pending_background_tasks","description":"Get pending background tasks for a location (created by dashboard delegation). Returns task descriptions that need processing.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"limit":{"type":"number","description":"Number of tasks (default 10)"}},"required":["locationId"]},"access":"private"},{"name":"update_background_task","description":"Update the status of a background task (mark as in_progress, completed, or failed).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"taskId":{"type":"string","description":"Background task ID"},"status":{"type":"string","description":"New status: in_progress, completed, or failed"},"result":{"type":"string","description":"Task result text (for completed tasks)"},"error":{"type":"string","description":"Error message (for failed tasks)"}},"required":["locationId","taskId","status"]},"access":"private"},{"name":"get_store_capabilities","description":"Audit which OpenPOS features are enabled vs available for a location. Returns utilization score, enabled features, available features, and top recommendations for growth.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_website_seo","description":"Get current SEO state: store description, cuisine tags, highlights, visibility settings, website meta tags. Use this to audit AI discoverability.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"update_store_seo_metadata","description":"Update store's AI-discoverable metadata: description, cuisine tags, highlights, price range, store instructions. Use after analyzing SEO state to apply improvements.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"description":{"type":"string","description":"New store description (updates location record)"},"cuisineTags":{"type":"array","items":{"type":"string"},"description":"Cuisine tags for AI discovery"},"highlights":{"type":"string","description":"Store highlights text"},"priceRange":{"type":"string","description":"Price range: $, $$, $$$, or $$$$"},"storeInstructions":{"type":"string","description":"Instructions for how AI represents the store"}},"required":["locationId"]},"access":"private"},{"name":"get_menu_performance_analysis","description":"Deep menu performance analysis from analytics_item_performance. Shows top performers, underperformers, trends, and modifier popularity over configurable date range.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"days":{"type":"number","description":"Number of days to analyze (default 30)"},"sortBy":{"type":"string","description":"\"revenue\", \"quantity\", or \"growth\" (default: revenue)"}},"required":["locationId"]},"access":"private"},{"name":"generate_daily_snapshot","description":"Generate the \"Daily Brief\" report for a location — yesterday's revenue, orders, AOV, top items, day-over-day comparison. No LLM needed; pure data. Engine calls this when fulfilling the daily.snapshot scheduled task.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"generate_weekly_review","description":"Generate the weekly review report — week-over-week revenue, top movers, anomalies, recommended focus areas. Engine calls this for weekly.review scheduled task.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_service_config","description":"Get the AI Manager service configuration for a location: enabled services, schedules, communication channels, quiet hours.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"update_service_config","description":"Update the AI Manager service configuration for a location. Can update services, channels, quiet hours, and active status.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"services":{"type":"object","description":"Service configurations to update (merge with existing)"},"channels":{"type":"object","description":"Communication channels","properties":{"email":{"type":"string"},"phone":{"type":"string"},"chat_platform":{"type":"string"},"language":{"type":"string"}}},"quietHoursStart":{"type":"string","description":"Quiet hours start HH:MM"},"quietHoursEnd":{"type":"string","description":"Quiet hours end HH:MM"},"timezone":{"type":"string","description":"Timezone (e.g., America/Chicago)"},"isActive":{"type":"boolean","description":"Enable/disable the AI Manager"}},"required":["locationId"]},"access":"private"},{"name":"send_notification","description":"Low-level send to the store owner via email, SMS, or push — raw delivery, NO dedupe/cooldown/quiet-hours. Use for one-off report/analysis delivery. For owner-facing operator notifications use notify_owner instead (it adds urgency-scaled cooldowns, per-store throttles, quiet hours, and dedupe so the owner is never spammed). Email is the default for reports. Push for staff. For email/SMS, `to` is optional — if omitted, falls back to the merchant owner's account email/phone so stores without an explicit AI Manager channel still get reports by default.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"channel":{"type":"string","enum":["email","sms","push"],"description":"Delivery channel: email (reports), sms (urgent alerts only), push (staff)"},"to":{"type":"string","description":"Recipient (email address or phone number). Optional — falls back to owner account email/phone when omitted."},"subject":{"type":"string","description":"Subject line (for email)"},"message":{"type":"string","description":"Message body"}},"required":["locationId","channel","message"]},"access":"private"},{"name":"log_activity","description":"Record an internal event in the activity audit log. ONLY for short event traces (\"daily check ran clean\", \"investigated anomaly — false positive\"). Use actionType=\"action\" or \"research\" for events. NEVER use actionType=\"report\" — owner-facing reports MUST go through propose_action(type=report_write) which auto-applies and writes the proper report.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"serviceName":{"type":"string","description":"Service name (e.g., \"daily_snapshot\", \"review_monitor\", \"onboarding\")"},"actionType":{"type":"string","enum":["alert","action","research"],"description":"Type of activity (event-only — use propose_action(report_write) for owner-facing reports)"},"summary":{"type":"string","description":"Human-readable summary"},"details":{"type":"object","description":"Full structured data (JSON)"},"deliveryChannel":{"type":"string","description":"How result was delivered (email, sms, push, chat)"}},"required":["locationId","serviceName","actionType","summary"]},"access":"private"},{"name":"get_recent_actions","description":"Recent ai_action_queue entries (proposed/applied/rejected/expired) for this location — what the AI has been doing. Use to avoid duplicating work + learn from owner approval patterns.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"days":{"type":"number"}},"required":["locationId"]},"access":"private"},{"name":"get_store_signals","description":"Sense the store — your complete live picture, start here. Returns: the store-LOCAL clock (timezone, localTime, localDayOfWeek, localHour) so you time actions to their day/hours; phase + what is set up (menu, hours, payments); order/customer/revenue counts; the ACTUAL active promotions (codes + offers + windows) so you never recreate or overlap one; the current loyaltyProgram (name/type/status) so you never add a second; and recentCampaigns (last 60 days) so you do not repeat marketing. Read it like a manager walking in.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_action_catalog","description":"The authoritative list of every action you can propose, with each one's exact payload shape (field names, types, enum values, required vs optional) — generated live from the validation schemas, so it never drifts. Consult this before propose_action if unsure of a payload; never guess field names or enum values. Returns [{ type, riskLevel, needsApproval, payload }].","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_outstanding_items","description":"Understand what's still open for the owner — to inform your play, NOT to notify (the system reminds the owner about these on cadence). Returns { pendingApprovals: [{ id, type, title, ageHours, timesNotified, lastNotifiedAt, approvalUrl }], nextStep: { key, title, summary } | null }. nextStep is the current system-owned setup/activation reminder (connect payments → add menu → set hours → get first order); pendingApprovals are money-out decisions the system re-nudges. Do NOT re-announce either via notify_owner.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_operator_followup","description":"RECONCILE PAST WORK — call FIRST each cycle, before proposing anything new. One call returns: failedActions + unverifiedActions (fix these), scheduledDue (now due), recentApplied (what you did), outcomeNotes (what you've learned, with confidence), and goalPace. Close the loop on prior plays — don't pile on new ones while old ones are unverified or their outcome is still pending.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_goal_state","description":"The store's active/proposed growth goal + progress, pace, and the journey of rungs achieved. The objective the agent is driving toward (and whether it's awaiting the owner).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"reconcile_goal","description":"Reconcile the store's goal STATE before you propose tactics — call this FIRST on a goal-driven tick. It is deterministic, server-validated, and idempotent: banks an achieved goal (exactly once), advances to the next rung, ensures a goal exists (proposes the entry goal to the owner for a fresh store), and celebrates a milestone exactly once. Returns { goal, drive, awaitingOwner }: if drive=false (awaiting owner / stopped / no goal) STOP — don't propose tactics this tick. If drive=true, proceed to sense + propose toward `goal`.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_value_summary","description":"What the AI has delivered for this store over the period — proven revenue, labor saved, volume handled (the value ledger). Use to favor what worked and avoid repeating finished work.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"days":{"type":"number"}},"required":["locationId"]},"access":"private"},{"name":"get_opportunities","description":"Proactive growth plays that currently fit this store (win-backs, promos, SEO/GEO, menu, catering, etc.), pre-filtered by store state. Candidate tactics to consider proposing.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_lapsed_customers","description":"Customers who have not ordered within the lapse window — win-back candidates. Returns the count (capped) + a sample (email, name, last order, lifetime orders).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"lapseDays":{"type":"number"}},"required":["locationId"]},"access":"private"},{"name":"get_foundation","description":"Owner-set boundaries the agent MUST operate within: marketing budget tier/cap, hard-nos (things never to do), optional direction. Always respect these.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"run_due_periodic_tasks","description":"Run any CALENDAR-due periodic work for this store: daily/weekly/monthly manager reports + email digests. Each is independently kill-switch-gated and idempotent (already-sent checks), so calling every cycle is safe — it no-ops when nothing is due. The engine calls this per store on its own loop (the engine is the clock; no external scheduler). Returns which items fired.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_due_operator_work","description":"Detect LLM operator work due for this store that YOU (the engine) should run: { welcome: bool (store newly configured, never onboarded), resumes: [{questionId, question, answer, resumeWith}] (owner answered a question you were waiting on — continue the in-flight skill/work) }. Detection only — run welcome via the welcome skill, run each resume by continuing per resumeWith, then call mark_welcome_done / mark_resume_done so it is not re-run. (goal_* answers resume automatically server-side; they are NOT returned here.)","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"mark_welcome_done","description":"Mark the welcome package as completed for this store (idempotency marker so get_due_operator_work stops returning welcome=true). Call after you finish the welcome onboarding run.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"summary":{"type":"string"}},"required":["locationId"]},"access":"private"},{"name":"mark_resume_done","description":"Mark an answered owner question as resumed (so get_due_operator_work stops returning it). Call after you continue the work that was waiting on the answer.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"questionId":{"type":"string"}},"required":["locationId","questionId"]},"access":"private"},{"name":"get_knowledge_entries","description":"Owner-supplied knowledge entries (story, brand voice, constraints, etc.). What only the owner knows that the AI should respect.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"recall_memory","description":"Recall the curated facts (owner-taught + AI-learned) most RELEVANT to your current decision — pass a query, tags, or a topic. Returns a small ranked set, never the whole store. The knowledge map lists what topics exist + a count; use this to pull the actual content only when the decision turns on it (don't guess — recall). Pinned facts always surface.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"query":{"type":"string","description":"Free-text describing what you need (e.g. \"delivery radius and fees\")."},"tags":{"type":"array","items":{"type":"string"},"description":"Tag filter (e.g. [\"pricing\"])."},"topic":{"type":"string","description":"Match a specific topic label."},"limit":{"type":"number","description":"Max facts to return (default 5, max 25)."}},"required":["locationId"]},"access":"private"},{"name":"write_memory","description":"Record a durable fact you just learned about this store (e.g. owner confirmed they cater weddings; the brunch crowd skews families). Goes into curated memory; a same-topic fact is superseded so memory stays current. Use for things worth remembering across cycles — NOT transient observations. Set expiresAt (ISO) for time-bound facts.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"topic":{"type":"string","description":"Short label for the fact (e.g. \"Catering\")."},"content":{"type":"string","description":"The fact, stated concisely."},"tags":{"type":"array","items":{"type":"string"},"description":"Tags for later relevance recall."},"expiresAt":{"type":"string","description":"ISO 8601 — omit for permanent facts."},"pinned":{"type":"boolean","description":"Pin to always surface in the prompt index (rare; for must-know facts)."}},"required":["locationId","topic","content"]},"access":"private"},{"name":"recall_research","description":"Recall persisted external research for this store (competitor_pricing, local_events, review_sentiment, peer_benchmark, cuisine_trends, ...). Pass a `query` for semantic search across all findings, a `topic` for its latest finding, or neither for the latest of every topic. Each result includes ageDays + stale, so you know whether to use it or re-gather. Use this BEFORE WebSearch — if a fresh finding is on file, don't re-crawl.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"query":{"type":"string","description":"Free-text — semantic search across all research findings."},"topic":{"type":"string","description":"Research topic (e.g. \"competitor_pricing\"). Omit to list the latest of every topic."},"limit":{"type":"number","description":"How many findings to return (default 1 for topic / 5 for query, max ~20)."}},"required":["locationId"]},"access":"private"},{"name":"write_research","description":"Persist a research finding you just gathered (via WebSearch/WebFetch) so it's saved for future cycles instead of re-crawled. Distil what you found into a concise summary + optional structured data + the source URLs. Pick a stable topic (competitor_pricing, local_events, review_sentiment, peer_benchmark, cuisine_trends). A sensible refresh cadence is set per topic automatically.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"topic":{"type":"string","description":"Stable topic key (e.g. \"competitor_pricing\")."},"subject":{"type":"string","description":"What you researched (e.g. \"lunch pricing within 1mi\")."},"summary":{"type":"string","description":"The distilled finding the AI will read later — concise."},"data":{"type":"object","description":"Optional structured detail (prices, event list, etc.)."},"sources":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string"},"url":{"type":"string"}}},"description":"Citations."},"confidence":{"type":"string","description":"high | medium | low."}},"required":["locationId","topic","subject","summary"]},"access":"private"},{"name":"get_sales_patterns","description":"This store's own sales rhythm from order history (default 90 days), bucketed in the store's LOCAL timezone: busiest/slowest day-of-week, busiest hour, full day + hour distributions, and the last-7-days-vs-prior-7 trend. Use it to TIME plays (when to run a promo, which daypart to push) and to read momentum — grounded in this store's numbers, not generic assumptions.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"days":{"type":"number","description":"Lookback window in days (default 90)."}},"required":["locationId"]},"access":"private"},{"name":"get_loyalty_program","description":"Current loyalty program(s) for this location (any non-archived). Returns type, status, thresholds. Empty array = no program yet.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"get_promotions","description":"Current promotions for this location. Status filter: active | paused | scheduled | all (default).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"status":{"type":"string"}},"required":["locationId"]},"access":"private"},{"name":"get_catering_menu","description":"Catering menu items for this location. Empty array = catering not yet enabled.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"}},"required":["locationId"]},"access":"private"},{"name":"notify_owner","description":"Reach the store owner for a NOVEL, owner-worthy event (a fresh insight, an anomaly, a notable result you just produced). Always writes to dashboard (Latest Brief); layers email + SMS by urgency. For an action that needs APPROVAL, use propose_action (do not notify for approvals).\nDO NOT use this for recurring status reminders — setup/activation next-steps (connect payments, add menu, get first order) and pending money-out approvals are reminded by the SYSTEM on the right cadence. Re-announcing them here is per-cycle spam. (See get_outstanding_items to understand what's already handled.)\nURGENCY (drives channels): critical = needs action now → email + SMS. high = important action/milestone → email + SMS. medium = useful FYI → email only. low = minor → dashboard + digest.\nCalling repeatedly is SAFE: notifies de-dupe per (store, dedupeKey/title) within a cooldown, and non-critical email is capped per store — but the right fix for recurring status is simply not to send it here. Pass a stable dedupeKey for any notice that could recur.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"title":{"type":"string"},"summary":{"type":"string"},"body":{"type":"string","description":"Full message content shown to the owner, verbatim. Falls back to summary if omitted."},"reviewUrl":{"type":"string"},"urgency":{"type":"string","enum":["low","medium","high","critical"]},"channel":{"type":"string","enum":["dashboard","email","sms","digest","auto"]},"kind":{"type":"string"},"dedupeKey":{"type":"string","description":"Stable id for a recurring notice so re-sends are suppressed within the cooldown."}},"required":["locationId","title","summary"]},"access":"private"},{"name":"claim_pending_invocation","description":"Engine-only: atomically claim the next pending invocation from the queue. Returns the row or null if nothing pending. Multiple agents can poll concurrently — claim is atomic.","inputSchema":{"type":"object","properties":{"claimedBy":{"type":"string"}},"required":["claimedBy"]},"access":"private"},{"name":"mark_invocation_completed","description":"Engine reports successful completion of a pending invocation. Owner-facing surfacing is then automatic via notify_pref.","inputSchema":{"type":"object","properties":{"invocationId":{"type":"string"},"resultSummary":{"type":"string"},"resultMetadata":{"type":"object"}},"required":["invocationId","resultSummary"]},"access":"private"},{"name":"mark_invocation_failed","description":"Engine reports failure. First failure → row resets to pending for retry. Second failure → status=failed, error visible to owner.","inputSchema":{"type":"object","properties":{"invocationId":{"type":"string"},"error":{"type":"string"}},"required":["invocationId","error"]},"access":"private"},{"name":"delegate_to_remote","description":"Hand work to the remote AI agent for background processing. Use when the request would take more than a few seconds, needs external research, or is multi-step. Returns { invocationId, ownerFacingMessage } — tell the owner what to expect. The remote agent polls + runs + posts the result back; the dashboard or chat will surface it when ready.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"trigger":{"type":"string"},"payload":{"type":"object"},"ownerFacingMessage":{"type":"string"},"expectedEtaSeconds":{"type":"number"},"notifyPref":{"type":"string"}},"required":["locationId","trigger","ownerFacingMessage"]},"access":"private"},{"name":"check_delegated_tasks","description":"See what delegations are in flight (still working), completed but not yet shown to owner, or recently done. Use to naturally surface completed work in chat — \"By the way, that catering research I started yesterday is done — want to see it?\" — without depending on owner having seen the dashboard.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"since":{"type":"string"}},"required":["locationId"]},"access":"private"},{"name":"get_action_approval_url","description":"Get the public no-auth approval URL for a pending action id. Returns /a/[token] — include in SMS/email so owner can approve from their phone without logging in. (propose_action already returns approvalUrl; use this only to re-fetch.)","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"actionId":{"type":"string"}},"required":["locationId","actionId"]},"access":"private"},{"name":"verify_action","description":"Re-check whether an applied action actually took effect — reads the change back from canonical state. Use after a fix/retry, or to confirm a play is live. Returns { ok, status: \"verified\" | \"failed\" | \"unknown\" | \"not_applicable\", detail }. \"not_applicable\" = no read-back exists for that type (rely on its status instead).","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"actionId":{"type":"string","description":"The action id returned by propose_action."}},"required":["locationId","actionId"]},"access":"private"},{"name":"propose_action","description":"The universal write path — your hands. You are a hired manager: DO THE JOB. REVERSIBLE actions (promo, menu, SEO, loyalty/referral, hours, website, drafts, reports — everything except a money-out customer blast) AUTO-EXECUTE immediately; you do NOT wait for approval, and the owner can trace/undo. Only IRREVERSIBLE / money-out actions (campaign_send) stay pending for owner approval (the owner is nudged aggressively). Pass `scheduledFor` (ISO timestamp) to run a reversible action at the OPTIMAL time instead of now (e.g. schedule a promo/SEO push or customer comms for Tuesday morning, not the midnight you decided it). Returns { actionId, status (auto_applied | scheduled | pending_approval | failed), approvalUrl, applyResult? }. For error-prone types the substrate reads the change back and stamps applyResult.verification = { status: \"verified\" | \"failed\" | \"unknown\", detail }. DONE only when status='auto_applied' AND verification.status='verified' (or there's no verification field — that type has no read-back, trust the status). verification 'failed' = did not take (fix it); 'unknown' = applied but unconfirmed (say so, don't claim confirmed); pending/scheduled are NOT done. Never claim success on a failed/unverified result.\n\nFor type='report_write' (auto-applies — reports are content, not decisions), the payload MUST include:\n  - title: report title\n  - body: full markdown report\n  - kind: REQUIRED — one of welcome | daily | weekly | monthly | audit | anomaly | research | catering | marketing | menu | other. Drives filter chips + digest grouping.\n  - serviceName: stable provenance tag (e.g. 'welcome_package', 'weekly_review')\n  - summary: optional one-line for the inbox row\n\nThe applyResult.reviewUrl points to the dashboard report view URL — use it as the notify_owner reviewUrl.\n\n`type` MUST be EXACTLY one of these registry names and `payload` must match the listed fields/types/enums (auto-generated from the live schemas — never invent a name like \"enable_service\" or guess an enum value; for the freshest copy any time, call get_action_catalog):\n  campaign_send [needs owner approval] — { campaignId: string, playType?: string, holdoutPercent?: number, promoCode?: string, promotionId?: string }\n  menu_item_update — { itemId: string, updates: {…} }\n  menu_price_update — { itemId: string, newPriceCents: number, reason?: string }\n  promo_create — { name?: string, promotionType?: 'percentage_off'|'fixed_amount_off', discountValue: number, code?: string, validDays?: number, startsInDays?: number, maxUsesPerCustomer?: number, minOrderAmount?: number }\n  menu_item_create — { input: {…} }\n  seo_update — { description?: string, cuisineType?: string, storeIntro?: string }\n  website_publish — { draftId?: string }\n  store_hours_update — { closures?: {…}[], regularHours?: {…}[] }\n  menu_item_86 — { itemId: string, reason?: string, expiresAt?: string }\n  menu_item_restore — { itemId: string }\n  guardrails_update — { maxDiscountPercent?: number, maxPriceChangePercent?: number }\n  website_seo_update — { title?: string, description?: string, ogImage?: string }\n  ai_discoverability_update — { cuisineTags?: string[], priceRange?: string, highlights?: string, storeInstructions?: string }\n  campaign_draft_create — { name: string, campaignType: 'email'|'sms'|'both', sourceType: 'ai_suggested'|'ai_auto', segmentId?: string, emailSubject?: string, emailContent?: string, smsContent?: string, description?: string }\n  loyalty_program_create — { name: string, description?: string, programType: 'points'|'punch_card'|'spend_threshold'|'cashback'|'tiered', pointsPerDollar?: number, pointsRedemptionRate?: number, pointsExpireDays?: number, punchesRequired?: number, punchQualifyingAmount?: number, spendThreshold?: number, spendPeriodDays?: number, cashbackPercent?: number, cashbackMinOrder?: number, autoEnroll?: boolean }\n  report_write — { title: string, body: string, kind?: 'welcome'|'daily'|'weekly'|'monthly'|'audit'|'anomaly'|'research'|'catering'|'marketing'|'menu'|'other', serviceName: string, summary?: string }","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"type":{"type":"string"},"title":{"type":"string"},"payload":{"type":"object"},"description":{"type":"string"},"riskLevel":{"type":"string"},"reason":{"type":"string"},"scheduledFor":{"type":"string"}},"required":["locationId","type","title","payload"]},"access":"private"},{"name":"execute_chat_action","description":"Owner-present chat: apply an action immediately (low/medium risk) or queue as pending_confirmation (high risk). Use ONLY when the owner is present in chat AND asked for the action. Wraps lib/ai-manager/actions/queue.executeChatAction.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"type":{"type":"string"},"title":{"type":"string"},"payload":{"type":"object"}},"required":["locationId","type","payload"]},"access":"private"},{"name":"ask_owner","description":"Pose a question to the owner asynchronously. The question lands as a notification + dashboard surface. Owner answers later via chat or a quick form. Agent does NOT block — it records the question and continues with other work. The answer becomes a knowledge entry; next agent invocation sees it. Use this only for things ONLY the owner can answer (brand voice, preferences, decisions). Don't use for info you can look up via read tools.","inputSchema":{"type":"object","properties":{"locationId":{"type":"string","description":"Location ID (UUID) to scope the request"},"question":{"type":"string"},"reason":{"type":"string"},"options":{"type":"array"},"urgency":{"type":"string"},"resumeWith":{"type":"object"},"dedupeKey":{"type":"string"}},"required":["locationId","question","reason"]},"access":"private"}],"auth":{"public_tools":"No authentication required for search_stores and get_store_context","action_tools":"No store OAuth required. Customer provides identity in the request.","private_tools":"API key required: Authorization: Bearer sk_xxx"},"usage":{"method":"POST","path":"/api/mcp","format":"JSON-RPC 2.0","examples":[{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"example","version":"1.0"}}},{"jsonrpc":"2.0","id":2,"method":"tools/list"},{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_stores","arguments":{"cuisine":"pizza"}}}]}}