spotify-mcp-server / README.md
LeoWalker's picture
Harden hosted token verification dependencies (#6)
7537286 unverified
|
Raw
History Blame Contribute Delete
7.92 kB
metadata
title: Spotify MCP Server
emoji: 🎧
colorFrom: green
colorTo: gray
sdk: docker
app_port: 7860
license: mit

Spotify MCP Server

A self-hostable, Pydantic-first MCP v2 server that bundles Spotify Web API operations into nine agent-friendly tools. It can run locally with operating-system keyring storage or remotely with Scalekit OAuth 2.1, CIMD client discovery, and encrypted per-user credentials in Neon Postgres.

The server targets MCP 2026-07-28, uses the official Python mcp v2 SDK, and serves stateless Streamable HTTP. Local mode is loopback-only. Hosted mode refuses to start without OAuth, database, and encryption configuration.

Tools

Tool Purpose Side effects
search_catalog Search albums, artists, playlists, tracks, shows, episodes, and audiobooks None
get_item Fetch heterogeneous items with natural child expansions None
player_status Fetch playback, devices, and queue together None
player_control Run up to 20 ordered playback actions Changes playback
playlist_read List playlists and retrieve owned/collaborative contents None
playlist_modify Create and mutate playlists in an ordered batch Changes playlists
library_read Read saved items, followed artists, and membership None
library_modify Save/remove/follow/unfollow up to 40 URIs per action Changes library
listening_activity Read recent tracks and top tracks/artists None

Prompts and resources

The server exposes four curated workflows that compose the nine tools without adding another API surface:

Prompt Purpose
catch_up_on_podcasts Prioritize unfinished or unplayed episodes from saved shows
weekly_music_recap Summarize patterns in recent plays and top music
build_playlist_for_mood Search and create a private playlist for a mood or activity
now_playing_briefing Produce a compact playback, device, progress, and queue summary

The intentionally small resource catalog contains spotify://me. It returns the current user's Spotify display name and stable account_id; dynamic playback, library, and playlist state remains behind tools.

Podcast transcripts and inferred podcast listening history are intentionally out of scope because Spotify does not expose them through the supported Web API. The server does not embed, train on, download, or persist Spotify content.

Local setup

Prerequisites:

  • Python 3.11+
  • uv
  • A Spotify developer application
  1. In the Spotify developer dashboard, register http://127.0.0.1:8765/callback as a redirect URI.

  2. Install the locked project environment and create local configuration:

    uv sync --locked --extra dev
    cp .env.example .env
    
  3. Set SPOTIFY_CLIENT_ID in .env or your process environment.

  4. Authorize once; the refresh token is stored in the operating system keychain while access tokens remain in memory:

    uv run spotify-mcp-auth
    
  5. Start the loopback server:

    uv run spotify-mcp-server
    

The MCP endpoint is http://127.0.0.1:8000/mcp by default. Configure an MCP v2 client to use that Streamable HTTP URL. The server rejects non-loopback MCP_HOST values.

Hosted deployment

The included Dockerfile is configured for a Hugging Face Docker Space on port 7860. Hosted mode uses Scalekit as the MCP authorization server and creates a single spotify_credentials table in Neon. The table contains a Scalekit subject, an encrypted Spotify refresh token, and an update timestamp. Spotify access tokens and Spotify content are not persisted.

  1. Create a Spotify developer application, add each permitted Spotify account to its development allowlist, and register this redirect URI:

    https://<space-owner>-<space-name>.hf.space/spotify/callback
    
  2. In Scalekit, register https://<space-owner>-<space-name>.hf.space/mcp as the MCP Server URL and enable CIMD. Configure DCR separately only if compatibility with a non-CIMD client is required.

  3. Create a Neon database and use its pooled connection string for DATABASE_URL.

  4. Generate a stable encryption key once:

    uv run python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
    
  5. Configure the Space variables and secrets listed below, then deploy this repository to the Docker Space. The remote MCP endpoint is the same URL registered in Scalekit.

After a user authorizes the MCP client through Scalekit, their first Spotify tool request returns a short-lived /spotify/connect link. They authorize the shared Spotify developer application with PKCE, return to the MCP client, and retry the request. Spotify's development-account limits still apply; this server does not bypass them.

Configuration

Variable Default Purpose
SPOTIFY_CLIENT_ID Public Spotify application client ID; required for authorization
SPOTIFY_REDIRECT_URI http://127.0.0.1:8765/callback Registered loopback OAuth callback
SPOTIFY_API_BASE_URL https://api.spotify.com/v1 Spotify API base; primarily useful in tests
SPOTIFY_ACCOUNTS_BASE_URL https://accounts.spotify.com Spotify OAuth base; primarily useful in tests
MCP_HOST 127.0.0.1 Loopback bind address only
MCP_PORT 8000 Local Streamable HTTP port

Hosted mode sets MCP_DEPLOYMENT_MODE=hosted in the Docker image and additionally requires:

Variable Storage Purpose
MCP_SERVER_URL Variable Exact public endpoint ending in /mcp; also the validated token audience
SCALEKIT_ENVIRONMENT_URL Secret or variable Scalekit environment issuer URL
SCALEKIT_RESOURCE_ID Variable Scalekit MCP resource ID beginning with res_
DATABASE_URL Secret Neon pooled PostgreSQL connection string
TOKEN_ENCRYPTION_KEY Secret Stable base64 key used to derive separate refresh-token and OAuth-state keys
SPOTIFY_CLIENT_ID Secret or variable Public client ID of the hosted Spotify developer application
MCP_ALLOWED_SUBJECTS Variable, optional Comma-separated Scalekit user IDs allowed to connect Spotify

Spotify OAuth credentials are never returned from MCP tools. Access tokens remain memory-only, and refresh tokens are stored either by the operating system credential backend or encrypted in Neon. If MCP_ALLOWED_SUBJECTS is unset, any user whom your Scalekit and Spotify configurations admit may connect; set it for a server-side allowlist.

The server validates Scalekit access tokens locally against the public signing keys at <SCALEKIT_ENVIRONMENT_URL>/keys; Scalekit client credentials are not required by the runtime.

Development

uv run ruff check .
uv run ruff format --check .
uv run pytest

Tests use mocked Spotify HTTP responses and the MCP in-memory client; they do not require a Spotify account or make network calls. A live smoke test additionally needs an authorized account and, for playback controls, an active Spotify device and any account capabilities Spotify requires.

API compatibility and policy

The HTTP client enforces an explicit allowlist audited against Spotify's post-February 2026 Web API surface. Deprecated bulk and type-specific mutation endpoints are rejected before a request is sent. Request limits mirror Spotify's published limits. A 429 response is retried within a small bounded budget and, if still unsuccessful, is returned as a structured warning alongside any successful partial results.

Before distribution, recheck Spotify's current Developer Terms for the intended MCP host and LLM runtime. Spotify content must not be used to train or fine-tune a model, and this server provides no long-lived content cache or embeddings.

License

MIT