MCP server: playlist import (files, TIDAL, Deezer, YouTube Music), MusicBrainz resolution, local library diff, Soulseek matching and library tidying for claude-music
25 tools with complete schemas and descriptions, but quality varies significantly. Naming is verb-forward and mostly clear (import_*, connect_*, list_*, etc.), following pattern:tool conventions. Descriptions range from 50 - 400 chars, generally adequate but some lack actionable context (e.g., 'Approve the current match state' doesn't explain what 'current' means or what happens next). All parameters have types and descriptions. Output schemas are not documented in the source, LLMs cannot predict return structure. Error handling is minimal; no recovery guidance visible. Composition is sound: tools chain logically (scan_library → diff_library → match_playlist → review_candidates → approve → queue_approved → sync_downloads). However, several tools combine multiple concerns (e.g., queue_approved both queues AND requires confirmation, mixing state mutation with user interaction).
Approve the current match state for a playlist (all picked candidates and all auto-matched tracks). Returns the download queue that will be submitted.
Hand finished downloads to `beet import` (dry run first, confirm=True to import). Requires beets to be installed. Beets uses MusicBrainz ids as hints and respects its own configuration for copy/move.
Connect a streaming service so its playlists can be listed and imported directly. tidal: official API with a browser login (PKCE). First call returns an authorize_url (and tries to open it); after the user has approved in the browser, call again to finish. Needs the tidal_client_id user config. youtube-music: pass headers_raw (request headers copied from a logged-in music.youtube.com tab) or auth_file (an existing ytmusicapi browser.json). deezer: nothing to connect, public playlists only. Tokens are stored 0600 under the plugin data dir and never leave this machine.
Find which tracks from a playlist are already in the local library (by MusicBrainz recording id). Returns a summary; the rest need to be matched on Soulseek.
Forget a service's stored tokens / cookies.
Output schemas not documented. LLMs cannot predict return structure (fields, types, pagination). Tools like playlist_tracks, approve, and match_playlist return complex objects but no schema is visible in source.
Confirmation pattern mixed with state mutation. queue_approved requires confirm=false first (dry-run), then confirm=true to execute. This splits one logical action across two calls and risks the LLM forgetting the confirmation step.
Minimal error handling guidance. No visible recovery hints (e.g., 'If MusicBrainz lookup fails, try resolve_playlist with skip_existing=false'). Errors are likely raw exceptions without actionable next steps for the LLM.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 68 | 2026-07-28+ | v2 |
Import a playlist file: Spotify data export (Playlist*.json / YourLibrary.json), Exportify CSV, generic CSV (header synonyms or column_mapping like {"title": "Song", "artist": "Band"}), M3U/M3U8, JSPF or XSPF. A Spotify export with several playlists imports them all unless playlist_name picks one. Each playlist is written as JSPF under the plugin data dir and gets a playlist_id. Spotify exports have no durations; resolve_playlist fills them in from MusicBrainz.
Import a playlist from a connected streaming service.
List playlists from a connected streaming service.
Match the tracks in a playlist on Soulseek through the running Nicotine+ (via the 'nicotine' bridge). Returns a job_id; poll playlist_status until it finishes.
Delete a playlist.
List all imported playlists.
Poll the status of a match_playlist job. Returns the job state and a summary of matches found so far.
Get a slice of tracks from a playlist.
Queue the approved matches for download on Soulseek. Nothing is downloaded until confirm=True is called after the user has seen the totals.
Request music by name: adds items to a persistent 'Requests' playlist, expands albums through MusicBrainz, matches on Soulseek and queues the confident matches without a separate confirmation (the request is the instruction). Reports what it understood right away.
Canonicalise a playlist against MusicBrainz: fill in missing durations, resolve artist/title ambiguities, and tag each track with its MusicBrainz recording and release ids.
Review the candidates found for a track and optionally pick one to download.
Scan the local music directory and index all audio files. Returns a summary; call once per session before diff_library.
Sync finished downloads from Nicotine+ into the local library and update the playlist state. Tidies newly downloaded tracks (tags + Artist/Album/NN - Title) unless auto_tidy is off.
Analyse the local library for tidying opportunities: missing tags, inconsistent naming, duplicates. Returns a dry-run report without making changes.
Apply the tidying plan: fix tags and rename files (Artist/Album/NN - Title). Nothing is deleted. Confirm=True to apply; without it, returns the plan.
Tidy files that arrived by other routes (not through sync_downloads): fix tags and rename to Artist/Album/NN - Title.
Resolve a TROI playlist: fetch tracks from ListenBrainz and match them on Soulseek.
Scan a MusicBrainz-tagged collection through the ListenBrainz content resolver (TROI). Returns matching tracks from the local library.
Write a finished playlist as M3U (with local paths for downloaded tracks and metadata for the rest).
Generic descriptions on discovery tools. list_remote_playlists, playlist_list, and playlist_delete have descriptions under 60 chars. LLMs cannot distinguish when to call list_remote_playlists vs playlist_list without more context.
Stateful workflow without explicit state machine. Tools like match_playlist → playlist_status → review_candidates → approve → queue_approved form a strict sequence, but there is no documented state enum or guard preventing out-of-order calls.