用于Spotify的可流式传输HTTP MCP服务器提供了搜索目录、读取播放器状态、控制播放和设备、管理播放列表以及管理您保存歌曲的工具。
作者:overment。
[!WARNING] 本警告仅适用于为方便而包含的HTTP传输和OAuth包装器(授权服务器/资源服务器)。它们旨在供个人/本地使用,并未经过生产强化。捆绑的HTTP服务器仅用于方便连接您的代理或UI。
MCP工具和模式本身实现了强验证、简洁输出、清晰错误处理和其他最佳实践。
如果您计划远程部署,请用生产基础设施替换OAuth/HTTP层:适当的令牌验证/检查、安全存储、TLS终止、严格的CORS/来源检查、速率限制、审计日志记录、会话/令牌持久性以及符合Spotify条款的规定。
乍一看,“Spotify MCP”可能显得多余——手动播放或跳过歌曲通常更快。当您不知道确切的标题(例如,“来自[电影名称]的配乐”)时,或者当您想“创建并播放一个与我的心情相符的播放列表”,或者在使用语音时,它变得真正有用。此MCP允许LLM处理模糊意图→搜索→选择→控制循环,并返回明确的操作确认。它与语音界面配合良好,并可以连接到智能家庭自动化中的代理/工作流程。
示例:

注意:该UI^[是]Alice,一个桌面应用。这是我项目之一。

