Skip to main content
Glama

Spotify Streamable MCP Server

by iceener

Spotify MCP Server

Streamable HTTP MCP server for Spotify — search music, control playback, manage playlists and saved songs.

Author: overment

WARNING

This warning applies only to the HTTP transport and OAuth wrapper included for convenience. They are intended for personal/local use and are not production‑hardened.

The MCP tools and schemas themselves are implemented with strong validation, slim outputs, clear error handling, and other best practices.

If you plan to deploy remotely, harden the OAuth/HTTP layer: proper token validation, secure storage, TLS termination, strict CORS/origin checks, rate limiting, audit logging, and compliance with Spotify's terms.

Motivation

At first glance, a "Spotify MCP" may seem unnecessary—pressing play or skipping a song is often faster by hand. It becomes genuinely useful when you don't know the exact title (e.g., "soundtrack from [movie title]"), when you want to "create and play a playlist that matches my mood", or when you're using voice. This MCP lets an LLM handle the fuzzy intent → search → selection → control loop, and it returns clear confirmations of what happened. It works well with voice interfaces and can be connected to agents/workflows for smart‑home automations.

Demo

Alice App Demo

Alice

Claude Desktop Demo

Claude Desktop

Related MCP server: Spotify MCP Server

Features

  • Search — Find tracks, albums, artists, playlists

  • Player Control — Play, pause, skip, seek, volume, shuffle, repeat, queue

  • Device Transfer — Move playback between devices

  • Playlists — Create, edit, add/remove tracks, reorder

  • Library — Save/remove tracks, check if saved

  • OAuth 2.1 — Secure PKCE flow with RS token mapping

  • Dual Runtime — Node.js/Bun or Cloudflare Workers

  • Production Ready — Encrypted token storage, rate limiting, multi-user support

Design Principles

  • LLM-friendly: Tools don't mirror Spotify's API 1:1 — interfaces are simplified and unified

  • Batch-first: Operations use arrays (queries[], operations[]) to minimize tool calls

  • Clear feedback: Every response includes human-readable _msg with what succeeded/failed

  • Best-effort verification: Player control verifies device, context, and current track

Quick Start

1. Install

cd spotify-mcp bun install

2. Configure

cp .env.example .env

Edit .env:

PORT=3000 AUTH_ENABLED=true # From https://developer.spotify.com/dashboard SPOTIFY_CLIENT_ID=your_client_id SPOTIFY_CLIENT_SECRET=your_client_secret # OAuth OAUTH_SCOPES=playlist-read-private playlist-read-collaborative playlist-modify-public playlist-modify-private user-read-playback-state user-modify-playback-state user-read-currently-playing user-library-read user-library-modify OAUTH_REDIRECT_URI=alice://oauth/callback OAUTH_REDIRECT_ALLOWLIST=alice://oauth/callback

3. Configure Spotify Dashboard

Add redirect URIs in Spotify Developer Dashboard:

http://127.0.0.1:3001/oauth/callback alice://oauth/callback

4. Run

bun dev # MCP: http://127.0.0.1:3000/mcp # OAuth: http://127.0.0.1:3001

Server Instructions (What the Model Sees)

Use these tools to find music, get the current player status, control and transfer playback, and manage playlists and saved songs. Tools - search_catalog: Find songs, artists, albums, or playlists - player_status: Read current player, available devices, queue, and current track - spotify_control: Batch control playback (play, pause, next, previous, seek, volume, shuffle, repeat, transfer, queue) - spotify_playlist: Manage playlists (list, get, items, create, update, add/remove items, reorder) - spotify_library: Manage saved songs (get, add, remove, contains) CRITICAL: device_id - device_id is a long alphanumeric hash, NOT a human-readable name - NEVER use the device name (like "MacBook Pro" or "iPhone") as device_id — this will fail! - Always copy the exact device_id value from player_status → devices[].id or player.device_id

Tools

search_catalog

Search songs, artists, albums, and playlists.

Input:

