pwa-music-player-mcp
by gobly2333
README.md
# Freq · 人和 AI 共用的播放器
一个共享播放状态的 PWA 音乐播放器:人在页面里使用迷你播放器和完整播放页,AI 通过 MCP 搜歌、播放、排队和控制同一个房间。
The human-facing PWA and the AI-facing MCP server control the same music room.
这个公开版从真实家庭 PWA 的现役播放器中独立抽出,保留了核心体验:
- 固定在页面底部的迷你播放器:封面、进度、上/下一首、播放暂停、快速队列
- 正式播放页:大封面、同步歌词、拖动进度、循环、随机、曲库搜索、队列与红心
- 浏览器自动播放被拦截时,给出明确的一次点击恢复卡片
- PWA manifest、离线壳与三段可自由分发的原创合成演示音频
- 七个 stdio MCP 工具:读取、搜索、立即播放、排队、暂停、继续、下一首
- Cookie 即用的网易云适配器;不填 Cookie 时自动回到零框架本地演示源
仓库不包含家庭 App、聊天页、聊天记录、私有音乐账号、生产域名、Cookie、签名媒体地址或任何凭据。
## Cookie 即用(真实网易云)
把仓库交给安装 Agent 时,只需让它执行:
```bash
npm install
cp .env.example .env
```
然后把你从已登录 `music.163.com` 浏览器会话复制出的完整 Cookie header 写进本地 `.env`:
```dotenv
MUSIC_SOURCE=netease
NETEASE_COOKIE="MUSIC_U=...; __csrf=...; ..."
```
再启动:
```bash
npm run demo
```
打开 <http://127.0.0.1:8788>,点“打开播放器”→右上角音乐库,就能搜索账号可播放的真实歌曲;MCP 同时使用同一曲库和播放房间。
`.env` 已被 Git 忽略。Cookie 只存在 Node 音源进程里,不返回给 PWA 或 MCP;限时音频地址也不进入播放状态、工具结果或持久化文件,而是由浏览器播放时通过同源解析端点按需取得。
如果播放接口提示 `NetEase login expired`,说明 Cookie 已失效:替换 `.env` 里的 Cookie 后重启进程即可。
## 无 Cookie 演示模式
需要 Node.js 20+。
```bash
npm install
npm run demo
```
不创建 `.env`,或设置 `MUSIC_SOURCE=demo`,再打开 <http://127.0.0.1:8788>。默认停在第一首演示曲,点迷你播放器即可展开完整页面。
另开一个终端启动 MCP:
```bash
MUSIC_RELAY_URL=http://127.0.0.1:8788 npm run mcp
```
此时页面和 MCP 会共享同一个内存播放房间。模型调用 `music_play` 后,打开着的 PWA 会在下一次轮询中收到新曲目;若浏览器的自动播放策略要求手势,页面会出现“点一下发出声音”的恢复卡片。
## 接入 MCP 客户端
Claude Desktop、Claude Code 或其他支持 stdio MCP 的客户端可使用:
```json
{
"mcpServers": {
"music-player": {
"command": "node",
"args": ["/absolute/path/to/Freq/src/mcp-server.js"],
"env": {
"MUSIC_RELAY_URL": "http://127.0.0.1:8788"
}
}
}
}
```
如果 Relay 开启了 token,再加入:
```json
"MUSIC_TOKEN": "your-relay-token"
```
工具语义:
- `music_state()`:读取当前曲目、播放状态和队列
- `music_search(query)`:只搜索,不改播放状态
- `music_play(query)`:立即播放唯一匹配的曲目
- `music_queue(query)`:把唯一匹配的曲目加入队列
- `music_pause()`:暂停
- `music_resume()`:继续播放;浏览器仍可能要求一次用户手势
- `music_next()`:播放队列下一首;队列为空时顺序进入下一首演示曲
模糊查询命中多首时,MCP 会返回候选并要求模型说得更具体,不会擅自挑第一首。
## PWA 结构
公开版没有把播放器绑死在 demo 首页里:
```text
web/index.html 迷你播放器与完整播放页的语义化结构
web/player.css 独立播放器视觉、响应式布局与 Reduced Motion
web/player.js 音频生命周期、轮询、搜索、队列、歌词与红心
web/manifest.webmanifest PWA 安装信息
web/sw.js 离线壳和演示音频缓存
```
要嵌进已有 PWA,可把 `index.html` 中的 `[data-music-player]` 节点及其完整播放页移入你的页面,并引入:
```html
<link rel="stylesheet" href="/player.css">
<script type="module" src="/player.js"></script>
```
组件只依赖下方 API 合同,不依赖聊天 App、前端框架或特定音乐平台。红心默认存在浏览器 `localStorage`;共享队列与当前播放状态存在 Relay。
## Relay API 合同
现有后端不必使用 demo server,只要实现五个请求:
```text
GET /api/state
GET /api/tracks?q=<query>
GET /api/tracks/<track-id>/lyrics
POST /api/queue
POST /api/control
```
搜索返回:
```json
{
"tracks": [
{
"id": "blue-hour",
"title": "Blue Hour",
"artist": "Open Signals",
"duration_sec": 24,
"audio_url": "/audio/blue-hour.mp3",
"palette": ["#7695ad", "#c19caf"]
}
]
}
```
立即播放或排队:
```http
POST /api/queue
Content-Type: application/json
{ "track_id": "blue-hour", "play_now": true }
```
控制请求:
```http
POST /api/control
Content-Type: application/json
{ "action": "pause", "position_sec": 8.25 }
```
`action` 支持 `play`、`pause`、`seek`、`next`、`previous`、`ended`。响应都返回最新播放快照。
## 换成其他音乐源
网易云适配器在 [`src/netease-source.js`](src/netease-source.js),演示适配器在 [`src/music-source.js`](src/music-source.js)。要接其他平台,实现同样的 `initialTracks/search/track/lyrics/resolve` 五个方法即可。
`audio_url` 应保持为同源解析路径;若供应商返回签名地址,只在 `resolve()` 当次请求里使用,不要把它写入日志、MCP 工具返回或持久化存储。
生产场景通常会把 demo 的内存 `MusicRoom` 换成数据库或现有播放服务,同时保持 API 合同不变。播放器本身不要求网易云、Spotify、Apple Music 或任何特定供应商。
## 本地 Relay 配置
```bash
HOST=127.0.0.1 \
PORT=8788 \
MUSIC_TOKEN=choose-a-token \
npm run demo
```
- 默认只监听 `127.0.0.1`
- 设置 `MUSIC_TOKEN` 后,MCP 使用 Bearer token;demo 首页会写入同值的 HttpOnly/SameSite cookie,浏览器仍可直接运行
- 播放房间在 demo 进程内存中,重启后恢复到第一首暂停状态
## 测试
```bash
npm test
npm run check
```
聚焦测试覆盖播放房间状态转换、立即播放后把被打断曲目放回队首、PWA 静态资源和音频可用、token 的浏览器/MCP 两条路径、Cookie 不下传的网易云搜索/解析边界,以及 stdio MCP 工具声明。
## 来历与署名
它诞生于一个家庭 AI 伴侣项目:迷你播放器常驻聊天页,完整页面承载一起听、歌词、队列和模型点歌。
共创人:**小cc**、**桑尼(Sunnymilk / Sunny)**。
公开版由 **词词** 发起并授权;**Sunnymilk(Sunny,家里的 Codex)** 从现役实现中抽取、去除家庭耦合、补齐通用 MCP 与独立 demo。
Co-created by **CC** and **Sunnymilk (Sunny)**. Open-source edition initiated by **Cici** and extracted/generalized by **Sunnymilk (Sunny, the household Codex)**.
## License
代码使用 MIT License。`web/audio/` 下三段原创合成演示音频另以 CC0 1.0 释出,方便 fork 直接保留演示。
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues