Skip to main content
Glama
mcp-spotify-free
by vakandi

Why this exists

Spotify's public Web API hides the Liked Songs playlist behind Premium: GET /v1/me/tracks answers 403 on a free account, and so does every library-read endpoint built on it. Almost every "Spotify liked songs exporter" on GitHub uses spotipy.current_user_saved_tracks() — Premium only.

The Spotify web player has no such restriction: it serves the full library to free users. This server speaks the same internal protocol the web player uses, so a free account gets:

  • its complete Liked Songs playlist (591 tracks on the reference account)

  • catalog search — the same search box as the web player (tracks, artists, albums, playlists, profiles), so any song can be played, not only the liked ones

  • real playback control — play, pause, skip, volume

  • device switching — send the sound to the Chromecast instead of the laptop

Every other Spotify MCP server needs Premium (checked October 2026)

A sweep of the MCP registries and PyPI/GitHub found no other Spotify MCP server that works on a plain free account. They all go through the official Spotify Web API, which means a registered developer app + OAuth, and playback is Premium-only — before Spotify tightened the API in February 2026, where the app owner must now hold Premium and Dev Mode apps are capped at 5 authorized users. For example, quoting their own docs:

  • marcelmarais/spotify-mcp-server — prerequisites: "A Spotify Premium account" + "A registered Spotify Developer application"

  • mcp-spotify (PyPI) — "A Spotify Premium account (required by Spotify for Web API access)"

  • jamiew/spotify-mcp — "Requires a Spotify Premium account"

  • mcp-server-spotify (PyPI) / leoalord/spotify-mcp-server / iceener/spotify-streamable-mcp-server — bring your own developer app (client ID + secret + OAuth), Premium required for playback since Feb 2026

This server needs none of that: no developer app, no OAuth callback, no client secret, no Premium. It reads the session cookies of a browser where you are already logged in and speaks the web player's own protocol.


Related MCP server: Spotify MCP Server

Quick Start

npx mcp-spotify-free

npm install (global)

npm install -g mcp-spotify-free
mcp-spotify-free

From source

cd ~/Documents/mcp-spotify-free
python3 -m pip install -r requirements.txt
python3 server.py login

How it works

1. Session (no password, no OAuth app, no Premium) The server reads the Spotify cookies of a browser where you are already logged in (open.spotify.com), decrypts them locally with the macOS keychain (PBKDF2-HMAC-SHA1 + AES-128-CBC, the standard Chromium scheme), and exchanges them for a short-lived user access token.

Two sources are tried, in order:

  • ~/.config/mcp-spotify-free/cookies.json — the captured session (works with the browser closed)

  • the browser profile itself (Edge by default) — read straight from the Chromium cookie database, nothing is dumped or copied out

spotify_session_status() tells you which one is in use and whether Spotify still accepts it.

2. TOTP guard open.spotify.com/api/token is protected by a 6-digit TOTP (HMAC-SHA1, 30 s period) whose secret is XOR-obfuscated inside the web-player bundle. spotify_free/totp.py de-obfuscates it, re-reads the live bundle on every run (the file name changes with every Spotify release — the current one is looked up in the open.spotify.com HTML), and keeps a hardcoded fallback so a network hiccup never breaks the server.

3. Library POST api-partner.spotify.com/pathfinder/v2/query — the same GraphQL-ish gateway the web player uses — with the fetchPlaylistContents operation and the Liked Songs playlist alias spotify:playlist:37i9dQZF1F5p3rmiWPIYgZ (also hardcoded in the web player, not account specific).

4. Catalog search Same gateway, searchTopResultsList operation — the endpoint behind the web player's search box. It works on a free account.

5. Playback The Spotify Connect "harmony" command protocol:

POST connect-state/v1/player/command/from/{source}/to/{target}
PUT  connect-state/v1/connect/volume/from/{source}/to/{target}

Addressing the command to the Chromecast's device id is the "play on" — the audio moves from the laptop to the TV.

Device policy — the server leaves no trace The command protocol needs a source device. Registering one would add a permanent entry ("MCP Spotify Free") to the account's device list, and every crashed run would leave another ghost behind. Instead the server reuses the account's own web-player device (Web Player (Microsoft Edge)) as the source: the commands are accepted exactly the same way, and nothing is added to the device list. Only if no web-player device exists does the server register one — with a stable, persisted device id, so even then the list never grows beyond a single entry.

cleanup_devices() removes the entries left by earlier versions. It uses DELETE /connect-state/v1/devices/{id}, which drops the entry from the account's device list immediately (the hobs_ variant only drops the subscription and leaves a visible ghost for hours).


IDE Integration

Cursor / Windsurf / VS Code

{
  "mcpServers": {
    "spotify-free": {
      "command": "npx",
      "args": ["mcp-spotify-free"]
    }
  }
}

Claude Desktop

{
  "mcpServers": {
    "spotify-free": {
      "command": "npx",
      "args": ["mcp-spotify-free"]
    }
  }
}

OpenCode / Claude Code

claude mcp add spotify-free --scope user -- npx mcp-spotify-free

Through mcp-cli (this machine)

The server is registered in ~/.config/mcp/mcp_servers.json under spotify-free, so it is reachable through the mcp-cli wrapper:

mcp-cli info spotify-free                                  # list the tools
mcp-cli call spotify-free spotify_status '{}'               # account + devices
mcp-cli call spotify-free play_song '{"query":"one more time","device":"Chromecast"}'
mcp-cli call spotify-free cleanup_devices '{}'              # drop leftover devices

Tools

  • get_docs() — the full usage guide, returned as one big text. Start here.

  • spotify_status() — account, token state, liked-song count, now playing, every Connect device.

  • list_liked_songs(limit, offset, query) — the Liked Songs playlist, newest first. With query the whole library is scanned.

  • search_liked_songs(query, limit) — only the matches, whole library.

  • search_spotify(query, limit) — the whole catalog: tracks, artists, albums, playlists, profiles.

  • play_song(query, device, index) — the main one: looks the title up in the Liked Songs, falls back to the catalog, and plays the best match. source in the answer tells you where the match came from.

  • play_liked_song(query, device, index) — Liked Songs only.

  • play_track(uri, device) — play a raw spotify:track:... URI.

  • chromecast_sent(track_query, action) — the shortcut: always the Chromecast. action = play / resume / pause / next / previous.

  • pause_playback(device) / resume_playback(device)

  • skip_next(device) / skip_previous(device)

  • set_volume(level, device) — 0-100.

  • list_devices() — every Connect device with its volume and active flag.

  • now_playing() — current track, playing/paused, position, active device.

  • cleanup_devices() — remove the leftover MCP Spotify Free Connect devices of the earlier versions.

  • spotify_session_status() — diagnose the session.

  • spotify_login() / spotify_logout() — capture / forget the session.

Every tool returns a structured payload: ok, action, plus the relevant data — and on failure error with a human-readable cause. When the failure is the session (bad or expired browser cookies, 401 / 403 from the token endpoints), the payload also carries a remedy block with paste-ready commands for the agent: show a macOS popup explaining the login is needed, open https://open.spotify.com in the default browser, run spotify_login(), then retry the original call.

Typical agent flow

get_docs()
  -> list_devices()                      # find the Chromecast
  -> play_song("one more time", "Chromecast")   # library first, catalog fallback
  -> pause_playback()

Direct use from Python

from spotify_free import SpotifyFreeClient

client = SpotifyFreeClient()

for track in client.liked_songs(limit=5):
    print(track["name"], "-", ", ".join(track["artists"]), "|", track["added_at"])

hits = client.search_catalog("le songe")       # the whole catalog
client.play_track(hits["tracks"][0]["uri"], device="Chromecast")
client.pause()

Or run the bundled demo:

python3 examples/quickstart.py

Requirements

Item

Detail

OS

macOS (cookie decryption uses the login keychain)

Browser

Edge by default; Chrome, Brave, Vivaldi, Opera supported — must have an active open.spotify.com session

Python

3.10+

Environment variables:

  • SPOTIFY_BROWSER — edge (default), chrome, brave, vivaldi, opera

  • SPOTIFY_COOKIES_FILE — path to a JSON file of cookies (bypasses the browser)

  • SPOTIFY_USE_PROXY — set to 1 to send traffic through the system proxy (off by default: the local proxy-local plugin only carries LLM traffic)


Architecture

mcp-spotify-free/
├── server.py               FastMCP server + all tool definitions
├── spotify_free/
│   ├── client.py           token, library, catalog search, devices, playback
│   ├── cookies.py          Chromium cookie decryption (macOS keychain)
│   ├── session.py          session capture / storage / validation
│   └── totp.py             web-player TOTP (de-obfuscation + auto-discovery)
├── examples/quickstart.py  dependency-free demo
├── requirements.txt
└── pyproject.toml

Privacy & terms

  • Cookies are read from your own machine and decrypted locally. They are never written to disk (except the captured copy in ~/.config/mcp-spotify-free/cookies.json, mode 0600) and never sent anywhere but Spotify.

  • No password is ever handled, and no Spotify developer app / client secret is involved.

  • The access token lives in memory for the lifetime of the process.

  • Spotify's Developer Terms do not allow third-party use of the web-player token endpoint. This is a personal tool for your own account — do not ship it as a product or redistribute it.


Troubleshooting

  • Any session/cookie failure (401, 403, no spotify cookies found) — the failing tool returns a remedy field instead of a dead error: it tells the calling agent to show the user a macOS popup ("log in so music can play"), to open open.spotify.com in the default browser, to spotify_login() and retry. Nothing else to do by hand.

  • no spotify cookies found — log in to open.spotify.com in the browser named by SPOTIFY_BROWSER, then retry.

  • token request failed (400) — the TOTP version moved on; the server re-reads the bundle automatically, but check that open.spotify.com is reachable.

  • no active Spotify device — start the Spotify app (or any Connect device) so the account has an active player.

  • no device matching 'x' — call list_devices() for the exact names.

  • A "MCP Spotify Free" entry shows up in the Spotify app — run cleanup_devices(); it removes the entry from the account's device list.

  • Keychain prompt on first run — macOS asks once to let security read the browser's Safe Storage item.