{ queries: string[]; // Search terms types: ("album"|"artist"|"playlist"|"track")[]; // What to search market?: string; // 2-letter country code limit?: number; // 1-50 (default 20) offset?: number; // 0-1000 (default 0) include_external?: "audio"; }

Output:

{ _msg: string; batches: Array<{ query: string; totals: Record<string, number>; items: Array<{ type, id, uri, name, artists?, album? }>; }>; }

player_status

Read current player state, devices, queue, and current track.

Input:

{ include?: ("player"|"devices"|"queue"|"current_track")[] }

Output:

{ _msg: string; player?: { is_playing: boolean; device_id?: string; // Use this for control! shuffle_state?: boolean; repeat_state?: "off"|"track"|"context"; progress_ms?: number; context_uri?: string|null; }; current_track?: { type, id, uri, name, artists, album, duration_ms } | null; devices?: Array<{ id: string; // Use this for control! name: string; type: string; is_active: boolean; volume_percent?: number; }>; queue?: { current_id?: string; next_ids: string[] }; }

spotify_control

Control playback with batch operations.

Input:

{ operations: Array<{ action: "play"|"pause"|"next"|"previous"|"seek"|"volume"|"shuffle"|"repeat"|"transfer"|"queue"; device_id?: string; // Long alphanumeric hash from player_status position_ms?: number; // For seek or play start position volume_percent?: number; // 0-100 for volume shuffle?: boolean; repeat?: "off"|"track"|"context"; context_uri?: string; // Album/playlist URI uris?: string[]; // Track URIs (don't combine with context_uri) offset?: { position?: number; uri?: string }; queue_uri?: string; transfer_play?: boolean; }>; parallel?: boolean; // Run concurrently (default: sequential) }

Output:

{ _msg: string; results: Array<{ index, action, ok, error?, device_id?, device_name? }>; summary: { ok: number; failed: number }; }

spotify_playlist

Manage playlists.

Input:

// List user playlists { action: "list_user"; limit?: number; offset?: number } // Get playlist details { action: "get"; playlist_id: string } // Get playlist tracks (includes position for play offset) { action: "items"; playlist_id: string; limit?: number; offset?: number } // Create playlist { action: "create"; name?: string; description?: string; public?: boolean } // Update details { action: "update_details"; playlist_id: string; name?: string; description?: string } // Add tracks { action: "add_items"; playlist_id: string; uris: string[] } // Remove tracks { action: "remove_items"; playlist_id: string; tracks: { uri: string }[] } // Reorder tracks { action: "reorder_items"; playlist_id: string; range_start: number; insert_before: number }

spotify_library

Manage saved tracks.

Input:

// List saved tracks { action: "tracks_get"; limit?: number; offset?: number } // Save tracks (use track IDs, not URIs) { action: "tracks_add"; ids: string[] } // Remove saved tracks { action: "tracks_remove"; ids: string[] } // Check if saved { action: "tracks_contains"; ids: string[] }

Example Session

A complete walkthrough showing all tools working together.

1. "What's playing?"

Tool: player_status

{ "include": ["player", "devices", "current_track"] }

Response:

'Come With Me - Radio Mix' is playing on 'MacBook Pro' (device_id: "8fc48c51d766..."). Available devices (use device_id for control): • MacBook Pro (Computer) [ACTIVE] → device_id: "8fc48c51d766..."

2. "Play Protected from this playlist"

First, get playlist items to find the track position:

Tool: spotify_playlist

{ "action": "items", "playlist_id": "2mMPIccnFiOd2xgkO0iABm", "limit": 50 }

Response:

Loaded 50 items from 'Nora' (context: spotify:playlist:2mMPIccnFiOd2xgkO0iABm). - #0 Come with Me - Radio Mix — spotify:track:2FxwTax2LGVybNIrreiwXv - #7 Protected — spotify:track:1cRRIRrUiPnLOvsnWNhoH9 … and more

Then play at position #7:

Tool: spotify_control

{ "operations": [{ "action": "play", "context_uri": "spotify:playlist:2mMPIccnFiOd2xgkO0iABm", "offset": { "position": 7 } }] }

Response:

Successful: play. Status: Now playing on 'MacBook Pro'. Current track: 'Protected'.

3. "Add this to my favorites"

Tool: spotify_library

{ "action": "tracks_add", "ids": ["1cRRIRrUiPnLOvsnWNhoH9"] }

Response:

Saved 1 track: - Protected — spotify:track:1cRRIRrUiPnLOvsnWNhoH9

4. "Turn volume up to 100%"

Tool: spotify_control

{ "operations": [{ "action": "volume", "volume_percent": 100 }] }

Response:

Successful: volume. Status: Now playing on 'MacBook Pro'. Current track: 'Protected'. Volume: 100%

HTTP Endpoints

  • POST /mcp — MCP JSON-RPC 2.0 endpoint

  • GET /mcp — SSE stream (Node.js only)

  • GET /health — Health check

  • GET /.well-known/oauth-authorization-server — OAuth AS metadata

  • GET /.well-known/oauth-protected-resource — OAuth RS metadata

OAuth (PORT+1):

  • GET /authorize — Start OAuth flow

  • GET /oauth/callback — Provider callback

  • POST /token — Token exchange

  • POST /revoke — Revoke tokens

Client Configuration (Claude Desktop)

{ "mcpServers": { "spotify": { "command": "bunx", "args": ["mcp-remote", "http://127.0.0.1:3000/mcp", "--transport", "http-only"], "env": { "NO_PROXY": "127.0.0.1,localhost" } } } }

Cloudflare Workers

Setup

  1. Create KV namespace:

wrangler kv:namespace create TOKENS
  1. Update wrangler.toml:

[[kv_namespaces]] binding = "TOKENS" id = "your-kv-id" [vars] AUTH_ENABLED = "true" OAUTH_SCOPES = "playlist-read-private user-read-playback-state user-modify-playback-state user-library-read user-library-modify"
  1. Set secrets:

wrangler secret put SPOTIFY_CLIENT_ID wrangler secret put SPOTIFY_CLIENT_SECRET # Generate encryption key (32-byte base64url): openssl rand -base64 32 | tr -d '=' | tr '+/' '-_' # Copy the output, then: wrangler secret put TOKENS_ENC_KEY # Paste the generated key when prompted

Note: TOKENS_ENC_KEY encrypts OAuth tokens stored in KV (AES-256-GCM). Without it, tokens are stored in plaintext (not recommended for production).

  1. Deploy:

wrangler deploy

Development

bun dev # Start with hot reload bun run typecheck # TypeScript check bun run lint # Lint code bun run build # Production build bun start # Run production

Architecture

src/ ├── shared/ │ ├── tools/ # Tool definitions (work in Node + Workers) │ │ ├── player-status.ts │ │ ├── search-catalog.ts │ │ ├── spotify-control.ts │ │ ├── spotify-playlist.ts │ │ └── spotify-library.ts │ ├── oauth/ # OAuth flow (PKCE, discovery) │ └── storage/ # Token storage (file, KV, memory) ├── services/ │ └── spotify/ # Spotify API clients │ ├── sdk.ts # SpotifyApi wrapper │ ├── player.ts # Player API │ ├── catalog.ts # Search API │ └── oauth.ts # Token refresh ├── schemas/ │ ├── inputs.ts # Zod input schemas │ └── outputs.ts # Zod output schemas ├── config/ │ └── metadata.ts # Server & tool descriptions ├── index.ts # Node.js entry └── worker.ts # Workers entry

Troubleshooting

Issue

Solution

"Device not found"

You used device name instead of device_id. Get the actual ID from

player_status → devices[].id

"No active device"

Open Spotify on a device, then use

player_status

to list devices

"Unauthorized"

Complete OAuth flow. Tokens may have expired.

"Rate limited"

Wait a moment and retry

License

MIT

-
security - not tested
F
license - not found
-
quality - not tested

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/iceener/spotify-streamable-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server