get_vip_pricing
Retrieve VIP table pricing and minimum spend requirements for nightlife venues, including weekday/weekend ranges and booking availability.
Instructions
Get VIP pricing information for a venue. Returns honest weekday and weekend minimum spend ranges, zone summaries, table chart image URL, and booking affordance.
WHEN TO CALL: When a user asks about VIP tables, VIP pricing, bottle service costs, minimum spend, or table reservations at a specific venue.
WHAT TO DO AFTER:
Present pricing conversationally ("Weekday minimums start around ¥100K, weekends from ¥200K")
Show table chart URL as a layout reference only — do not infer availability from the image
If booking_supported is true and user is interested, offer to submit an inquiry via create_vip_booking_request
Do NOT suggest specific table codes unless the user asks
If busy_night is true, mention the event name and that demand may be higher on that night
When pricing_approximate is true, use hedging language ("around", "approximately") rather than stating exact figures
When venue_open is false, the venue is closed on that specific date but general pricing ranges for open nights are still included. Present the open nights and pricing to the user.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| venue_id | Yes | ||
| date | No |
Implementation Reference
- src/services/vipPricing.ts:362-483 (handler)The main implementation of the `get_vip_pricing` service function which fetches venue details, pricing information, and availability.
export async function getVipPricing( supabase: SupabaseClient, input: GetVipPricingInput, ): Promise<VipPricingResult> { // 1. Validate UUID const venueId = ensureUuid(input.venue_id, "venue_id"); // 2. Resolve venue const venue = await resolveVenueForPricing(supabase, venueId); const venueName = venue.name; // 3. Resolve city context const city = await fetchPricingCityContext(supabase, venue.city_id); // 4. Determine service date (null when no date requested) let serviceDate: string | null = null; const dateInput = input.date?.trim().toLowerCase(); if (dateInput === "tonight") { serviceDate = getCurrentServiceDate(new Date(), city.timezone, city.cutoff); } else if (dateInput && /^\d{4}-\d{2}-\d{2}$/.test(dateInput)) { serviceDate = dateInput; } // When no date provided: serviceDate stays null — return general pricing only // 5. Run open-day check (only when a specific date was requested) let venueOpen: boolean | null = null; let venueClosedMessage: string | null = null; let eventName: string | null = null; if (serviceDate) { const { closedDates, eventByDate } = await resolvePricingClosedDates(supabase, venueId, [serviceDate], city); venueOpen = !closedDates.has(serviceDate); venueClosedMessage = venueOpen ? null : `${venueName || "This venue"} appears to be closed on ${serviceDate}.`; eventName = eventByDate.get(serviceDate) ?? null; } // 6. Fetch active vip_venue_tables const { data: tableData, error: tableError } = await supabase .from("vip_venue_tables") .select("id,table_code,table_name,metadata,zone,capacity_min,capacity_max") .eq("venue_id", venueId) .eq("is_active", true); if (tableError) { throw new NightlifeError("DB_QUERY_FAILED", "Failed to load VIP venue tables.", { cause: tableError.message, }); } const tables = (tableData || []) as VipVenueTableRow[]; // 7. Fetch ALL vip_table_day_defaults for venue const { data: dayDefaultData, error: dayDefaultError } = await supabase .from("vip_table_day_defaults") .select("vip_venue_table_id,day_of_week,min_spend,currency,note") .eq("venue_id", venueId); if (dayDefaultError) { throw new NightlifeError("DB_QUERY_FAILED", "Failed to load VIP table day defaults.", { cause: dayDefaultError.message, }); } const dayDefaults = (dayDefaultData || []) as VipTableDayDefaultRow[]; // 8. Check per-date overrides from vip_table_availability let eventPricingNote: string | null = null; const { data: dateOverrideData } = await supabase .from("vip_table_availability") .select("min_spend") .eq("venue_id", venueId) .eq("booking_date", serviceDate); const overrideRows = (dateOverrideData || []) as Array<{ min_spend: number | string | null }>; const hasOverridesWithPricing = overrideRows.some((r) => coerceMinSpend(r.min_spend) !== null); if (hasOverridesWithPricing) { eventPricingNote = `Special event pricing may apply on ${serviceDate} — actual minimums may differ from the general ranges shown.`; } // 9. Aggregate pricing const aggregated = aggregatePricing(tables, dayDefaults); // 10. Determine pricing_configured and pricing_approximate const venueDefaultMinSpend = coerceMinSpend(venue.vip_default_min_spend); const pricingConfigured = dayDefaults.length > 0 || venueDefaultMinSpend !== null; const pricingNotConfiguredMessage = pricingConfigured ? null : "VIP pricing information is not yet available for this venue."; const pricingApproximate = dayDefaults.length === 0 && venueDefaultMinSpend !== null; // 11. Extract layout_image_url const layoutImageUrl = tables.map((t) => extractLayoutImageUrl(t.metadata)).find(Boolean) || null; // 12. Determine booking fields const bookingSupported = venue.vip_booking_enabled === true; // 13. Return result const busyNight = eventName !== null; return { venue_id: venueId, venue_name: venueName, venue_open: venueOpen === null ? true : venueOpen, venue_closed_message: venueClosedMessage, pricing_configured: pricingConfigured, pricing_not_configured_message: pricingNotConfiguredMessage, weekday_min_spend: aggregated.weekday_min_spend, weekend_min_spend: aggregated.weekend_min_spend, currency: aggregated.currency, zones: aggregated.zones, layout_image_url: layoutImageUrl, booking_supported: bookingSupported, booking_note: null, generated_at: new Date().toISOString(), service_date: serviceDate, event_pricing_note: eventPricingNote, event_name: eventName, busy_night: busyNight, pricing_approximate: pricingApproximate, }; } - src/tools/vipTables.ts:124-138 (registration)Registration of the `get_vip_pricing` tool within the McpServer, including description and schema definitions.
export function registerVipPricingTool(server: McpServer, deps: ToolDeps): void { server.registerTool( "get_vip_pricing", { description: VIP_PRICING_DESCRIPTION, inputSchema: vipPricingInputSchema, outputSchema: vipPricingOutputSchema, }, async (args) => runTool( "get_vip_pricing", vipPricingOutputSchema, async () => getVipPricing(deps.supabase, args), ), ); } - src/tools/vipTables.ts:102-122 (schema)Zod schema definition for the `get_vip_pricing` tool's output.
export const vipPricingOutputSchema = z.object({ venue_id: z.string(), venue_name: z.string().nullable(), venue_open: z.boolean(), venue_closed_message: z.string().nullable(), pricing_configured: z.boolean(), pricing_not_configured_message: z.string().nullable(), weekday_min_spend: z.number().nullable(), weekend_min_spend: z.number().nullable(), currency: z.string(), zones: z.array(vipZonePricingSummarySchema), layout_image_url: z.string().nullable(), booking_supported: z.boolean(), booking_note: z.string().nullable(), generated_at: z.string(), service_date: z.string().nullable(), event_pricing_note: z.string().nullable(), event_name: z.string().nullable(), busy_night: z.boolean(), pricing_approximate: z.boolean(), });