get_venue_info
Retrieve detailed venue information including upcoming events and VIP booking options for nightlife planning.
Instructions
Get full details for a specific venue ID, including upcoming events and VIP booking support.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| venue_id | Yes |
Implementation Reference
- src/services/venues.ts:1175-1303 (handler)The main implementation of the get_venue_info logic, which fetches venue details from the database, resolves city context, retrieves upcoming events, and builds the VenueDetail object.
export async function getVenueInfo( supabase: SupabaseClient, config: AppConfig, venueId: string, ): Promise<VenueDetail | null> { const cleanedId = venueId.trim(); if (!cleanedId) { throw new NightlifeError("INVALID_VENUE_ID", "venue_id cannot be blank."); } if (!UUID_RE.test(cleanedId)) { throw new NightlifeError("INVALID_VENUE_ID", "venue_id must be a UUID."); } const { data: venue, error: venueError } = await supabase .from("venues") .select(VENUE_SELECT) .eq("id", cleanedId) .maybeSingle<VenueRow>(); if (venueError) { throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch venue details.", { cause: venueError.message, }); } if (!venue) { return null; } const fallbackCity = await getCityContext( supabase, config.defaultCity, config.defaultCountryCode, ); const cityContext = venue.city_id ? await supabase .from("cities") .select("id,slug,timezone,service_day_cutoff_time,country_code") .eq("id", venue.city_id) .maybeSingle<{ id: string; slug: string; timezone: string; service_day_cutoff_time: string; country_code: string; }>() : { data: null, error: null }; if (cityContext.error) { throw new NightlifeError("DB_QUERY_FAILED", "Failed to resolve venue city context.", { cause: cityContext.error.message, }); } const citySlug = cityContext.data?.slug?.toLowerCase() || fallbackCity?.slug || config.defaultCity; const timezone = cityContext.data?.timezone || fallbackCity?.timezone || "UTC"; const cutoffTime = cityContext.data?.service_day_cutoff_time || fallbackCity?.serviceDayCutoffTime || "06:00"; const fallbackCurrency = defaultCurrencyForCountry( cityContext.data?.country_code || fallbackCity?.countryCode || config.defaultCountryCode, ); const now = new Date(); const currentServiceDate = getCurrentServiceDate(now, timezone, cutoffTime); const window = serviceDateWindowToUtc( currentServiceDate, addDaysToIsoDate(currentServiceDate, 31), timezone, cutoffTime, ); const { data: occurrenceRows, error: occurrenceError } = await supabase .from("event_occurrences") .select(OCCURRENCE_SELECT) .eq("published", true) .eq("venue_id", venue.id) .gte("start_at", window.startIso) .lt("start_at", window.endIso) .order("start_at", { ascending: true }) .range(0, 49); if (occurrenceError) { throw new NightlifeError("DB_QUERY_FAILED", "Failed to fetch venue events.", { cause: occurrenceError.message, }); } const occurrences = (occurrenceRows || []) as unknown as EventOccurrenceRow[]; const vipHourOccurrences = buildVipHoursSyntheticOccurrences( venue, currentServiceDate, addDaysToIsoDate(currentServiceDate, 31), timezone, ); const effectiveOccurrences = [...occurrences, ...vipHourOccurrences].sort((a, b) => String(a.start_at || "").localeCompare(String(b.start_at || "")), ); const metadata = await fetchOccurrenceMetadata( supabase, occurrences.map((row) => row.id), ); const upcomingEvents = effectiveOccurrences .map((row) => toEventSummary(row, citySlug, config.nightlifeBaseUrl, fallbackCurrency, metadata), ) .slice(0, 5); return { venue_id: venue.id, name: venueName(venue), area: venueArea(venue), address: venueAddress(venue), website: venue.website || null, image_url: venue.image_url || null, vip_booking_supported: venue.vip_booking_enabled === true, sns_instagram: venue.sns_instagram || null, sns_tiktok: venue.sns_tiktok || null, sns_x: venue.sns_x || null, sns_youtube: venue.sns_youtube || null, guest_list_enabled: venue.guest_list_enabled, upcoming_event_count: effectiveOccurrences.length, upcoming_events: upcomingEvents, nlt_url: buildVenueUrl(config.nightlifeBaseUrl, citySlug, venue.id), }; } - src/tools/venues.ts:134-155 (registration)Registration of the get_venue_info tool in the MCP server, including its input schema and the handler wrapper.
server.registerTool( "get_venue_info", { description: "Get full details for a specific venue ID, including upcoming events and VIP booking support.", inputSchema: { venue_id: z.string().min(1), }, outputSchema: venueDetailSchema, }, async ({ venue_id }) => runTool( "get_venue_info", venueDetailSchema, async () => { const detail = await getVenueInfo(deps.supabase, deps.config, venue_id); if (!detail) { throw new NightlifeError("VENUE_NOT_FOUND", `Venue not found: ${venue_id}`); } return detail; }, ), ); - src/tools/venues.ts:36-52 (schema)Schema definition for the output of the get_venue_info tool using Zod.
const venueDetailSchema = z.object({ venue_id: z.string(), name: z.string(), area: z.string().nullable(), address: z.string().nullable(), website: z.string().nullable(), image_url: z.string().nullable(), vip_booking_supported: z.boolean(), sns_instagram: z.string().nullable(), sns_tiktok: z.string().nullable(), sns_x: z.string().nullable(), sns_youtube: z.string().nullable(), guest_list_enabled: z.boolean().nullable(), upcoming_event_count: z.number().int().min(0), upcoming_events: z.array(eventSummarySchema), nlt_url: z.string(), });