Skip to main content
Glama
alcylu

Nightlife Search

by alcylu

get_performer_info

Retrieve performer details including social links and upcoming events snapshot using a performer ID. This tool helps users access comprehensive information about music artists and their scheduled performances.

Instructions

Get performer details by performer ID, including social links and upcoming events snapshot.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
performer_idYes

Implementation Reference

  • The implementation of getPerformerInfo which queries the database for performer details, genres, media, social links, and upcoming events.
    export async function getPerformerInfo(
      supabase: SupabaseClient,
      config: AppConfig,
      performerId: string,
    ): Promise<PerformerDetail | null> {
      const cleanedId = performerId.trim();
      if (!cleanedId) {
        throw new NightlifeError("INVALID_PERFORMER_ID", "performer_id cannot be blank.");
      }
      if (!UUID_RE.test(cleanedId)) {
        throw new NightlifeError("INVALID_PERFORMER_ID", "performer_id must be a UUID.");
      }
    
      const { data: performer, error: performerError } = await supabase
        .from("performers")
        .select("id,name,name_en,name_ja,slug,bio,bio_en,follower_count,ranking_score,is_hot_rising_star")
        .eq("id", cleanedId)
        .maybeSingle<PerformerRow>();
    
      if (performerError) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch performer details.", {
          cause: performerError.message,
        });
      }
      if (!performer) {
        return null;
      }
    
      const genresByPerformer = await fetchGenresByPerformer(supabase, [performer.id]);
      const imageByPerformer = await fetchPrimaryImagesByPerformer(supabase, [performer.id]);
    
      const { data: socialRows, error: socialError } = await supabase
        .from("performer_social_links")
        .select("username,full_url,platform:social_media_platforms(name)")
        .eq("performer_id", performer.id);
    
      if (socialError) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch performer social links.", {
          cause: socialError.message,
        });
      }
    
      const socialLinks = ((socialRows || []) as unknown as SocialLinkRow[])
        .map((row) => {
          const platform = firstRelation(row.platform);
          return {
            platform: platform?.name || "unknown",
            username: row.username,
            url: row.full_url || null,
          };
        })
        .sort((a, b) => a.platform.localeCompare(b.platform));
    
      const { data: timetableRows, error: timetableError } = await supabase
        .from("event_timetables")
        .select("event_stage_id,start_time,end_time")
        .eq("performer_id", performer.id);
    
      if (timetableError) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch performer timetable slots.", {
          cause: timetableError.message,
        });
      }
    
      const timetableSlots = (timetableRows || []) as Array<{
        event_stage_id: string;
        start_time: string | null;
        end_time: string | null;
      }>;
    
      if (timetableSlots.length === 0) {
        const defaultCity = config.defaultCity;
        return {
          performer_id: performer.id,
          name: performer.name_en || performer.name || performer.name_ja || "Unknown Performer",
          slug: performer.slug,
          bio: performer.bio_en || performer.bio || null,
          follower_count: performer.follower_count,
          ranking_score: performer.ranking_score,
          genres: genresByPerformer.get(performer.id) || [],
          image_url: imageByPerformer.get(performer.id) || null,
          social_links: socialLinks,
          upcoming_events: [],
          nlt_url: buildPerformerUrl(config.nightlifeBaseUrl, defaultCity, performer.slug || performer.id),
        };
      }
    
      const stageIds = Array.from(new Set(timetableSlots.map((row) => row.event_stage_id)));
      const stageRows: EventStageRow[] = [];
    
      for (const idsChunk of chunkArray(stageIds, 100)) {
        const { data, error } = await supabase
          .from("event_stages")
          .select("id,event_id,custom_name,venue_stage:venue_stages(name)")
          .in("id", idsChunk);
    
        if (error) {
          throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch performer stages.", {
            cause: error.message,
          });
        }
    
        stageRows.push(...((data || []) as unknown as EventStageRow[]));
      }
    
      const stageById = new Map<string, EventStageRow>();
      for (const stage of stageRows) {
        stageById.set(stage.id, stage);
      }
    
      const eventIds = Array.from(
        new Set(
          timetableSlots
            .map((slot) => stageById.get(slot.event_stage_id)?.event_id)
            .filter((value): value is string => Boolean(value)),
        ),
      );
    
      const { data: occurrenceRows, error: occurrenceError } = await supabase
        .from("event_occurrences")
        .select(OCCURRENCE_SELECT)
        .in("id", eventIds)
        .eq("published", true)
        .gte("start_at", new Date().toISOString())
        .order("start_at", { ascending: true });
    
      if (occurrenceError) {
        throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch performer events.", {
          cause: occurrenceError.message,
        });
      }
    
      const occurrences = (occurrenceRows || []) as unknown as EventOccurrenceRow[];
      const occurrenceById = new Map<string, EventOccurrenceRow>();
      for (const row of occurrences) {
        occurrenceById.set(row.id, row);
      }
    
      const cityIds = Array.from(
        new Set(occurrences.map((row) => row.city_id).filter((value): value is string => Boolean(value))),
      );
      const cityById = new Map<string, CityRow>();
      if (cityIds.length > 0) {
        const { data: cityRows, error: cityError } = await supabase
          .from("cities")
          .select("id,slug,country_code")
          .in("id", cityIds);
    
        if (cityError) {
          throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch city metadata.", {
            cause: cityError.message,
          });
        }
    
        for (const row of (cityRows || []) as unknown as CityRow[]) {
          cityById.set(row.id, row);
        }
      }
    
      const metadata = await fetchOccurrenceMetadata(
        supabase,
        occurrences.map((row) => row.id),
      );
    
      const upcomingByEvent = new Map<string, PerformerUpcomingEvent>();
      let firstUpcomingCitySlug: string | null = null;
      for (const slot of timetableSlots) {
        const stage = stageById.get(slot.event_stage_id);
        if (!stage) {
          continue;
        }
    
        const occurrence = occurrenceById.get(stage.event_id);
        if (!occurrence) {
          continue;
        }
    
        const cityRow = occurrence.city_id ? cityById.get(occurrence.city_id) : null;
        const citySlug = cityRow?.slug?.toLowerCase() || config.defaultCity;
        if (!firstUpcomingCitySlug) {
          firstUpcomingCitySlug = citySlug;
        }
        const fallbackCurrency = defaultCurrencyForCountry(cityRow?.country_code || config.defaultCountryCode);
    
        const eventSummary = toEventSummary(
          occurrence,
          citySlug,
          config.nightlifeBaseUrl,
          fallbackCurrency,
          metadata,
        );
    
        const stageRef = firstRelation(stage.venue_stage);
        const stageName = stage.custom_name || stageRef?.name || null;
    
        const current = upcomingByEvent.get(eventSummary.event_id);
        if (!current) {
          upcomingByEvent.set(eventSummary.event_id, {
            event: eventSummary,
            stage: stageName,
            set_start_time: slot.start_time || null,
            set_end_time: slot.end_time || null,
          });
          continue;
        }
    
        const currentStart = current.set_start_time || "99:99:99";
        const candidateStart = slot.start_time || "99:99:99";
        if (candidateStart < currentStart) {
          upcomingByEvent.set(eventSummary.event_id, {
            event: eventSummary,
            stage: stageName,
            set_start_time: slot.start_time || null,
            set_end_time: slot.end_time || null,
          });
        }
      }
    
      const upcomingEvents = Array.from(upcomingByEvent.values())
        .sort((a, b) => a.event.date.localeCompare(b.event.date))
        .slice(0, 10);
    
      const detailCitySlug = (firstUpcomingCitySlug || config.defaultCity).toLowerCase();
    
      return {
        performer_id: performer.id,
        name: performer.name_en || performer.name || performer.name_ja || "Unknown Performer",
        slug: performer.slug,
        bio: performer.bio_en || performer.bio || null,
        follower_count: performer.follower_count,
        ranking_score: performer.ranking_score,
        genres: genresByPerformer.get(performer.id) || [],
        image_url: imageByPerformer.get(performer.id) || null,
        social_links: socialLinks,
        upcoming_events: upcomingEvents,
        nlt_url: buildPerformerUrl(
          config.nightlifeBaseUrl,
          detailCitySlug,
          performer.slug || performer.id,
        ),
      };
    }
  • The tool registration for "get_performer_info", which uses the getPerformerInfo service to handle the tool execution.
    server.registerTool(
      "get_performer_info",
      {
        description:
          "Get performer details by performer ID, including social links and upcoming events snapshot.",
        inputSchema: {
          performer_id: z.string().min(1),
        },
        outputSchema: performerDetailSchema,
      },
      async ({ performer_id }) => runTool(
        "get_performer_info",
        performerDetailSchema,
        async () => {
          const detail = await getPerformerInfo(deps.supabase, deps.config, performer_id);
          if (!detail) {
            throw new NightlifeError(
              "PERFORMER_NOT_FOUND",
              `Performer not found: ${performer_id}`,
            );
          }
          return detail;
        },
      ),
    );
  • The Zod schema for the performer detail output structure.
    const performerDetailSchema = z.object({
      performer_id: z.string(),
      name: z.string(),
      slug: z.string().nullable(),
      bio: z.string().nullable(),
      follower_count: z.number().nullable(),
      ranking_score: z.number().nullable(),
      genres: z.array(z.string()),
      image_url: z.string().nullable(),
      social_links: z.array(
        z.object({
          platform: z.string(),
          username: z.string(),
          url: z.string().nullable(),
        }),
      ),
      upcoming_events: z.array(
        z.object({
          event: eventSummarySchema,
          stage: z.string().nullable(),
          set_start_time: z.string().nullable(),
          set_end_time: z.string().nullable(),
        }),
      ),
      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