spotify_get_recommendations
Generate personalized music recommendations from Spotify using seed tracks, artists, or genres with adjustable audio features like energy, danceability, and tempo to discover new music.
Instructions
Get track recommendations from Spotify based on seed tracks, artists, or genres.
Generates personalized recommendations using up to 5 seeds (any combination) with
tunable audio features (energy, danceability, valence, tempo).
Args:
- seed_tracks/seed_artists/seed_genres: Up to 5 total seeds (track IDs, artist IDs, or genre names)
- limit: Results to return, 1-100 (default: 20)
- min/max/target audio features: Energy, danceability, valence (0.0-1.0), tempo (BPM)
- response_format: 'markdown' (formatted) or 'json' (structured data)
Returns:
Markdown: Numbered list with track details (name, artists, album, duration, ID, popularity)
JSON: {"total": N, "tracks": [{id, name, artists, album, duration_ms, popularity, uri, external_urls}]}
Examples:
- "Find energetic workout music" -> seed_genres=['electronic'], target_energy=0.9
- "Songs like this track" -> seed_tracks=['track_id']
- "Happy danceable songs" -> target_valence=0.8, target_danceability=0.8
Errors: Returns error for no seeds, >5 seeds, auth failure (401), rate limits (429), or no results.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Input Schema (JSON Schema)
{
"properties": {
"params": {
"$ref": "#/$defs/GetRecommendationsInput"
}
},
"required": [
"params"
],
"type": "object"
}
Implementation Reference
- server.py:67-167 (handler)The core handler function implementing the spotify_get_recommendations tool. It validates inputs, builds query parameters for Spotify's recommendations API, fetches data, formats the response in markdown or JSON, and handles errors.async def spotify_get_recommendations(params: GetRecommendationsInput) -> str: """Get track recommendations from Spotify based on seed tracks, artists, or genres. Generates personalized recommendations using up to 5 seeds (any combination) with tunable audio features (energy, danceability, valence, tempo). Args: - seed_tracks/seed_artists/seed_genres: Up to 5 total seeds (track IDs, artist IDs, or genre names) - limit: Results to return, 1-100 (default: 20) - min/max/target audio features: Energy, danceability, valence (0.0-1.0), tempo (BPM) - response_format: 'markdown' (formatted) or 'json' (structured data) Returns: Markdown: Numbered list with track details (name, artists, album, duration, ID, popularity) JSON: {"total": N, "tracks": [{id, name, artists, album, duration_ms, popularity, uri, external_urls}]} Examples: - "Find energetic workout music" -> seed_genres=['electronic'], target_energy=0.9 - "Songs like this track" -> seed_tracks=['track_id'] - "Happy danceable songs" -> target_valence=0.8, target_danceability=0.8 Errors: Returns error for no seeds, >5 seeds, auth failure (401), rate limits (429), or no results. """ try: # Validate at least one seed is provided total_seeds = ( len(params.seed_tracks or []) + len(params.seed_artists or []) + len(params.seed_genres or []) ) if total_seeds == 0: return ( "Error: At least one seed (track, artist, or genre) must be provided." ) if total_seeds > 5: return "Error: Maximum of 5 seeds total allowed across all seed types." # Build query parameters query_params: dict = {"limit": params.limit} if params.seed_tracks: query_params["seed_tracks"] = ",".join(params.seed_tracks) if params.seed_artists: query_params["seed_artists"] = ",".join(params.seed_artists) if params.seed_genres: query_params["seed_genres"] = ",".join(params.seed_genres) # Add tunable attributes for attr in [ "min_energy", "max_energy", "target_energy", "min_danceability", "max_danceability", "target_danceability", "min_valence", "max_valence", "target_valence", "min_tempo", "max_tempo", "target_tempo", ]: value = getattr(params, attr) if value is not None: query_params[attr] = value # Make API request data = await make_spotify_request("recommendations", params=query_params) tracks = data.get("tracks", []) if not tracks: return "No recommendations found for the provided seeds and parameters." # Format response if params.response_format == ResponseFormat.MARKDOWN: lines = ["# Spotify Track Recommendations\n"] for i, track in enumerate(tracks, 1): lines.append(f"## {i}. {format_track_markdown(track)}\n") result = "\n".join(lines) truncation_msg = check_character_limit(result, tracks) if truncation_msg: tracks = tracks[: len(tracks) // 2] lines = ["# Spotify Track Recommendations\n", truncation_msg] for i, track in enumerate(tracks, 1): lines.append(f"## {i}. {format_track_markdown(track)}\n") result = "\n".join(lines) return result else: # JSON format return json.dumps( {"total": len(tracks), "tracks": tracks}, indent=2, ) except Exception as e: return handle_spotify_error(e)
- spotify_mcp/types.py:26-101 (schema)Pydantic BaseModel defining the input schema for the spotify_get_recommendations tool, including seed lists, limit, tunable audio features (min/max/target for energy, danceability, valence, tempo), and response format, with validation.class GetRecommendationsInput(BaseModel): """Input model for getting track recommendations.""" model_config = ConfigDict(str_strip_whitespace=True, validate_assignment=True) seed_tracks: list[str] | None = Field( default=None, description="List of Spotify track IDs to use as seeds (e.g., ['3n3Ppam7vgaVa1iaRUc9Lp'])", max_length=5, ) seed_artists: list[str] | None = Field( default=None, description="List of Spotify artist IDs to use as seeds (e.g., ['4NHQUGzhtTLFvgF5SZesLK'])", max_length=5, ) seed_genres: list[str] | None = Field( default=None, description="List of genre names to use as seeds (e.g., ['pop', 'rock'])", max_length=5, ) limit: int | None = Field( default=20, description="Number of recommendations to return", ge=1, le=100, ) min_energy: float | None = Field( default=None, description="Minimum energy (0.0-1.0)", ge=0.0, le=1.0 ) max_energy: float | None = Field( default=None, description="Maximum energy (0.0-1.0)", ge=0.0, le=1.0 ) target_energy: float | None = Field( default=None, description="Target energy (0.0-1.0)", ge=0.0, le=1.0 ) min_danceability: float | None = Field( default=None, description="Minimum danceability (0.0-1.0)", ge=0.0, le=1.0 ) max_danceability: float | None = Field( default=None, description="Maximum danceability (0.0-1.0)", ge=0.0, le=1.0 ) target_danceability: float | None = Field( default=None, description="Target danceability (0.0-1.0)", ge=0.0, le=1.0 ) min_valence: float | None = Field( default=None, description="Minimum valence/positivity (0.0-1.0)", ge=0.0, le=1.0 ) max_valence: float | None = Field( default=None, description="Maximum valence/positivity (0.0-1.0)", ge=0.0, le=1.0 ) target_valence: float | None = Field( default=None, description="Target valence/positivity (0.0-1.0)", ge=0.0, le=1.0 ) min_tempo: float | None = Field( default=None, description="Minimum tempo in BPM", ge=0.0 ) max_tempo: float | None = Field( default=None, description="Maximum tempo in BPM", ge=0.0 ) target_tempo: float | None = Field( default=None, description="Target tempo in BPM", ge=0.0 ) response_format: ResponseFormat = Field( default=ResponseFormat.MARKDOWN, description="Output format: 'markdown' for human-readable or 'json' for machine-readable", ) @field_validator("seed_tracks", "seed_artists", "seed_genres") @classmethod def validate_seeds(cls, v: list[str] | None) -> list[str] | None: """Validate seed lists.""" if v is not None and len(v) > 5: raise ValueError("Maximum 5 seeds allowed per type") return v
- server.py:57-66 (registration)MCP tool registration decorator that registers the spotify_get_recommendations handler with the FastMCP server, specifying the name and operational hints.@mcp.tool( name="spotify_get_recommendations", annotations={ "title": "Get Spotify Track Recommendations", "readOnlyHint": True, "destructiveHint": False, "idempotentHint": True, "openWorldHint": True, }, )