Skip to main content
Glama
alcylu

Nightlife Search

by alcylu

get_event_details

Retrieve comprehensive event information using a specific UUID to access details like venue, performers, schedules, and ticket availability for nightlife planning.

Instructions

Get full details for a specific event occurrence ID (UUID).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
event_idYes

Implementation Reference

  • Implementation of getEventDetails service function, responsible for fetching event occurrence data, ticket tiers, guest list settings, and formatting the response.
    export async function getEventDetails(
      supabase: SupabaseClient,
      config: AppConfig,
      eventId: string,
    ): Promise<EventDetail | null> {
      const cleanedId = eventId.trim();
      if (!cleanedId) {
        throw new NightlifeError("INVALID_EVENT_ID", "event_id cannot be blank.");
      }
    
      if (cleanedId.startsWith("virtual:")) {
        throw new NightlifeError(
          "UNSUPPORTED_EVENT_ID",
          "virtual:* IDs are reserved for future support. Use a concrete event occurrence ID.",
        );
      }
      if (!UUID_RE.test(cleanedId)) {
        throw new NightlifeError(
          "INVALID_EVENT_ID",
          "event_id must be a UUID in v1.",
        );
      }
    
      const { data: occurrence, error } = await supabase
        .from("event_occurrences")
        .select(
          "id,city_id,venue_id,name_en,name_i18n,description_en,description_i18n,start_at,end_at,published,featured,source,source_url,entrance_costs,venue:venues(id,name,name_en,name_ja,address,address_en,address_ja,city,city_en,city_ja,website),occurrence_days:event_occurrence_days(id,service_date,start_at,end_at,published,title_en_override,title_i18n_override)",
        )
        .eq("id", cleanedId)
        .eq("published", true)
        .maybeSingle<EventOccurrenceRow>();
    
      if (error) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch event details.", {
          cause: error.message,
        });
      }
      if (!occurrence) {
        return null;
      }
    
      const cityQuery = occurrence.city_id
        ? await supabase
            .from("cities")
            .select("slug,timezone,service_day_cutoff_time,country_code")
            .eq("id", occurrence.city_id)
            .maybeSingle()
        : { data: null };
    
      const citySlug =
        (cityQuery.data?.slug as string | undefined)?.toLowerCase() ||
        config.defaultCity;
      const fallbackCurrency = defaultCurrencyForCountry(
        (cityQuery.data?.country_code as string | undefined) || config.defaultCountryCode,
      );
      const timezone = (cityQuery.data?.timezone as string | undefined) || "UTC";
      const cutoffTime =
        (cityQuery.data?.service_day_cutoff_time as string | undefined) || "06:00";
    
      const dayIds = (occurrence.occurrence_days || [])
        .map((day) => day.id)
        .filter(Boolean);
    
      const [metadata, ticketOccurrenceResult, ticketDayResult, guestSettingsResult] =
        await Promise.all([
          fetchOccurrenceMetadata(supabase, [occurrence.id]),
          supabase
            .from("event_ticket_tiers")
            .select("tier_name,price,currency,status,url,provider")
            .eq("occurrence_id", occurrence.id),
          dayIds.length > 0
            ? supabase
                .from("event_ticket_tiers")
                .select("tier_name,price,currency,status,url,provider,event_day_id")
                .in("event_day_id", dayIds)
            : Promise.resolve({ data: [], error: null }),
          dayIds.length > 0
            ? supabase
                .from("event_guest_list_settings")
                .select("event_day_id,enabled,capacity,cutoff_time")
                .in("event_day_id", dayIds)
            : Promise.resolve({ data: [], error: null }),
        ]);
    
      if (ticketOccurrenceResult.error) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch ticket tiers.", {
          cause: ticketOccurrenceResult.error.message,
        });
      }
      if (ticketDayResult.error) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch day ticket tiers.", {
          cause: ticketDayResult.error.message,
        });
      }
      if (guestSettingsResult.error) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch guest list settings.", {
          cause: guestSettingsResult.error.message,
        });
      }
    
      const tiers = [
        ...(ticketOccurrenceResult.data || []),
        ...(ticketDayResult.data || []),
      ]
        .map((row) => ({
          tier_name: String(row.tier_name || "Unknown Tier"),
          price: typeof row.price === "number" ? row.price : null,
          currency: row.currency || null,
          status: row.status || "on_sale",
          url: row.url || null,
          provider: row.provider || null,
        }))
        .sort((a, b) => a.tier_name.localeCompare(b.tier_name));
    
      const lowerTier = (value: string) => value.toLowerCase();
      const doorTier = tiers.find((tier) => lowerTier(tier.tier_name).includes("door"));
      const advanceTier = tiers.find((tier) => {
        const value = lowerTier(tier.tier_name);
        return value.includes("advance") || value.includes("presale") || value.includes("early");
      });
    
      const day = primaryDay(occurrence.occurrence_days);
      const settings = (guestSettingsResult.data || []).find(
        (row) => row.event_day_id === day?.id,
      );
    
      let guestStatus: "available" | "full" | "closed" = "closed";
      if (settings?.enabled === true && day?.service_date) {
        const cutoffPassed = isCutoffPassedForServiceDate(
          new Date(),
          day.service_date,
          timezone,
          settings.cutoff_time || cutoffTime,
        );
    
        if (!cutoffPassed) {
          if (typeof settings.capacity === "number" && settings.capacity >= 0) {
            const countResult = await supabase
              .from("event_guest_list_entries")
              .select("id", { count: "exact", head: true })
              .eq("event_day_id", day.id);
    
            if (countResult.error) {
              throw new NightlifeError(
                "DB_QUERY_FAILED",
                "Failed to fetch guest list counts.",
                { cause: countResult.error.message },
              );
            }
    
            const currentCount = countResult.count || 0;
            guestStatus = currentCount >= settings.capacity ? "full" : "available";
          } else {
            guestStatus = "available";
          }
        }
      }
    
      const flyer = metadata.mediaByEvent.get(occurrence.id)?.[0]?.media_url || null;
      const lineup = metadata.lineupByEvent.get(occurrence.id) || [];
      const venue = firstRelation(occurrence.venue);
      const address = venue?.address_en || venue?.address || venue?.address_ja || null;
    
      return {
        event_id: occurrence.id,
        name: eventName(occurrence),
        date: day?.start_at || occurrence.start_at || "",
        start_time: day?.start_at || occurrence.start_at || null,
        end_time: day?.end_at || occurrence.end_at || null,
        service_date: day?.service_date || null,
        venue: {
          id: venue?.id || occurrence.venue_id || "",
          name: venueName(venue),
          area: venueArea(venue),
          address,
          map_link: buildMapLink(address),
          website: venue?.website || null,
        },
        lineup,
        genres: metadata.genresByEvent.get(occurrence.id) || [],
        price: {
          entrance_summary: summarizeEntranceCosts(occurrence.entrance_costs, fallbackCurrency),
          door: doorTier ? tierText(doorTier, fallbackCurrency) : null,
          advance: advanceTier ? tierText(advanceTier, fallbackCurrency) : null,
          tiers,
        },
        flyer_url: flyer,
        guest_list_status: guestStatus,
        nlt_url: buildEventUrl(config.nightlifeBaseUrl, citySlug, occurrence.id),
      };
    }
  • Registration of the get_event_details MCP tool.
    server.registerTool(
      "get_event_details",
      {
        description:
          "Get full details for a specific event occurrence ID (UUID).",
        inputSchema: {
          event_id: z.string().min(1),
        },
        outputSchema: eventDetailSchema,
      },
      async ({ event_id }) => runTool(
        "get_event_details",
        eventDetailSchema,
        async () => {
          const detail = await getEventDetails(deps.supabase, deps.config, event_id);
          if (!detail) {
            throw new NightlifeError(
              "EVENT_NOT_FOUND",
              `Event not found: ${event_id}`,
            );
          }
          return detail;
        },
      ),
    );
  • Schema definition for the event details output.
    const eventDetailSchema = z.object({
      event_id: z.string(),
      name: z.string(),
      date: z.string(),
      start_time: z.string().nullable(),
      end_time: z.string().nullable(),
      service_date: z.string().nullable(),
      venue: z.object({
        id: z.string(),
        name: z.string(),
        area: z.string().nullable(),
        address: z.string().nullable(),
        map_link: z.string().nullable(),
        website: z.string().nullable(),
      }),
      lineup: z.array(
        z.object({
          stage: z.string().nullable(),
          performer_name: z.string(),
          start_time: z.string().nullable(),
          end_time: z.string().nullable(),
        }),
      ),
      genres: z.array(z.string()),
      price: z.object({
        entrance_summary: z.string().nullable(),
        door: z.string().nullable(),
        advance: z.string().nullable(),
        tiers: z.array(
          z.object({
            tier_name: z.string(),
            price: z.number().nullable(),
            currency: z.string().nullable(),
            status: z.string(),
            url: z.string().nullable(),
            provider: z.string().nullable(),
          }),
        ),
      }),
      flyer_url: z.string().nullable(),
      guest_list_status: z.enum(["available", "full", "closed"]),
      nlt_url: z.string(),
    });

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