spotify-streamable-mcp-server
MCP 服务配置
复制以下 JSON 到 OPClaw 或其他 MCP 客户端的配置文件中即可使用
{
"mcpServers": {
"spotify": {
"args": [
"mcp-remote",
"http://127.0.0.1:3000/mcp",
"--transport",
"http-only"
],
"command": "bunx",
"env": {
"NO_PROXY": "127.0.0.1,localhost"
}
}
}
}
服务介绍
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 productionhardened.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 unnecessarypressing 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 smarthome automations.
Demo

Alice a desktop AI assistant

Claude Desktop
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
_msgwith 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 /mcpMCP JSON-RPC 2.0 endpointGET /mcpSSE stream (Node.js only)GET /healthHealth checkGET /.well-known/oauth-authorization-serverOAuth AS metadataGET /.well-known/oauth-protected-resourceOAuth RS metadata
OAuth (PORT+1):
GET /authorizeStart OAuth flowGET /oauth/callbackProvider callbackPOST /tokenToken exchangePOST /revokeRevoke 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
- Create KV namespace:
wrangler kv:namespace create TOKENS
- 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"
- 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_KEYencrypts OAuth tokens stored in KV (AES-256-GCM). Without it, tokens are stored in plaintext (not recommended for production).
- 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