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
| Name | Required | Description | Default |
|---|---|---|---|
| city | No | tokyo | |
| date | No | ||
| genre | No | ||
| query | No | ||
| sort_by | No | popularity | |
| limit | No | ||
| offset | No |
Implementation Reference
- src/tools/performers.ts:118-141 (registration)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), ), ); - src/tools/performers.ts:28-33 (schema)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(), }); - src/services/performers.ts:672-768 (handler)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) {