Spotify MCP服务器
Spotify的流式HTTP MCP服务器——搜索音乐、控制播放、管理播放列表和保存的歌曲。
作者 过度
\[!警告\] 此警告仅适用于为方便起见而包含的HTTP传输和OAuth包装器。它们是供个人/本地使用的,不是生产硬化的。 MCP工具和模式本身通过强大的验证、精简的输出、清晰的错误处理和其他最佳实践来实现。 如果您计划远程部署,请加强OAuth/HTTP层:适当的令牌验证、安全存储、TLS终止、严格的CORS/源代码检查、速率限制、审计日志记录以及遵守Spotify的条款。
动机
乍一看,“Spotify MCP”似乎没有必要——手动按下播放或跳过歌曲通常更快。当你不知道确切的标题(例如,“\[电影标题\]的配乐”),当你想“创建和播放一个符合我心情的播放列表”,或者当你使用语音时,它变得非常有用。此MCP允许LLM处理模糊意图→ 搜索→ 选择→ 控制循环,并返回所发生事情的明确确认。它与语音界面配合良好,可以连接到智能家居自动化的代理/工作流。
演示

*爱丽丝 --桌面AI助手*

*克劳德桌面版*
特性
- ✅ 搜索 --查找曲目、专辑、艺术家、播放列表
- ✅ 玩家控制 --播放、暂停、跳过、查找、音量、洗牌、重复、排队
- ✅ 设备转移 --在设备之间移动播放
- ✅ 播放列表 --创建、编辑、添加/删除曲目、重新排序
- ✅ 图书馆 --保存/删除曲目,检查是否已保存
- ✅ OAuth 2.1 --使用RS令牌映射保护PKCE流
- ✅ 双运行时 --Node.js/Bun或Cloudflare Workers
- ✅ 生产就绪 --加密令牌存储、速率限制、多用户支持
设计原则
- LLM友好:工具不镜像Spotify的API 1:1-界面简化和统一
- 第一批:操作使用数组(
queries[],operations[])尽量减少工具调用 - 清晰的反馈:每个响应都包含人类可读的内容
_msg成功/失败的原因 - 尽力验证:播放器控件验证设备、上下文和当前曲目
快速开始
1.安装
cd spotify-mcp
bun install2.配置
cp .env.example .env编辑 .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/callback3.配置Spotify仪表板
在中添加重定向URI Spotify开发者仪表板:
http://127.0.0.1:3001/oauth/callback
alice://oauth/callback4.跑步
bun dev
# MCP: http://127.0.0.1:3000/mcp
# OAuth: http://127.0.0.1:3001服务器说明(模型看到的内容)
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工具
search_catalog
搜索歌曲、艺术家、专辑和播放列表。
输入:
{
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";
}输出:
{
_msg: string;
batches: Array;
items: Array;
}>;
}player_status
读取当前玩家状态、设备、队列和当前曲目。
输入:
{ include?: ("player"|"devices"|"queue"|"current_track")[] }输出:
{
_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;
queue?: { current_id?: string; next_ids: string[] };
}spotify_control
通过批处理操作控制播放。
输入:
{
operations: Array;
parallel?: boolean; // Run concurrently (default: sequential)
}输出:
{
_msg: string;
results: Array;
summary: { ok: number; failed: number };
}spotify_playlist
管理播放列表。
输入:
// 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
管理已保存的曲目。
输入:
// 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[] }示例会话
显示所有工具协同工作的完整演练。
1.“在玩什么?”
工具: player_status
{ "include": ["player", "devices", "current_track"] }答复:
'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.“播放受此播放列表保护”
首先,获取播放列表项以查找曲目位置:
工具: spotify_playlist
{ "action": "items", "playlist_id": "2mMPIccnFiOd2xgkO0iABm", "limit": 50 }答复:
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然后在位置#7打球:
工具: spotify_control
{
"operations": [{
"action": "play",
"context_uri": "spotify:playlist:2mMPIccnFiOd2xgkO0iABm",
"offset": { "position": 7 }
}]
}答复:
Successful: play. Status: Now playing on 'MacBook Pro'. Current track: 'Protected'.3.“将此添加到我的收藏夹”
工具: spotify_library
{ "action": "tracks_add", "ids": ["1cRRIRrUiPnLOvsnWNhoH9"] }答复:
Saved 1 track:
- Protected — spotify:track:1cRRIRrUiPnLOvsnWNhoH94.“将音量调到100%”
工具: spotify_control
{
"operations": [{ "action": "volume", "volume_percent": 100 }]
}答复:
Successful: volume. Status: Now playing on 'MacBook Pro'. Current track: 'Protected'. Volume: 100%HTTP端点
POST /mcp--MCP JSON-RPC 2.0端点GET /mcp--SSE流(仅限Node.js)GET /health--健康检查GET /.well-known/oauth-authorization-server--OAuth AS元数据GET /.well-known/oauth-protected-resource--OAuth RS元数据
OAuth(端口+1):
GET /authorize--启动OAuth流GET /oauth/callback--提供商回调POST /token--代币兑换POST /revoke--撤销代币
客户端配置(克劳德桌面)
{
"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员工
设置
- 创建KV命名空间:
wrangler kv:namespace create TOKENS- 更新
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"- 设置秘密:
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注: TOKENS_ENC_KEY 加密存储在KV中的OAuth令牌(AES-256-GCM)。如果没有它,令牌将以明文存储(不建议用于生产)。- 部署:
wrangler deploy发展
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建筑
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故障排除
| 问题 | 解决方案 |
|---|---|
| “找不到设备” | 您使用的是设备名称而不是Device_id。请从以下位置获取实际id player_status → devices[].id |
| “无活动设备” | 在设备上打开Spotify,然后使用 player_status 列出设备 |
| “未经授权” | 完成OAuth流程。令牌可能已过期。 |
| “速率受限” | 请稍候,然后重试 |
许可证
麻省理工学院
