使用说明
Spotify MCP Server
Control Spotify playback through Claude using the Model Context Protocol (MCP).
Uses PKCE authentication - just install and authorize your Spotify account.
Quick Start
1. Install
git clone https://github.com/panuhen/spotify-mcp.git ~/spotify-mcp
cd ~/spotify-mcp
python -m venv .venv
.venv/bin/pip install -e .
2. First Run - Authenticate
Run once in a terminal to authorize with your Spotify account:
~/spotify-mcp/.venv/bin/spotify-mcp --login
This opens your browser for Spotify login. After authorizing, the token is cached at ~/.spotify-mcp-token
and refreshed automatically from then on. The server itself never opens a browser: if a tool call
finds no usable token it returns an auth error that tells you to run --login.
3. Add to Claude Code
Add to ~/.mcp.json:
{
"mcpServers": {
"spotify": {
"command": "/home/YOUR_USER/spotify-mcp/.venv/bin/spotify-mcp"
}
}
}
Then enable in ~/.claude/settings.local.json:
{
"enabledMcpjsonServers": ["spotify"]
}
Restart Claude Code and you're ready!
Available Tools
Playback Control
play- Resume, or play a track (uri), an album/playlist/artist (context_uri), or one of your playlists by name (playlist)play_liked- Play your Liked Songs, shuffled unlessshuffle: false(see below)pause- Pause playbacknext- Skip to next trackprevious- Go to previous trackseek- Seek to position in trackset_volume- Set volume (0-100)shuffle- Toggle shuffle moderepeat- Set repeat mode (off/track/context)
Information
get_current_track- Get currently playing track infoget_playback_state- Get full playback stateget_queue- Get upcoming tracksget_devices- List available devices
Search & Library
search- Search for tracks, albums, artists, playlists (at most 10 per type; Spotify rejects more)add_to_queue- Add track to queueget_playlists- List your playlistsget_playlist_tracks- Get tracks from a playlist (Spotify refuses some playlists the user does not own:forbidden)add_to_playlist- Add tracks to a playlist by IDsave_tracks- Save tracks to Liked Songsremove_saved_tracks- Remove tracks from Liked Songsget_saved_tracks- Get your liked tracks
Library & Playlists by Name
Made for voice and small models: one call, the server finds the playing track and the playlist.
like_current- Save the playing track to Liked Songs (already_likedif it was there)add_current_to_playlist- Add the playing track to one of your playlists (playlist: name, ID, URI or link). Reads the playlist first and skips the add if the track is already there.create_if_missing: truecreates a private playlist when no name matches.find_playlist- Your 1-5 best matching playlists for a name: name, id, uri, owned, track countremove_from_playlist- Remove a track ("current", a URI or a link) from one of your playlistscreate_playlist- Create a playlist, private unlesspublic: true. Refuses a name you already own unlessforce: true.
The write tools (like_current, add_current_to_playlist, remove_from_playlist,
create_playlist) say so in their descriptions and carry MCP annotations
(remove_from_playlist is marked destructive).
Results are short, e.g. {"liked": "Blue Monday – New Order"},
{"added": "Blue Monday – New Order", "playlist": "Running"}, {"already_there": …},
{"removed": …}, {"not_in_playlist": …}, {"created": "Gym", "id": …, "uri": …}.
Spotify moved playlist contents to /playlists/{id}/items, library writes to /me/library and
playlist creation to /me/playlists in 2026; the old endpoints answer 403. The server uses the
new ones and falls back to the old ones only when the new one is missing (404).
Favourites are Liked Songs
"Favourites" are Liked Songs: like_current adds the playing track, play_liked plays them,
get_saved_tracks lists them. (An earlier local favorites file, ~/.spotify-mcp-favorites.json,
and its five tools were removed; the server no longer reads that file.)
play_liked starts up to 50 Liked Songs as a track list: by default a random page of 50, shuffled;
with shuffle: false the 50 newest in order. Spotify documents only albums, artists and playlists
as playback contexts, and has refused Liked Songs' own URI (spotify:user:<id>:collection) with
"Non supported context uri" and PLAYER_COMMAND_REJECTED, so the server reads the saved tracks
and plays them by URI. It is one play call: nothing is added to the queue.
Playlist names
add_current_to_playlist, remove_from_playlist, find_playlist and play's playlist take a
name and find the playlist in your library. The first rule that matches anything decides:
- the same name, ignoring case;
- the same after removing accents, punctuation, extra spaces and filler words ("my", "playlist");
- every word you said starts a word of the name ("run" finds "Running Mix"), or every word of the name is in what you said;
- a fuzzy match (Python's
difflibratio of at least 0.8, on the text and on a rough sound-alike spelling), so speech-to-text errors still land: "shrance" finds "Schranz".
If one rule finds several playlists, the call fails with bad_request and lists up to five names
so the model can ask which one. Nothing found gives not_found with the closest names.
The write tools only look at playlists you own or that are collaborative; a name that only
matches someone else's playlist gives forbidden. Your playlist list is cached for 60 seconds
and re-read after every write.
URIs and links
Every argument that takes a Spotify URI also takes an open.spotify.com link (with or without
https://, an intl-xx/ prefix or a ?si= query) and the old spotify:user:<name>:playlist:<id>
form. Playlist and track ID arguments also take a bare ID.
play puts each URI where Spotify needs it, whichever argument it came in:
| You pass | Sent to Spotify |
|---|---|
a track or episode, in uri or context_uri |
uris: [track] |
an album, playlist, artist or show, in uri or context_uri |
context_uri |
a track in uri and an album or playlist in context_uri |
the album/playlist, starting at that track |
| nothing | resume |
So a track passed as context_uri no longer fails with 400 "Non supported context uri".
add_to_queue takes tracks and episodes only and says so if given an album.
Errors
Every failed call returns one JSON object, with the MCP isError flag set:
{"error": "No Spotify device is active. Open Spotify on a computer or phone, or name a device_id from get_devices.",
"code": "no_active_device", "status": 404, "details": "Player command failed: No active device found"}
error: one plain sentence, fit to show or speak. Never a traceback, URL or token.code: a stable category to branch on (below).status: the HTTP status, when Spotify answered with one.details: Spotify's own message without the URL, when there is one.
The keys are always a subset of error, code, status, details, message.
| code | Meaning |
|---|---|
no_active_device |
Nothing is playing anywhere and no device was given. |
not_found |
No such track/playlist/etc., no playlist matches the name, nothing is playing (like_current, add_current_to_playlist), or Liked Songs is empty (play_liked). |
rate_limited |
Spotify sent 429 with a longer wait than the server will sit through; the message says when to retry. |
network |
Spotify could not be reached, or did not answer within the call's time budget. |
auth |
Not signed in, or the sign-in expired or was revoked. Run spotify-mcp --login. |
premium_required |
The command needs Spotify Premium. |
restricted |
Spotify refused a player command ("Restriction violated"): usually already playing/paused, or the device does not allow it. |
forbidden |
Spotify does not let this app do that (403), or a write to a playlist the user does not own. |
bad_request |
A missing or invalid argument, several playlists match a name, or Spotify rejected the request (400). |
unavailable |
Spotify answered with a 5xx. |
internal |
A bug in this server; details go to its stderr log. |
Arguments are checked by the server rather than by the MCP SDK, so a missing argument comes back
in this shape too ("Missing argument: position_ms."). Near misses are accepted: "50" or "50%"
for a number, "on"/"off" for a boolean, "all"/"one" for repeat, a single string for a list.
Reliability
- Stale connections. Spotify's edge closes an idle keep-alive connection after about ten
minutes, and a request sent on it at that moment fails at once with
RemoteDisconnected. The server closes pooled connections that have been idle for 120 seconds (time asleep counts), so the next call opens a fresh one, and if a connection still fails it retries:- before anything was sent (refused, DNS, connect timeout): any request, up to twice;
- after the request may have been sent (reset, closed without answer, read timeout): only
GET, PUT and DELETE, which set state and are safe to repeat (play, pause, volume, shuffle,
repeat, seek, transfer, play_liked, save/remove tracks, remove from a playlist). POST is not repeated,
because
next,previous,add_to_queue,add_to_playlist,add_current_to_playlistandcreate_playlistwould act twice; - the token refresh (a POST) is retried, because the next call would retry it anyway.
- IPv4 first. New connections try IPv4 addresses first and give each address 1.5 seconds, so a broken IPv6 route costs nothing instead of a full timeout per connection.
- Rate limits. A 429 with
Retry-Afterof up to 5 seconds is waited out and retried (twice at most). A longer wait returnsrate_limitedat once. A 5xx on GET/PUT/DELETE is retried up to twice. - Bounded calls. Every tool call has a 15-second budget shared by all of its requests; connect timeout 3 s and read timeout 8 s per attempt, never past the budget. The token refresh has the same limits (spotipy's own has no timeout).
- 401. A 401 on a token that should still be valid forces one refresh and one retry.
- Shared token cache. Several server processes (for example one per client) can share
~/.spotify-mcp-token: it is written atomically in spotipy's format, and a read that catches another process mid-write is retried.
Settings
Environment variables, all optional:
| Variable | Default | Effect |
|---|---|---|
SPOTIFY_MCP_AUTO_DEVICE |
off |
auto: when play finds no active device, start on the only available device, or on the device this server last saw active. With several devices and no known last one it lists them in the error instead of guessing. |
SPOTIFY_MCP_COMPACT |
off | 1: smaller results for small context windows (search returns 5 per type by default, no artist genres, no album or added_at in playlist and Liked Songs listings, queue capped at 10). |
SPOTIFY_MCP_CALL_TIMEOUT |
15 |
Seconds one tool call may take in total. |
SPOTIFY_MCP_CONNECT_TIMEOUT |
3 |
Connect timeout per attempt, seconds. |
SPOTIFY_MCP_READ_TIMEOUT |
8 |
Read timeout per attempt, seconds. |
SPOTIFY_MCP_IDLE_RESET |
120 |
Seconds idle after which pooled connections are dropped. |
SPOTIFY_MCP_MAX_RETRY_AFTER |
5 |
Longest Retry-After the server waits out, seconds. |
Results are compact JSON (no indentation) in every mode.
Usage Examples
Once configured, you can ask Claude:
- "What song is currently playing?"
- "Pause the music"
- "Play the next track"
- "Search for 'Bohemian Rhapsody' and add it to the queue"
- "Set the volume to 50%"
- "Turn on shuffle"
- "Show my playlists"
- "I like this song" or "add this to my favourites" (
like_current) - "Play my favourites" (
play_liked) - "Add this to my running playlist" (
add_current_to_playlist) - "Play my Schranz playlist" (
playwithplaylist)
Setup Your Own Spotify App
Optional. Every tool, writes included, works with the bundled client ID. To use your own app instead:
- Create an app at https://developer.spotify.com/dashboard
- In your app settings, add redirect URI:
http://127.0.0.1:8888/callback - Copy your Client ID and add to
~/spotify-mcp/.env:SPOTIPY_CLIENT_ID=your_client_id_here - Delete the cached token and sign in again:
rm ~/.spotify-mcp-token ~/spotify-mcp/.venv/bin/spotify-mcp --login - Restart the MCP server
Troubleshooting
"No active device" error
Make sure Spotify is open on at least one device (phone, desktop app, web player), or set
SPOTIFY_MCP_AUTO_DEVICE=auto to let play start on the only available device.
Authentication issues ("code": "auth")
Run spotify-mcp --login in a terminal. If that does not help, delete ~/.spotify-mcp-token first.
Token expired
The token auto-refreshes. If Spotify revokes it, calls return an auth error; run spotify-mcp --login.
Logs
The server logs one line per failed call and per retry to stderr, never tokens or request bodies.
Development
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest
The tests run offline: a throwaway HOME, no DNS for anything but localhost, and Spotify's
answers scripted at the HTTP adapter or served by a local keep-alive server.