2.1 KiB
2.1 KiB
BPM Enrichment Design
Date: 2026-05-25 Status: Approved
Goal
Add a bpm field to every track object returned by the MCP server, sourced from Spotify's /audio-features endpoint.
Scope
Three tools return track objects and must be updated:
get_playlist_trackslist_saved_trackssearch_tracks
No new MCP tools. No changes to server.py.
Architecture
New helper: _enrich_with_bpm(sp, tracks)
Location: spotify_client.py
- Takes a
spotipy.Spotifyclient and a list of already-parsed track dicts. - Extracts track IDs from each dict's
"id"field. - Slices into chunks of 100 (API limit) and calls
sp.audio_features(ids)once per chunk. - Matches results back by position and sets
track["bpm"]to the rounded tempo float. - If the API returns
Nonefor a track (features unavailable), setsbpmtonull. - Mutates the list in place; returns nothing.
Changes to track parsers
Each of the three functions adds "id": track["id"] to the dict it builds (currently absent from the output). After the full track list is assembled, each function calls _enrich_with_bpm(sp, results) before returning.
Data Shape
Before:
{
"name": "oh baby",
"artists": ["LCD Soundsystem"],
"album": "american dream",
"duration_ms": 349452,
"duration": "5:49",
"popularity": null,
"uri": "spotify:track:53PkA8aXiwH4ppa0V0iO7o",
"added_at": "2026-05-22T17:41:18Z"
}
After:
{
"name": "oh baby",
"artists": ["LCD Soundsystem"],
"album": "american dream",
"duration_ms": 349452,
"duration": "5:49",
"popularity": null,
"uri": "spotify:track:53PkA8aXiwH4ppa0V0iO7o",
"id": "53PkA8aXiwH4ppa0V0iO7o",
"added_at": "2026-05-22T17:41:18Z",
"bpm": 128.0
}
Error Handling
sp.audio_features()may returnNoneentries for tracks with no features (rare, e.g. local files, some podcasts). These getbpm: null.- Any exception from
sp.audio_features()propagates naturally — same behaviour as the rest of the client.
API Cost
get_playlist_tracks/list_saved_tracks: 1 extra API call per 100 tracks (batched).search_tracks: at most 1 extra call (max 50 results per search).