Files
mcp-spotify/docs/superpowers/specs/2026-05-25-bpm-enrichment-design.md

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_tracks
  • list_saved_tracks
  • search_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.Spotify client 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 None for a track (features unavailable), sets bpm to null.
  • 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 return None entries for tracks with no features (rare, e.g. local files, some podcasts). These get bpm: 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).