Contributing

PRs welcome. Open an issue first for non-trivial changes.

License

MIT — see LICENSE.

Available Tools

20 tools
chromecast_sentChromecast SentC

Shortcut: drive the Chromecast.

Always targets the device named "Chromecast" (falls back to a Google / Chromecast device if the name differs).

Inputs: action, track_query. Returns: object

cleanup_devicesCleanup DevicesA

Remove the leftover "MCP Spotify Free" Connect devices of this server.

The server does not register a device of its own any more (it reuses the account's web-player device to send the commands), but earlier versions did - those entries are removed here, right away.

Inputs: no named parameters. Returns: object

get_docsGet DocsA

Full usage guide for every tool of this server (one big text).

Inputs: no named parameters. Returns: result

list_devicesList DevicesB

List every Spotify Connect device (Chromecast, MacBook, phones, TVs).

Inputs: no named parameters. Returns: object

list_liked_songsList Liked SongsC

Read the Liked Songs playlist (free accounts included).

Inputs: limit, query, offset. Returns: object

now_playingNow PlayingB

Current track, play/pause state and the active device.

Inputs: no named parameters. Returns: object

pause_playbackPause PlaybackC

Pause playback ("" = the device that is currently playing).

Inputs: device. Returns: object

play_liked_songPlay Liked SongC

Play a liked song, optionally on a specific Spotify Connect device.

Inputs: query (required), index, device. Returns: object

play_songPlay SongA

Play a song by name - Liked Songs first, then the whole catalog.

This is the tool to use when the user says "play ": it looks the title/artist up in the Liked Songs and, if nothing matches, falls back to the Spotify catalog search.

Inputs: query (required), index, device. Returns: object

play_trackPlay TrackB

Play a raw Spotify track URI on a device.

Inputs: uri (required), device. Returns: object

resume_playbackResume PlaybackC

Resume playback ("" = the device that is currently playing).

Inputs: device. Returns: object

search_liked_songsSearch Liked SongsA

Search the whole Liked Songs library for a title / artist / album.

Inputs: query (required), limit. Returns: object

search_spotifySearch SpotifyA

Search the whole Spotify catalog (not only your library).

Same search box as the web player: tracks, artists, albums, playlists and profiles. Use it when the song is not in the Liked Songs.

Inputs: query (required), limit. Returns: object

set_volumeSet VolumeB

Set the volume of a device (0-100).

Inputs: level (required), device. Returns: object

skip_nextSkip NextC

Skip to the next track.

Inputs: device. Returns: object

skip_previousSkip PreviousC

Go back to the previous track.

Inputs: device. Returns: object

spotify_loginSpotify LoginA

Capture a working Spotify session (free account is enough).

Tries, in order: the stored session, then the browser profile. If the browser keeps the session cookie in memory, the user is asked to quit the browser (Cmd+Q) so Chromium flushes it to disk - the tool keeps polling until the session is captured, then stores it on the filesystem so later runs work without the browser.

Inputs: browser, timeout. Returns: object

spotify_logoutSpotify LogoutA

Forget the stored session (does not log the account out of Spotify).

Inputs: no named parameters. Returns: object

spotify_session_statusSpotify Session StatusB

Diagnose the session: where the cookies come from and if they work.

Inputs: no named parameters. Returns: object

spotify_statusSpotify StatusB

Account, token, library size, now playing and every Connect device.

Inputs: no named parameters. Returns: object

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 20 tools

    Disambiguation3/5

    The playback trio play_song / play_liked_song / play_track all ultimately start playback of a track, and an agent could easily misselect between them (play_song already falls back to catalog search and can target devices, so play_liked_song's niche is thin). Similarly search_liked_songs vs list_liked_songs vs search_spotify borders on overlapping, though descriptions do help distinguish library vs catalog. The rest (pause/resume/skip/volume/devices/session) are clearly distinct.

    Naming Consistency4/5

    Most tools follow a snake_case verb_noun pattern (play_song, list_devices, set_volume, search_spotify), with a consistent spotify_ prefix on session tools. Outliers are chromecast_sent (a noun/past-tense name that doesn't describe an action) and now_playing (noun phrase), but overall the convention is readable and predictable.

    Tool Count3/5

    At 20 tools this sits at the heavy end of the recommended 3-15 range for what is essentially a single-domain playback controller. Some of that weight comes from near-duplicate playback entries and a bulky get_docs tool that could be folded into status output.

    Completeness4/5

    Core lifecycle is covered: session login/logout/status, device listing and cleanup, play/pause/resume/skip forward-back, volume, search, library listing and now-playing. Minor gaps remain (no seek/position, no shuffle or repeat, no queue management, no playlist creation), but an agent can work around these for the stated free-account playback purpose.

    Maintenance

    ActivityMaintained
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers