返回市场
Spotify可播放流MCP服务器

Spotify可播放流MCP服务器

作者:iceener59 星标更新:2025-09-07

项目介绍

Spotify MCP Server (Streamable HTTP / OAuth / 远程)

用于Spotify的可流式传输HTTP MCP服务器提供了搜索目录、读取播放器状态、控制播放和设备、管理播放列表以及管理您保存歌曲的工具。

作者:overment

[!WARNING] 本警告仅适用于为方便而包含的HTTP传输和OAuth包装器(授权服务器/资源服务器)。它们旨在供个人/本地使用,并未经过生产强化。捆绑的HTTP服务器仅用于方便连接您的代理或UI。

MCP工具和模式本身实现了强验证、简洁输出、清晰错误处理和其他最佳实践。

如果您计划远程部署,请用生产基础设施替换OAuth/HTTP层:适当的令牌验证/检查、安全存储、TLS终止、严格的CORS/来源检查、速率限制、审计日志记录、会话/令牌持久性以及符合Spotify条款的规定。

动机

乍一看,“Spotify MCP”可能显得多余——手动播放或跳过歌曲通常更快。当您不知道确切的标题(例如,“来自[电影名称]的配乐”)时,或者当您想“创建并播放一个与我的心情相符的播放列表”,或者在使用语音时,它变得真正有用。此MCP允许LLM处理模糊意图→搜索→选择→控制循环,并返回明确的操作确认。它与语音界面配合良好,并可以连接到智能家庭自动化中的代理/工作流程。

示例:

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

该UI^[是Claude Desktop。

安装

  1. 克隆并安装
git clone https://github.com/overment/mcp.git
cd mcp/servers/spotify
bun install
  1. 准备环境
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
  1. 在Spotify仪表板中配置重定向URI

在您的Spotify开发者仪表板 → 应用 → 重定向URI,添加:

alice://oauth/callback - 如果您使用的是Alice应用。
http://127.0.0.1:3031/spotify/callback
  1. 运行服务器
bun dev
# MCP端点:        http://127.0.0.1:3030/mcp
# 授权服务器: http://127.0.0.1:3031
  1. 连接您的代理/UI

将您的桥接客户端指向MCP端点,例如http://127.0.0.1:3030/mcp(参见“客户端配置”部分中的Claude Desktop)。

模型看到的内容(服务器指令)

服务器向客户端提供简洁的描述,以便模型可以有效地使用它,而无需加载完整的模式。此描述总结了工具、关键规则和使用模式。

设计说明(有意使LLM友好):

  • 工具不一一映射Spotify的API。接口被简化和统一以减少混淆。
  • 尽可能地,操作是批量优先(如queries[]operations[]),以最小化工具调用并使意图明确。
  • 每个工具都返回人类友好的反馈,明确说明哪些成功,哪些失败,并提供下一步指导。
  • 对于播放器控制,服务器执行最佳努力背景验证(如检查设备、上下文和当前曲目),因为Spotify的API对于即时状态可能是模棱两可的。

MCP身份

  • 名称: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或将播放转移到列出的设备。

工具设计和惯例(LLM友好)

  • 适用情况下仅批量:queries: string[]用于搜索;operations[]用于控制。
  • 确定性的精简输出:固定形状,具有最少字段(如id、uri、name等)。
  • 每个工具返回一个可读的_msg摘要。控制在可能的情况下验证上下文/曲目和设备。
  • 错误处理:整个调用错误设置isError: true;批量结果包括每项{ ok, error? }和一个聚合摘要。

返回的消息

  • 每个工具在两个地方返回简洁的人类消息:
    • structuredContent._msg(或失败时的structuredContent.error
    • content: [{ type: "text", text: "<相同消息>" }, ... ]

这些旨在直接展示给用户,其中一个设计用于较旧的MCP客户端。

工具目录(名称、描述、输入、输出)

  1. search_catalog
  • 描述:搜索歌曲、艺术家、专辑和播放列表。输入:queries[]types[album|artist|playlist|track],可选marketlimit(1-50)offset(0-1000)include_external['audio']
  • 认证/注释:readOnlyHint=true,openWorldHint=true(应用令牌;无用户OAuth)。
  • 输入形状:
{
  queries: string[];
  types: ("album"|"artist"|"playlist"|"track")[];
  market?: string; // 两位字母
  limit?: number;  // 1..50(默认20)
  offset?: number; // 0..1000(默认0)
  include_external?: "audio";
}
  • 输出形状(SpotifySearchBatchOutput,精简):
{
  _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>;
  }>;
}
  1. player_status
  • 描述:读取当前播放器状态、设备、队列和当前曲目。使用此工具学习device_id,然后再进行控制。
  • 认证/注释:readOnlyHint=true,openWorldHint=true(需要用户OAuth)。
  • 输入形状:
{ include?: ("player"|"devices"|"queue"|"current_track")[] }
  • 输出形状(SpotifyStatusOutput):
{
  _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[] };
}
  1. spotify_control
  • 描述:控制Spotify播放:播放、暂停、下一曲/上一曲、快进、随机播放、重复、音量、转移和队列。批量接口;可选parallel=true。尽可能验证设备/上下文/曲目,并返回简洁的状态。
  • 认证/注释:readOnlyHint=false,openWorldHint=true(需要用户OAuth)。
  • 输入形状:
{
  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;
}
  • 输出形状(SpotifyControlBatchOutput):
{
  _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获取完整详情。
  1. spotify_playlist
  • 描述:管理当前用户的播放列表。
  • 动作:list_usergetitemscreateupdate_detailsadd_itemsremove_itemsreorder_items
  • 认证/注释:mutating动作时readOnlyHint=false;读取时为true;openWorldHint=true(需要用户OAuth)。
  • 输入形状:
// 列出当前用户的播放列表
{ 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 } }
  1. spotify_library
  • 描述:管理保存的歌曲(您的库)。
  • 动作:tracks_gettracks_addtracks_removetracks_contains
  • 认证/注释:读取时readOnlyHint=true;写入时为false;openWorldHint=true(需要用户OAuth)。
  • 输入形状:
// 列出保存的曲目
{ 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? } }
    • 使用曲目ID进行库操作;使用完整的曲目URI进行播放列表添加/删除。

HTTP端点

  • POST /mcp — JSON-RPC 2.0消息通过可流式传输HTTP。初始化会话并处理请求。
  • GET /mcp — 服务器到客户端的通知流,针对现有会话;需要Mcp-Session-Id头。
  • DELETE /mcp — 结束会话;需要Mcp-Session-Id头。
  • GET /health — 健康探测。