Skip to main content
Glama

SeatGeek MCP Server

findEventRecommendations.ts9.6 kB
import { z } from 'zod'; import { fetchJson, RECOMMENDATIONS_ENDPOINT } from '../shared/core.js'; import { searchPerformers, searchEvents } from '../shared/endpoints.js'; import { CondensedEvent, condenseEventData } from '../shared/helpers.js'; // Schema for event recommendations const EventRecommendationsQuerySchema = z.object({ q: z.string().nullable().optional(), geoip: z.boolean().nullable().optional(), lat: z.number().nullable().optional(), lon: z.number().nullable().optional(), postal_code: z.string().nullable().optional(), range: z.string().nullable().optional(), start_utc: z.string().nullable().optional(), end_utc: z.string().nullable().optional(), per_page: z.number().min(1).max(50).default(10), page: z.number().min(1).default(1), format: z.enum(['structured', 'json']).default('structured'), }); type EventRecommendationsQuery = z.infer<typeof EventRecommendationsQuerySchema>; function buildQuery(params: EventRecommendationsQuery, performerId?: number, eventId?: number): Record<string, any> { const query: Record<string, any> = { "geoip": params.geoip, "lat": params.lat, "lon": params.lon, "postal_code": params.postal_code, "range": params.range, per_page: Math.min(params.per_page, 50), page: params.page, }; // Add performer and event IDs if provided if (performerId) { query["performers.id"] = performerId; } if (eventId) { query["events.id"] = eventId; }; // Add date filters if provided if (params.start_utc) { query["datetime_utc.gte"] = params.start_utc; } if (params.end_utc) { query["datetime_utc.lte"] = params.end_utc; } // Drop null/undefined values to avoid noisy query strings const filteredQuery: Record<string, any> = {}; for (const [key, value] of Object.entries(query)) { if (value !== null && value !== undefined) { filteredQuery[key] = value; } } return filteredQuery; } const inputSchema = { q: z.string().optional().describe('Search query to find either an event or performer to base recommendations on. If provided, the system will first look up the event ID or performer ID/slug automatically.'), geoip: z.boolean().optional().describe('Use IP geolocation to provide recommendations for events near the user.'), lat: z.number().optional().describe('Latitude coordinate for location-based recommendations. Use with lon and optionally range.'), lon: z.number().optional().describe('Longitude coordinate for location-based recommendations. Use with lat and optionally range.'), postal_code: z.string().optional().describe('Postal code for location-based recommendations. Use with country code for better accuracy.'), range: z.string().optional().describe('Search radius for location-based recommendations (e.g., "50mi", "25km"). Use with lat/lon or postal_code.'), start_utc: z.string().optional().describe('Start date filter in ISO8601 UTC format (YYYY-MM-DD). Use for date ranges like "next month" or "this weekend".'), end_utc: z.string().optional().describe('End date filter in ISO8601 UTC format (YYYY-MM-DD). Use with start_utc to define date ranges.'), per_page: z.number().min(1).max(50).default(10).describe('Number of results to return per page (1-50). Default is 10.'), page: z.number().min(1).default(1).describe('Page number for pagination. Default is 1.'), format: z.enum(['structured', 'json']).default('structured').describe('Output format. Use "structured" for readable format (default) or "json" for raw API response. Only use "json" if explicitly requested.'), }; /** * Find event recommendations based on a performer or event. * * Uses the q parameter on the GET /events endpoint to search if the user's query doesn't pertain to a performer to get the eventid. * Otherwise uses the q parameter on the GET /performers endpoint to find the performer id or performer slug. * Then calls the GET /recommendations/events endpoint with either the performer's id or slug OR the event id. * * Returns a list of recommended events based on the search criteria. */ export const findEventRecommendationsTool = { name: 'find_event_recommendations', description: 'Get personalized event recommendations based on performers, events, or location. This tool first searches for performers and/or events based on the query (q parameter), then uses the IDs to find similar events. Use location parameters (geoip, lat/lon, postal_code) for nearby events.', inputSchema: inputSchema, handler: async (args: any, extra: any) => { try { const params = EventRecommendationsQuerySchema.parse(args); let performerId: number | null = null; let eventId: number | null = null; // If we have a q parameter, try to look up performer and event IDs if (params.q) { try { // First try to find performers with the query const performers = await searchPerformers(params.q, params.per_page, 1); // If we found performers, aggregate unique performer slugs and make Promise.all query if (performers.length > 0) { // Get unique performer slugs const uniqueSlugs = [...new Set(performers.map((p: any) => p.slug).filter(Boolean))] as string[]; if (uniqueSlugs.length > 0) { // Create promises for each unique performer slug const promises = uniqueSlugs.map(slug => { return searchEvents(slug, params.per_page, { "performers.slug": slug }); }); // Execute all promises const results = await Promise.all(promises); // Merge events from all results const allEvents: any[] = []; for (const result of results) { if (Array.isArray(result)) { allEvents.push(...result); } } // Remove duplicates based on event id const eventIds = new Set(); const uniqueEvents = allEvents.filter(event => { if (eventIds.has(event.id)) { return false; } eventIds.add(event.id); return true; }); // Use the first event's ID for recommendations if available if (uniqueEvents.length > 0 && uniqueEvents[0].id) { eventId = uniqueEvents[0].id; } } else { // If no valid slugs found, search events directly with the q parameter const events = await searchEvents(params.q, params.per_page, {}); if (events.length > 0 && events[0].id) { eventId = events[0].id; } } } else { // If no performer found, search events directly with the q parameter const events = await searchEvents(params.q, params.per_page, {}); if (events.length > 0 && events[0].id) { eventId = events[0].id; } } } catch (error) { // If lookup fails, continue with original query console.warn('Failed to lookup performer or event, continuing with original query:', error); } } // Build query with found IDs (convert null to undefined for TypeScript) const query = buildQuery( params, performerId !== null ? performerId : undefined, eventId !== null ? eventId : undefined ); const data = await fetchJson(RECOMMENDATIONS_ENDPOINT, query); if (params.format === 'json') { return { content: [ { type: 'text' as const, text: JSON.stringify(data, null, 2) } ] }; } // Extract events from recommendations const recommendationsRaw = Array.isArray(data) ? data : (data.recommendations || []); const results: CondensedEvent[] = []; for (const item of recommendationsRaw) { try { // Each recommendation has an event object if (item.event) { // Condense the event data const condensedEvent = condenseEventData(item.event); results.push(condensedEvent); } } catch (error) { // Skip invalid events console.warn('Skipping invalid event recommendation:', error); } } return { content: [ { type: 'text' as const, text: JSON.stringify(results, null, 2) } ] }; } catch (error) { console.error('Error in find_event_recommendations handler:', error); // Provide more detailed error information const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorDetails = { error: 'API_REQUEST_FAILED', message: errorMessage, timestamp: new Date().toISOString(), endpoint: RECOMMENDATIONS_ENDPOINT, args: args }; return { content: [ { type: 'text' as const, text: JSON.stringify({ error: 'Failed to fetch event recommendations', details: errorDetails, suggestion: 'Please check your parameters and try again. Common issues include invalid performer IDs, event IDs, or location parameters.' }, null, 2) } ] }; } }, };

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/PeterShin23/seatgeek-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server