Skip to content

Give MCP search_tracks disambiguators agents can actually read#653

Open
JobDoesburg wants to merge 2 commits into
masterfrom
mcp-track-disambiguation
Open

Give MCP search_tracks disambiguators agents can actually read#653
JobDoesburg wants to merge 2 commits into
masterfrom
mcp-track-disambiguation

Conversation

@JobDoesburg

@JobDoesburg JobDoesburg commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

The problem

Searching "sterrenstof" via the MCP search_tracks tool returns 5 results. Three of them are genuinely named "Sterrenstof" — but they have different Spotify IDs because they're different recordings (album version, single, regional release, …). The website shows album covers so the user can tell them apart at a glance. The MCP agent gets:

{ "id": "7D5vAulNfrQV6xEwzgH0OF", "name": "Sterrenstof", "artists": ["De Jeugd Van Tegenwoordig"] }
{ "id": "4PW9WvbhNlrFoThvc7zheh", "name": "Sterrenstof", "artists": ["De Jeugd Van Tegenwoordig"] }
{ "id": "6CGXTNx0MsvLGAxU7gSHRe", "name": "Sterrenstof", "artists": ["De Jeugd Van Tegenwoordig"] }

The agent says, correctly, that it can't tell the difference between them.

A secondary issue: the tool docstring never told the agent that id is a Spotify track ID (opaque, base62) or that the catalog being searched is Spotify's. Some agents try to pass URIs, URLs, or hashes back into request_song.

What this PR does

Add disambiguators to the search results

album (album name), album_release_date (Spotify's YYYY[-MM[-DD]] string), and duration_ms join the existing id / name / artists / image fields on both the REST API (/api/v1/.../search/) and the MCP tool. The two surfaces stay aligned: the MCP exposes a strict subset of what the REST API exposes, never more. With these fields the agent can pick the right "Sterrenstof":

  • album="Parels Voor De Zwijnen", album_release_date="2005-09-26", duration_ms=222000 → the original studio album cut
  • album="Singles 2005-2010", album_release_date="2010-…" → a compilation
  • album="Live At Pinkpop", … → the live recording

Make image resilient

Spotify occasionally returns a track with an empty album.images array. The previous code crashed with a KeyError; now image is None. The front-end already handles image !== null.

Rewrite the MCP docstrings

The tool descriptions now say, explicitly:

  • the catalog is Spotify's
  • id is a Spotify track ID; pass it through verbatim, don't transform
  • results are ordered by Spotify's relevance/popularity ranking — first is the best match Spotify could find, lower-position hits may not even be exact name matches
  • two same-name hits are different recordings; use album + release_date + duration_ms to choose; if unsure, ask the user
  • Marietje-backed venues return []; that is not "no songs match"
  • open.spotify.com/track/{id} is a useful confirmation URL for the user
  • request_song should confirm with the user before being called

Test plan

  • CI: 218 tests pass, flake8 + black clean, OpenAPI schema validates.
  • Via Claude / Cursor: search for a song that exists in multiple recordings (e.g. "Sterrenstof", "Smells Like Teen Spirit"). Confirm the agent sees album, album_release_date and duration_ms, picks one, and asks the user to confirm before queueing it.
  • Browser smoke test: open /thaliedje/<venue>/ and search via the website. The dropdown still renders correctly; the underlying /api/v1/.../search/ response now also carries album / album_release_date / duration_ms (additive — the existing fields the front-end consumes are unchanged).
  • Marietje venue: search_tracks returns an empty results list as before; the new docstring tells the agent not to interpret that as "no matches".

@JobDoesburg JobDoesburg force-pushed the mcp-track-disambiguation branch from 3cf3eff to 491aa9d Compare June 27, 2026 14:02
Searching the MCP ``search_tracks`` tool for a song that exists in
multiple recordings (album version, single, live cut, remaster, …)
returned identical-looking rows: same ``name``, same ``artists``,
different opaque ``id``. The agent had no way to choose. The website
hides this by rendering album art; headless consumers have nothing to
look at.

Add ``album`` (album name), ``album_release_date`` (Spotify's
``YYYY[-MM[-DD]]`` string) and ``duration_ms`` to each search result on
**both** the REST API (``/api/v1/.../search/``) and the MCP tool. The
two surfaces stay aligned — the MCP exposes a strict subset of the REST
surface, never more. ``image`` (album cover URL) was already returned;
also made it resilient to Spotify occasionally handing back an empty
``album.images`` array (was a KeyError, now ``None``).

The MCP tool docstring is rewritten to explicitly tell the agent:

- the catalog is **Spotify's** (not TOSTI's)
- ``id`` is a Spotify track ID, opaque, pass through verbatim — no URI,
  URL, ISRC, or fabrication
- results are ordered by Spotify's relevance/popularity; lower-position
  hits may not be exact name matches (trust ``name`` + ``artists``, not
  position)
- two same-name results are different recordings; use ``album`` +
  ``release_date`` + ``duration_ms`` to choose; if unsure, ask the user
- Marietje-backed venues return ``[]``; that is not "no songs match"
- ``open.spotify.com/track/{id}`` is a useful confirmation URL for the
  user
- ``request_song`` should confirm the picked result with the user
  before being called

Existing model tests cover the wider projection plus the empty-album-art
resilience; existing service tests cover the MCP path's normalisation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JobDoesburg JobDoesburg force-pushed the mcp-track-disambiguation branch from 491aa9d to ed1211e Compare June 28, 2026 09:28
The album/album_release_date/duration_ms fields the API now returns are
useless to the UI if the UI doesn't render them. Add a third line under
each track result showing the album name + release year (e.g. "Parels
Voor De Zwijnen (2005)"), and put the track duration in mm:ss to the
right of the track name.

This is the same disambiguation lever the MCP agent now uses — two
"Sterrenstof" entries with the same name and artists are now visibly
different in the dropdown too: different album, different year,
different length. The album cover thumbnail was already there; this
just adds the textual context for cases where two recordings share
the same cover image.

``mm:ss`` duration formatter mirrors the existing inline format in
``queue.html`` for consistency. ``album_release_date.slice(0, 4)``
pulls the year from Spotify's variable-precision string
(``YYYY``/``YYYY-MM``/``YYYY-MM-DD``).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant