Skip to main content
Glama
recommendations.ts7.8 kB
import type { SpotifyClient } from "../spotify-client.js"; import type { SpotifyTrack, SpotifyArtist } from "../types.js"; import { getRecentlyPlayedTracks, type RecentlyPlayedResponse } from "./library.js"; export interface RecommendationsParams { seed_tracks?: string[]; seed_artists?: string[]; seed_genres?: string[]; limit?: number; target_energy?: number; target_danceability?: number; target_valence?: number; use_recent_listening?: boolean; // New parameter to automatically use recent listening } export interface RecommendationsResponse { tracks: SpotifyTrack[]; seeds: Array<{ afterFilteringSize: number; afterRelinkingSize: number; href: string | null; id: string; initialPoolSize: number; type: string; }>; } interface SearchResponse { tracks: { items: SpotifyTrack[]; }; } /** * Get track recommendations based on seeds using Search API * Note: Spotify deprecated the Recommendations API. This uses search with filters as an alternative. */ export async function getRecommendations( client: SpotifyClient, params: RecommendationsParams ): Promise<RecommendationsResponse> { let { seed_tracks, seed_artists, seed_genres, limit = 10, target_energy, target_danceability, target_valence, use_recent_listening = false, } = params; // If use_recent_listening is true and no seeds provided, use recently played tracks if ( use_recent_listening && (!seed_tracks || seed_tracks.length === 0) && (!seed_artists || seed_artists.length === 0) ) { try { const recentTracks = await getRecentlyPlayedTracks(client, 10); if (recentTracks.items.length > 0) { // Extract unique artist IDs from recent tracks const artistIds = new Set<string>(); const trackIds = new Set<string>(); recentTracks.items.forEach(item => { trackIds.add(item.track.id); item.track.artists.forEach(artist => { artistIds.add(artist.id); }); }); // Use up to 2 tracks and 2 artists as seeds seed_tracks = Array.from(trackIds).slice(0, 2); seed_artists = Array.from(artistIds).slice(0, 2); } } catch (error) { console.error("Failed to get recent tracks for recommendations:", error); // Continue without recent listening data } } // At least one seed is required if ( (!seed_tracks || seed_tracks.length === 0) && (!seed_artists || seed_artists.length === 0) && (!seed_genres || seed_genres.length === 0) ) { throw new Error( "At least one seed (track, artist, or genre) is required for recommendations. " + "You can set use_recent_listening=true to automatically use your recent listening history." ); } // Build search query using filters const queryParts: string[] = []; // If we have seed tracks, get their details to build a better query if (seed_tracks && seed_tracks.length > 0) { try { // Get track details to extract artist and genre info const trackDetails = await client.get<SpotifyTrack>( `/tracks/${seed_tracks[0]}` ); if (trackDetails.artists && trackDetails.artists.length > 0) { queryParts.push(`artist:${trackDetails.artists[0].name}`); } } catch (error) { // If track lookup fails, continue with other seeds console.error("Failed to get track details:", error); } } // Add artist names if provided if (seed_artists && seed_artists.length > 0) { try { const artistDetails = await client.get<SpotifyArtist>( `/artists/${seed_artists[0]}` ); queryParts.push(`artist:${artistDetails.name}`); } catch (error) { console.error("Failed to get artist details:", error); } } // Add genre filters if (seed_genres && seed_genres.length > 0) { queryParts.push(`genre:${seed_genres[0]}`); } // Build query based on mood parameters let moodQuery = ""; if (target_energy !== undefined || target_danceability !== undefined || target_valence !== undefined) { // Use mood-based keywords if (target_energy !== undefined && target_energy > 0.7) { moodQuery = "energetic upbeat"; } else if (target_energy !== undefined && target_energy < 0.3) { moodQuery = "calm relaxing"; } if (target_valence !== undefined && target_valence > 0.7) { moodQuery += " happy"; } else if (target_valence !== undefined && target_valence < 0.3) { moodQuery += " melancholic"; } } // Combine query parts let finalQuery = moodQuery || "music"; if (queryParts.length > 0) { finalQuery = `${moodQuery} ${queryParts.join(" ")}`.trim(); } // Use tag:new for recent music to get fresh recommendations if (!seed_tracks && !seed_artists) { finalQuery += " tag:new"; } // Search for tracks const searchParams = { q: finalQuery, type: "track", limit: Math.min(limit * 2, 50), // Get more results to filter }; const response = await client.get<SearchResponse>("/search", searchParams); // Filter out seed tracks if provided let tracks = response.tracks.items; if (seed_tracks && seed_tracks.length > 0) { tracks = tracks.filter((track) => !seed_tracks.includes(track.id)); } // Limit results tracks = tracks.slice(0, limit); // Format response to match old recommendations API structure return { tracks, seeds: [ { afterFilteringSize: tracks.length, afterRelinkingSize: tracks.length, href: null, id: seed_genres?.[0] || seed_artists?.[0] || seed_tracks?.[0] || "unknown", initialPoolSize: response.tracks.items.length, type: seed_genres?.[0] ? "genre" : seed_artists?.[0] ? "artist" : "track", }, ], }; } /** * Get available genre seeds * Note: Since the recommendations API is deprecated, we return a curated list of common genres */ export async function getAvailableGenreSeeds( client: SpotifyClient ): Promise<string[]> { // Return common Spotify genres that work well with search return [ "acoustic", "afrobeat", "alt-rock", "alternative", "ambient", "blues", "chill", "classical", "country", "dance", "dancehall", "deep-house", "disco", "drum-and-bass", "dub", "dubstep", "edm", "electro", "electronic", "emo", "folk", "funk", "gospel", "goth", "grunge", "guitar", "hard-rock", "hardcore", "hip-hop", "house", "indie", "indie-pop", "industrial", "jazz", "k-pop", "latin", "latino", "metal", "metalcore", "minimal-techno", "party", "piano", "pop", "pop-film", "punk", "punk-rock", "r-n-b", "rainy-day", "reggae", "reggaeton", "rock", "rock-n-roll", "rockabilly", "romance", "sad", "salsa", "samba", "singer-songwriter", "ska", "sleep", "soul", "study", "summer", "synth-pop", "tango", "techno", "trance", "trip-hop", "world-music", ]; } /** * Format recommendations for display */ export function formatRecommendations( response: RecommendationsResponse ): string { if (!response.tracks || response.tracks.length === 0) { return "No recommendations found."; } const lines = ["**Recommended Tracks:**", ""]; response.tracks.forEach((track, i) => { const artists = track.artists.map((a) => a.name).join(", "); lines.push(`${i + 1}. **${track.name}** by ${artists}`); lines.push(` Album: ${track.album.name}`); lines.push(` URI: ${track.uri}`); lines.push(` Link: ${track.external_urls.spotify}`); lines.push(""); }); return lines.join("\n"); }

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/nicklaustrup/mcp-spotify'

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