Skip to main content
Glama
alcylu

Nightlife Search

by alcylu

search_performers

Find live music performers in a specific city and date range, with options to filter by genre or search terms to discover nightlife entertainment.

Instructions

Search performers by city/date window with optional genre and text query filters.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cityNotokyo
dateNo
genreNo
queryNo
sort_byNopopularity
limitNo
offsetNo

Implementation Reference

  • Registration of the 'search_performers' MCP tool.
    server.registerTool(
      "search_performers",
      {
        description:
          "Search performers by city/date window with optional genre and text query filters.",
        inputSchema: {
          city: z.string().default(deps.config.defaultCity),
          date: z.string().optional(),
          genre: z.string().optional(),
          query: z.string().optional(),
          sort_by: z
            .enum(["popularity", "recent_activity", "alphabetical", "rising_stars"])
            .default("popularity"),
          limit: z.number().int().min(1).max(20).default(10),
          offset: z.number().int().min(0).default(0),
        },
        outputSchema: searchPerformersOutputSchema,
      },
      async (args) => runTool(
        "search_performers",
        searchPerformersOutputSchema,
        async () => searchPerformers(deps.supabase, deps.config, args),
      ),
    );
  • Zod schema defining the output structure of the 'search_performers' tool.
    const searchPerformersOutputSchema = z.object({
      city: z.string(),
      date_filter: z.string().nullable(),
      performers: z.array(performerSummarySchema),
      unavailable_city: cityUnavailableSchema.nullable(),
    });
  • The actual implementation of the searchPerformers logic that the tool handler calls.
    export async function searchPerformers(
      supabase: SupabaseClient,
      config: AppConfig,
      input: SearchPerformersInput,
    ): Promise<SearchPerformersOutput> {
      const citySlug = normalizeCity(input.city, config.defaultCity);
      const city = await getCityContext(supabase, citySlug, config.defaultCountryCode);
    
      if (!city) {
        return {
          city: citySlug,
          date_filter: input.date || null,
          performers: [],
          unavailable_city: await unavailableCityPayload(
            supabase,
            citySlug,
            config.nightlifeBaseUrl,
            config.topLevelCities,
          ),
        };
      }
    
      const now = new Date();
      let parsedDate: ReturnType<typeof parseDateFilter>;
      try {
        parsedDate = parseDateFilter(input.date, now, city.timezone, city.serviceDayCutoffTime);
      } catch (error) {
        const message = error instanceof Error ? error.message : "Invalid date filter.";
        throw new NightlifeError("INVALID_DATE_FILTER", message);
      }
    
      const currentServiceDate = getCurrentServiceDate(now, city.timezone, city.serviceDayCutoffTime);
      const startServiceDate = parsedDate?.startServiceDate || currentServiceDate;
      const endServiceDateExclusive =
        parsedDate?.endServiceDateExclusive || addDaysToIsoDate(currentServiceDate, 31);
      const window = serviceDateWindowToUtc(
        startServiceDate,
        endServiceDateExclusive,
        city.timezone,
        city.serviceDayCutoffTime,
      );
    
      const { data: occurrenceRows, error: occurrenceError } = await supabase
        .from("event_occurrences")
        .select("id,start_at,occurrence_days:event_occurrence_days(id,service_date,start_at,end_at,published,title_en_override,title_i18n_override)")
        .eq("published", true)
        .eq("city_id", city.id)
        .gte("start_at", window.startIso)
        .lt("start_at", window.endIso)
        .order("start_at", { ascending: true })
        .range(0, 1999);
    
      if (occurrenceError) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch city events.", {
          cause: occurrenceError.message,
        });
      }
    
      const occurrences = (occurrenceRows || []) as unknown as Array<{
        id: string;
        start_at: string | null;
        occurrence_days: EventOccurrenceRow["occurrence_days"];
      }>;
    
      if (occurrences.length === 0) {
        return {
          city: city.slug,
          date_filter: parsedDate?.label || null,
          performers: [],
          unavailable_city: null,
        };
      }
    
      const eventIds = occurrences.map((row) => row.id);
      const occurrenceDateById = new Map<string, string | null>();
      for (const row of occurrences) {
        occurrenceDateById.set(row.id, primaryDay(row.occurrence_days)?.start_at || row.start_at || null);
      }
    
      const stageRows: EventStageRow[] = [];
      for (const idsChunk of chunkArray(eventIds, 100)) {
        const { data, error } = await supabase
          .from("event_stages")
          .select("id,event_id,custom_name,venue_stage:venue_stages(name)")
          .in("event_id", idsChunk);
    
        if (error) {
          throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch event stages.", {
            cause: error.message,
          });
        }
    
        stageRows.push(...((data || []) as unknown as EventStageRow[]));
      }
    
      const stageById = new Map<string, EventStageRow>();
      for (const stage of stageRows) {

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