Skip to main content
Glama
alcylu

Nightlife Search

by alcylu

create_vip_booking_request

Submit VIP table booking requests to nightlife venues that support VIP reservations. This tool sends booking details directly to venue booking desks for processing.

Instructions

Create a VIP table booking request and send it directly to the venue booking desk. The venue must have vip_booking_supported=true. Before calling this tool, always confirm booking date and arrival time in venue local time. For arrivals from 00:00 to 05:59, use the 'night + actual day' format to avoid midnight confusion. Required format: '[Night] night, [time] ([Actual Day] [time])' — e.g., 'Friday night, 2am (Saturday 2am)'. Example confirmation: 'So you're coming Friday night, 2am (Saturday 2am), table for 4 at Zouk?' If the user gives a time like 2am without a day, ask: 'Do you mean Thursday night, 2am (Friday morning), or Friday night, 2am (Saturday morning)?' If the user changes the requested day, regenerate confirmation before calling this tool.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
venue_idYes
booking_dateYes
arrival_timeYes
party_sizeYes
customer_nameYes
customer_emailYes
customer_phoneYes
preferred_table_codeNo
special_requestsNo

Implementation Reference

  • The main handler function `createVipBookingRequest` which processes the VIP booking creation request, validates inputs, interacts with Supabase to insert the booking, and triggers related events and tasks.
    export async function createVipBookingRequest(
      supabase: SupabaseClient,
      input: CreateVipBookingRequestInput,
      options?: { resendApiKey?: string },
    ): Promise<VipBookingCreateResult> {
      const venueId = ensureUuid(input.venue_id, "venue_id");
      const bookingDate = normalizeBookingDate(input.booking_date);
      const arrivalTime = normalizeArrivalTime(input.arrival_time);
      const partySize = normalizePartySize(input.party_size);
      const customerName = normalizeCustomerName(input.customer_name);
      const customerEmail = normalizeCustomerEmail(input.customer_email);
      const customerPhone = normalizeCustomerPhone(input.customer_phone);
      const preferredTableCode = normalizeOptionalTableCode(input.preferred_table_code);
      const specialRequests = normalizeOptionalText(input.special_requests, 2000);
    
      const window = await resolveBookingWindow(supabase, venueId);
      if (bookingDate < window.currentServiceDate || bookingDate > window.maxServiceDate) {
        throw new NightlifeError(
          "INVALID_BOOKING_REQUEST",
          `booking_date must be between ${window.currentServiceDate} and ${window.maxServiceDate}.`,
        );
      }
    
      let minSpend: number | null = null;
      let minSpendCurrency: string | null = null;
      let tableWarning: string | null = null;
    
      if (preferredTableCode) {
        const pricing = await lookupTablePricing(supabase, venueId, preferredTableCode, bookingDate);
        if (!pricing.found) {
          tableWarning = `Table "${preferredTableCode}" was not found in our system for this venue. The booking request has been submitted and the venue will confirm table availability.`;
        }
        minSpend = pricing.minSpend;
        minSpendCurrency = pricing.currency;
      }
    
      const { data: created, error: createError } = await supabase
        .from("vip_booking_requests")
        .insert({
          venue_id: venueId,
          booking_date: bookingDate,
          arrival_time: arrivalTime,
          party_size: partySize,
          customer_name: customerName,
          customer_email: customerEmail,
          customer_phone: customerPhone,
          preferred_table_code: preferredTableCode,
          min_spend: minSpend,
          min_spend_currency: minSpendCurrency,
          special_requests: specialRequests,
          status: "submitted",
          status_message: DEFAULT_STATUS_MESSAGE,
        })
        .select("id,status,created_at,status_message")
        .single<VipBookingInsertRow>();
    
      if (createError || !created) {
        throw new NightlifeError("REQUEST_WRITE_FAILED", "Failed to create VIP booking request.", {
          cause: createError?.message || "Unknown insert error",
        });
      }
    
      const { error: eventError } = await supabase
        .from("vip_booking_status_events")
        .insert({
          booking_request_id: created.id,
          from_status: null,
          to_status: "submitted",
          actor_type: "customer",
          note: "VIP booking request sent to venue booking desk.",
        });
    
      const { error: taskError } = await supabase
        .from("vip_agent_tasks")
        .insert({
          booking_request_id: created.id,
          task_type: "new_vip_request",
          status: "pending",
          attempt_count: 0,
          next_attempt_at: new Date().toISOString(),
        });
    
      if (eventError || taskError) {
        throw new NightlifeError(
          "REQUEST_WRITE_FAILED",
          "Failed to submit VIP booking request.",
          {
            cause: {
              status_event_error: eventError?.message || null,
              task_error: taskError?.message || null,
            },
          },
        );
      }
    
      // Send submitted email (fire-and-forget)
      if (options?.resendApiKey) {
        try {
          const { sendBookingSubmittedEmail } = await import("./email.js");
          await sendBookingSubmittedEmail(supabase, options.resendApiKey, created.id);
        } catch (emailError) {
          logEvent("email.send_error", {
            booking_request_id: created.id,
            error: emailError instanceof Error ? emailError.message : "Unknown error",
          });
        }
      }
    
      return {
        booking_request_id: created.id,
        status: created.status,
        created_at: created.created_at,
        message: created.status_message,
        preferred_table_code: preferredTableCode,
        min_spend: minSpend,
        min_spend_currency: minSpendCurrency,
        table_warning: tableWarning,
      };
    }
  • Registration of the `create_vip_booking_request` tool in the MCP server using `registerVipBookingTools`.
    export function registerVipBookingTools(server: McpServer, deps: ToolDeps): void {
      server.registerTool(
        "create_vip_booking_request",
        {
          description: createVipBookingToolDescription,
          inputSchema: createVipBookingInputSchema,
          outputSchema: createVipBookingOutputSchema,
        },
        async (args) => runTool(
          "create_vip_booking_request",
          createVipBookingOutputSchema,
          async () => createVipBookingRequest(deps.supabase, args, {
            resendApiKey: deps.config.resendApiKey ?? undefined,
          }),
        ),
      );
  • Input schema definition for the `create_vip_booking_request` tool using Zod.
    export const createVipBookingInputSchema = {
      venue_id: z.string().min(1),
      booking_date: z.string().min(1),
      arrival_time: z.string().min(1),
      party_size: z.number().int().min(1).max(30),
      customer_name: z.string().min(1),
      customer_email: z.string().min(1),
      customer_phone: z.string().min(1),
      preferred_table_code: z.string().optional(),
      special_requests: z.string().optional(),
    };
  • Output schema definition for the `create_vip_booking_request` tool using Zod.
    export const createVipBookingOutputSchema = z.object({
      booking_request_id: z.string(),
      status: z.enum(["submitted", "in_review", "deposit_required", "confirmed", "rejected", "cancelled"]),
      created_at: z.string(),
      message: z.string(),
      preferred_table_code: z.string().nullable(),
      min_spend: z.number().nullable(),
      min_spend_currency: z.string().nullable(),
      table_warning: z.string().nullable(),
    });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/alcylu/nightlife-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server