该UI^[是Claude Desktop。
git clone https://github.com/overment/mcp.git
cd mcp/servers/spotify
bun install
cp env.example .env
编辑.env并至少设置以下内容:
PORT=3030
HOST=127.0.0.1
AUTH_ENABLED=true
# Spotify开发者应用凭证
SPOTIFY_CLIENT_ID=<your_client_id>
SPOTIFY_CLIENT_SECRET=<your_client_secret>
# 重定向URI白名单
OAUTH_REDIRECT_ALLOWLIST=https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback
# 授权服务器回调(此服务器)用于接收Spotify代码
REDIRECT_URI=http://127.0.0.1:3031/spotify/callback
# Spotify端点(默认)
SPOTIFY_API_URL=https://api.spotify.com/v1
SPOTIFY_ACCOUNTS_URL=https://accounts.spotify.com
在您的Spotify开发者仪表板 → 应用 → 重定向URI,添加:
alice://oauth/callback - 如果您使用的是Alice应用。
http://127.0.0.1:3031/spotify/callback
bun dev
# MCP端点: http://127.0.0.1:3030/mcp
# 授权服务器: http://127.0.0.1:3031
将您的桥接客户端指向MCP端点,例如http://127.0.0.1:3030/mcp(参见“客户端配置”部分中的Claude Desktop)。
服务器向客户端提供简洁的描述,以便模型可以有效地使用它,而无需加载完整的模式。此描述总结了工具、关键规则和使用模式。
设计说明(有意使LLM友好):
queries[],operations[]),以最小化工具调用并使意图明确。Spotify Music[!NOTE] 下面的服务器描述是客户端呈现给模型的MCP服务器的“指令”。它旨在提供对服务器功能的清晰理解,而不深入每个模式细节。
使用这些工具来查找音乐、获取当前播放器状态、控制和转移播放、以及管理播放列表和保存的歌曲。
工具
- search_catalog: 查找歌曲、艺术家、专辑或播放列表。输入:queries[], types[album|artist|playlist|track],可选市场(两位字母),限制(1-50),偏移(0-1000),include_external['audio']。按查询顺序返回项目(如id、name、uri;曲目包括艺术家)。
- player_status: 读取当前播放器、可用设备、队列和当前曲目。首先使用此工具发现device_id,然后再进行控制。
- spotify_control: 批量控制,operations[]。action ∈ {play,pause,next,previous,seek,volume,shuffle,repeat,transfer,queue}。提供匹配参数(position_ms, volume_percent, repeat, device_id, context_uri/uris, offset, queue_uri, transfer_play)。可选parallel=true并发运行操作。工具会在动作后自动获取播放器状态,并报告播放是否活跃、目标设备及当前音量。在转移之前,调用player_status选择设备;如果没有活动设备,则提示用户打开Spotify。
- spotify_playlist: 管理播放列表。action ∈ {list_user,get,items,create,update_details,add_items,remove_items,reorder_items}。
- spotify_library: 管理保存的歌曲。action ∈ {tracks_get,tracks_add,tracks_remove,tracks_contains}。
注意事项
- 如果调用返回未经授权,请提示用户进行身份验证并重试。
- 除非另有指示,否则尽量使用小限制和最少轮询。
- 使用player_status选择device_id后再进行控制。如果找不到活动设备,请提示用户打开Spotify或将播放转移到列出的设备。
- 控制动作后,工具会包含简洁的状态。要获取完整详情,您可以调用player_status。如果不播放,请提示用户打开Spotify或将播放转移到列出的设备。
queries: string[]用于搜索;operations[]用于控制。_msg摘要。控制在可能的情况下验证上下文/曲目和设备。isError: true;批量结果包括每项{ ok, error? }和一个聚合摘要。structuredContent._msg(或失败时的structuredContent.error)content: [{ type: "text", text: "<相同消息>" }, ... ]这些旨在直接展示给用户,其中一个设计用于较旧的MCP客户端。
search_catalogqueries[],types[album|artist|playlist|track],可选market,limit(1-50),offset(0-1000),include_external['audio']。{
queries: string[];
types: ("album"|"artist"|"playlist"|"track")[];
market?: string; // 两位字母
limit?: number; // 1..50(默认20)
offset?: number; // 0..1000(默认0)
include_external?: "audio";
}
{
_msg: string;
queries: string[];
types: ("album"|"artist"|"playlist"|"track")[];
limit: number;
offset: number;
batches: Array<{
inputIndex: number;
query: string;
totals: Record<string, number>;
items: Array<SlimTrack|SlimAlbum|SlimArtist|SlimPlaylist>;
}>;
}
player_statusdevice_id,然后再进行控制。{ include?: ("player"|"devices"|"queue"|"current_track")[] }
{
_msg: string;
player?: {
is_playing: boolean;
shuffle_state?: boolean;
repeat_state?: "off"|"track"|"context";
progress_ms?: number;
timestamp?: number;
device_id?: string;
context_uri?: string|null;
};
current_track?: SlimTrack | null;
devices?: SlimDevice[];
devicesById?: Record<string, SlimDevice>;
queue?: { current_id?: string | null; next_ids: string[] };
}
spotify_controlparallel=true。尽可能验证设备/上下文/曲目,并返回简洁的状态。{
operations: Array<{
action: "play"|"pause"|"next"|"previous"|"seek"|"volume"|"shuffle"|"repeat"|"transfer"|"queue";
device_id?: string;
position_ms?: number;
volume_percent?: number;
shuffle?: boolean;
repeat?: "off"|"track"|"context";
context_uri?: string;
uris?: string[];
offset?: { position?: number; uri?: string };
queue_uri?: string;
transfer_play?: boolean;
}>;
parallel?: boolean;
}
{
_msg: string;
results: Array<{
index: number;
action: string;
ok: boolean;
error?: string;
note?: string;
device_id?: string;
device_name?: string;
from_device_id?: string;
from_device_name?: string;
}>;
summary: {
ok: number;
failed: number;
}
}
注意事项:
context_uri(可选offset)或uris,但不能同时设置两者。player_status获取完整详情。spotify_playlistlist_user,get,items,create,update_details,add_items,remove_items,reorder_items。// 列出当前用户的播放列表
{ action: "list_user"; limit?: number; offset?: number }
// 获取播放列表详情
{ action: "get"; playlist_id: string; market?: string; fields?: string }
// 获取播放列表项
{
action: "items";
playlist_id: string;
market?: string;
limit?: number;
offset?: number;
fields?: string;
additional_types?: string;
}
// 创建播放列表
{
action: "create";
name?: string;
description?: string;
public?: boolean;
collaborative?: boolean;
}
// 更新播放列表详情
{
action: "update_details";
playlist_id: string;
name?: string;
description?: string;
public?: boolean;
collaborative?: boolean;
}
// 向播放列表添加项(如spotify:track:ID)
{ action: "add_items"; playlist_id: string; uris: string[] }
// 从播放列表删除项
{
action: "remove_items";
playlist_id: string;
tracks: { uri: string; positions?: number[] }[];
snapshot_id?: string;
}
// 在播放列表内重新排序项
{
action: "reorder_items";
playlist_id: string;
range_start: number;
insert_before: number;
range_length?: number;
snapshot_id?: string;
}
// 所有动作使用的通用封套
type SpotifyPlaylistOutputObject = {
ok: boolean;
action: string;
_msg?: string; // 简洁的人类消息
error?: string; // 当ok=false时存在
code?:
| "unauthorized"
| "forbidden"
| "rate_limited"
| "bad_response"
| "invalid_arguments";
data?: unknown; // 根据动作变化(见下文)
};
// list_user → 播放列表概要
type ListUserData = {
limit: number;
offset: number;
total: number;
items: Array<{ id: string; uri: string; name: string; type: "playlist" }>;
};
// get → 播放列表完整详情(精简)
type GetData = {
id: string;
uri: string;
name: string;
description?: string;
owner_name?: string;
public?: boolean;
collaborative?: boolean;
tracks_total?: number;
};
// items → 带有零基位置的曲目和播放列表context_uri
type ItemsData = {
playlist_id: string;
playlist_uri: string; // spotify:playlist:...
limit: number;
offset: number;
total: number;
items: Array<{
type: "track";
id: string;
uri: string;
name: string;
artists: string[];
album?: string;
duration_ms?: number;
position: number; // 零基位置用于播放偏移
}>;
};
// create → 创建的播放列表详情
type CreateData = GetData;
// update_details → 确认更新
type UpdateDetailsData = { updated: true };
// add_items/remove_items/reorder_items → 结果状态的快照id
type SnapshotData = { snapshot_id?: string };
{ ok: true, action, _msg?, data? };失败响应设置{ isError: true, structuredContent: { ok:false, action, error, code? } }。items注释每个返回的曲目带有零基position,并包含playlist_uri用于精确的spotify_control.play与{ context_uri, offset: { position } }。spotify_librarytracks_get,tracks_add,tracks_remove,tracks_contains。// 列出保存的曲目
{ action: "tracks_get"; limit?: number; offset?: number; market?: string }
// 通过ID保存曲目
{ action: "tracks_add"; ids: string[] } // 曲目ID(非URI)
// 通过ID删除保存的曲目
{ action: "tracks_remove"; ids: string[] }
// 检查曲目是否已保存
{ action: "tracks_contains"; ids: string[] }
// 所有动作使用的通用封套
type SpotifyLibraryOutputObject = {
ok: boolean;
action: string;
_msg?: string; // 简洁的人类消息
error?: string; // 当ok=false时存在
code?:
| "unauthorized"
| "forbidden"
| "rate_limited"
| "bad_response"
| "invalid_arguments";
data?: unknown; // 根据动作变化(见下文)
};
// tracks_get → 保存的曲目
type TracksGetData = {
limit: number;
offset: number;
total: number;
items: Array<{
type: "track";
id: string;
uri: string;
name: string;
artists: string[];
album?: string;
duration_ms?: number;
}>;
};
// tracks_add → 确认
type TracksAddData = { saved: number; ids: string[] };
// tracks_remove → 确认
type TracksRemoveData = { removed: number; ids: string[] };
// tracks_contains → 查找结果
type TracksContainsData = { ids: string[]; contains: boolean[] };
{ ok: true, action, _msg?, data? };失败响应设置{ isError: true, structuredContent: { ok:false, action, error, code? } }。POST /mcp — JSON-RPC 2.0消息通过可流式传输HTTP。初始化会话并处理请求。GET /mcp — 服务器到客户端的通知流,针对现有会话;需要Mcp-Session-Id头。DELETE /mcp — 结束会话;需要Mcp-Session-Id头。GET /health — 健康探测。