# Harmonic mixing over MCP: the DJ set-builder Spotify never shipped

> Source: <https://dev.to/birrings/harmonic-mixing-over-mcp-the-dj-set-builder-spotify-never-shipped-2dgo>
> Published: 2026-07-14 13:15:57+00:00

When Spotify deprecated Audio Features, Recommendations, and Related Artists for new apps in November 2024, a wave of "drop-in replacement" APIs appeared. Most stop at parity: you send a track, you get BPM, key and energy back. Useful — but that's the same lookup Spotify already gave you.

FreqBlog went a layer further. It rebuilt the dead endpoints, then shipped the thing Spotify never had: a **set-builder**. Pairwise transition scoring, next-track ranking, and full setlist ordering around the Camelot wheel. And the whole surface is exposed over an **MCP server**, so an LLM or agent can plan a DJ set by calling tools directly — no glue code between the model and the music theory.

This is for people building music or AI tooling. I'll show the harmonic-mixing model concretely, then call it two ways: plain REST and MCP.

Before the interesting part, the boring-but-necessary drop-ins:

`GET /recommendations`

(and the MCP tool `get_recommendations`

) — the replacement for the removed `/v1/recommendations`

, re-ranked by genre affinity so a feature-close cross-genre track can't outrank same-genre picks.`GET /related-artists`

— replaces the killed related-artists endpoint.`GET /v1/audio-features/{id}`

returns a bare Spotify `AudioFeaturesObject`

; `GET /v1/audio-features?ids=`

returns the `{"audio_features":[...]}`

array envelope. Both mirror Spotify's own shapes, so porting existing code is a small diff.The native lookup is flatter and richer. `GET /lookup`

resolves a track by name, ISRC, MusicBrainz ID or Spotify ID and returns **one flat object** — over 40 fields, no nesting:

```
curl -s "https://api.freqblog.com/lookup?track=Strobe&artist=deadmau5&wait=10" \
  -H "X-Api-Key: $FREQBLOG_KEY"
// shape (values illustrative) — every feature is top-level, no "audio_features" wrapper
{
  "track_name": "Strobe",
  "artist_name": "deadmau5",
  "bpm": 128.0,
  "key": "B",
  "camelot": "1A",
  "mode": "minor",
  "energy": 0.61,
  "danceability": 0.72,
  "valence": 0.35,
  "genre": "progressive house"
}
```

Two things worth knowing: `bpm`

and `key`

are always present and non-null, and `?wait=10`

opts into a bounded synchronous mode — up to 25 seconds — that returns the analysed track inline as a `200`

instead of the default `202 + Retry-After`

when a track isn't cached yet.

Every musical key maps to a clock position on the **Camelot wheel**: a number `1`

–`12`

plus a letter (`A`

= minor, `B`

= major). Two tracks mix without a key clash when they sit next to each other on the wheel: the **same** key, the **relative** major/minor (same number, flipped letter), or the **adjacent** `+1`

/`-1`

neighbours. Jump `+7`

and you get the classic energy-boost mix.

`find_compatible_keys`

is pure theory — no catalog hit, **zero quota**:

```
// find_compatible_keys(camelot="8A", extended=true)
{
  "camelot": "8A",
  "compatible": [
    { "camelot": "8A", "relation": "same" },
    { "camelot": "8B", "relation": "relative" },      // minor <-> major
    { "camelot": "7A", "relation": "adjacent_down" }, // -1
    { "camelot": "9A", "relation": "adjacent_up" },   // +1
    { "camelot": "3A", "relation": "energy_boost" },  // +7  (extended=true)
    { "camelot": "1A", "relation": "energy_drop" }    // -7  (extended=true)
  ]
}
```

Knowing which keys *could* mix is table stakes. `score_transition`

rates how well one real track mixes into another, `0`

–`100`

, blending Camelot key compatibility, octave-aware BPM proximity (half/double-time counts as a match), and energy smoothness — and it hands back a human reason:

```
// score_transition(from_track_id="apple_ad1829eeccb70f9a",
//                  to_track_id="apple_7c1120fbe0")  — costs 1 quota
{
  "score": 91,
  "components": { "harmonic": 95, "tempo": 92, "energy": 86 },
  "reason": "8A->9A +1 adjacent, 126->128 BPM (+2.0), energy +0.04"
}
```

There's no raw key/BPM endpoint anywhere that gives you *that* — the pairwise judgement is the product.

`suggest_next_track`

takes the track that's playing and returns the top-N catalog tracks to play next, each with the same score, components and reason (e.g. `"11B->11B same key, 118->117 BPM (-0.29), energy +0.12"`

). It's genre-aware by default, so an off-genre track that only *coincidentally* shares your key/BPM sinks to the bottom.

`build_setlist`

orders an entire crate (2–100 tracks) into a beat-matched set that follows an energy `arc`

— `peak_time`

, `warmup`

, `cooldown`

, or `flat`

— keeping every consecutive transition harmonically and tempo-smooth. It returns an overall `flow_score`

, the tracks in play order, and the per-step transitions.

Here's where it stops being an API and starts being a capability you hand to a model. Point any MCP client at:

```
https://mcp.freqblog.com/mcp
```

That exposes twelve tools — `search_catalog`

, `get_audio_features`

, `get_audio_features_batch`

, `find_tracks_by_bpm`

, `find_tracks_by_key`

, `find_compatible_keys`

, `get_recommendations`

, `get_related_artists`

, `score_transition`

, `suggest_next_track`

, `build_setlist`

, `tag_track`

. The agent orchestrates them itself. A single prompt like *"build me a 90-minute peak-time set from these ten tracks"* becomes:

`search_catalog`

on each fuzzy name → concrete `itunes_track_id`

s`build_setlist(track_ids=[...], arc="peak_time")`

→ ordered set + `flow_score`

`itunes_track_id`

s to `GET /export/rekordbox`

(also `traktor`

, `m3u`

, `cuesheet`

, `csv`

) and drop the crate straight into your DJ softwareNo orchestration code on your side — the tool descriptions carry enough for the model to chain them. The set-builder tools cost a little more quota than a plain lookup (`score_transition`

1, `get_recommendations`

2, `suggest_next_track`

3, `build_setlist`

5), because each one is doing real combinatorial work.

Auth is an `X-Api-Key`

header (a `?key=`

query fallback exists for browser and email links). Everything above is also available as plain REST — `GET /transition`

, `GET /next-track`

, `POST /setlist`

, `GET /similar?track_id=...`

— if you'd rather not run an MCP client. It's on RapidAPI too. Pricing starts at **£0.17/1k**, and the free tier is 1,000 requests/month, which is plenty to prototype a set planner.

`itunes_track_id`

s, so a track has to resolve first (`search_catalog`

/ `/lookup`

). Coverage is deep but not universal — niche or regional catalogs have holes.`/analyze`

and `/identify`

— not the audio itself.Grab a free key and the OpenAPI docs at ** api.freqblog.com/docs**, or read more about the API on
