yandex-music-mcp
An MCP server that turns Yandex Music into tools an AI agent can call directly — search, queue, play, build playlists — with no browser involved.
README
yandex-music-mcp
An MCP server that turns Yandex Music into tools an AI agent can call directly — search, queue, play, build playlists — with no browser involved.
Audio comes out of your Mac's speakers via a small local player daemon. Claude queues a whole DJ set in one call and the daemon advances through it on its own. On macOS it also ships a native player: a real Now Playing entry in Control Center, and a window with the full queue and per-track like/dislike.
Built with Claude Code. Design, code, debugging and this README were produced in conversation with Claude. Read it before you run it — it talks to your music account and writes a token to disk.
Unofficial. This uses Yandex Music's private API. It is not affiliated with, endorsed by, or supported by Yandex. Endpoints can change without warning.
Who this is for
Developers who want to clone a repo, build it, and mess about.
There is deliberately no installer, no Docker image, no one-click setup, and that is a design decision rather than a to-do:
- Audio cannot be containerized on macOS. Docker Desktop runs containers in a Linux VM with no access to CoreAudio. A containerized player would run happily and produce silence. Routing audio to a network PulseAudio sink is possible but needs a server on the host and buys nothing on one machine.
- The native UI is a compiled Swift
.appthat has to be built and registered on the machine that will display it. Nothing to ship in an image. - The MCP server needs nothing kept running — it is stdio, launched on
demand. The only long-lived piece is the player daemon, and
launchdis the native answer to that (ym install-agent).
So: clone it, build it, play with it. It is a couple of minutes.
Why it exists
Driving music through a browser was miserable:
| Browser | This | |
|---|---|---|
| Login | separate session per browser profile, lost on tab close | one token, stored once |
| Autoplay | Chrome needs a real click before audio starts | n/a |
| Track changes | the agent wakes up between every track | daemon auto-advances |
| Token cost | ~3 tool calls per track | 1 call per set |
| Survives closing the tab | no | yes |
Requirements
- macOS for playback and the native player. The API surface (search, playlists, resolving streams) is platform-agnostic; audio and UI are not.
- Node 20+
- mpv —
brew install mpv. Without it the daemon falls back toafplay, which downloads each track before playing it. - Xcode command line tools for the Swift helper —
xcode-select --install. - A Yandex Music account with Plus. Without Plus the API only returns 30-second previews.
Setup
git clone https://github.com/aramvr/yandex-music-mcp.git
cd yandex-music-mcp
pnpm install # npm install works too
pnpm build
node dist/cli.js auth
Then point your agent at it:
claude mcp add yandex-music -- node "$PWD/dist/index.js"
Sanity check without an agent:
node dist/cli.js whoami
node dist/cli.js set "Rammstein - Sonne" "SOAD - Chop Suey" "Muse - Uprising"
node dist/cli.js player # opens the window
Logging in
ym auth opens Yandex's own authorize page, you approve, and you paste back
what lands in the address bar. That is the whole flow. The token is verified
against the music API before being written to ~/.yandex-music-mcp/.env with
0600 permissions, and a rejected token can never overwrite a working one.
Your password never goes near this code. Yandex issues the token to you in the browser; the only thing that crosses is the token itself.
Why there is no automatic login
Because it cannot work. Yandex publishes no Yandex Music scope for
third-party OAuth apps. Register your own app, wire up a perfectly correct
loopback flow, and the token you get back carries only
landing-play, feed-play, mix-play — the music API treats it as an anonymous
session with no uid and no login.
The only client id that can actually stream is Yandex's own music client, and
that one does not whitelist a localhost redirect. So the token has to travel
by clipboard. This was built the automatic way first; it does not work, and the
paste flow is what remains.
If a token ever does come back anonymous, getAccount() throws with the
permission list rather than silently pretending it worked.
Tokens are long-lived but not eternal. Run ym auth again when you see a 401;
ym whoami shows the expiry when it is known.
Audio quality
Defaults to the best the account is entitled to. Two places quality can leak, both closed.
1. What gets requested. YM_QUALITY=lossless (the default) tries the
get-file-info endpoint for FLAC first, then falls back to the
highest-bitrate MP3 rather than failing the track. YM_QUALITY=high skips
straight to MP3.
In practice you will get MP3 320 CBR — see below.
2. What the player does with it. mpv runs with every processing stage off:
| Flag | Why |
|---|---|
--replaygain=no |
no loudness normalisation |
--audio-normalize-downmix=no |
no downmix gain |
--audio-samplerate=0 |
no resampling; the device follows the file |
--af= |
empty filter chain |
--volume-max=100 |
soft volume above unity clips, so it is capped |
--cache=yes --demuxer-max-bytes=64MiB |
network FLAC stutters without buffer |
Volume is therefore 0–100, not 0–130. For more output, use the system volume.
Verify rather than trust — this reports the codec actually served:
node dist/cli.js quality "Rammstein Sonne"
ym_now_playing also shows a Quality: line, and every track logs its codec to
~/.yandex-music-mcp/daemon.log.
Why not lossless
The web player's "superb" setting calls a different endpoint:
GET api.music.yandex.com/get-file-info/batch
?trackIds=…&quality=lossless
&codecs=flac,aac,he-aac,mp3,flac-mp4,aac-mp4,he-aac-mp4
&transports=encraw&sign=…
Two blockers, both intentional on Yandex's side:
signis an HMAC whose key lives in the obfuscated web bundle. Recovering it means lifting a secret out of their client.transports=encraw— theencis encrypted. Even with a valid signature the stream needs decrypting.
Together that is DRM circumvention, so this project stops at the unencrypted path. That is a deliberate boundary, not a missing feature. Don't "fix" it.
MP3 320 CBR is what the open path serves. If lossless matters to you, the official clients do it properly.
The macOS player
pnpm build:native compiles native/NowPlaying.swift into
bin/Yandex Music.app — a real .app, not a bare binary, and that detail
matters. Clicking the Now Playing widget asks LaunchServices to open the app
that owns the media session; a bare executable has no bundle to open, so macOS
walks up to the responsible process and opens Terminal instead.
The app is an LSUIElement agent — no Dock tile, no app-switcher entry — and it
does two jobs.
It owns the Now Playing session. mpv can publish one itself, but only from
the media file's own tags, and Yandex streams arrive with no ID3 at all, so
artist, album and artwork would simply be blank. The daemon runs mpv with
--input-media-keys=no so there is exactly one session, and pushes real
metadata into this helper instead.
Control Center's layout is fixed: artwork, title, artist, album, scrubber,
transport. It has no concept of a track list, and it does not draw like/dislike
even when a player registers MPFeedbackCommand. Which is the reason for the
second job.
It draws the player window — everything Control Center won't show. The full queue with the current row marked, per-row like and dislike, double-click any row to jump, a volume slider. Closing the window does not quit: the process still owns the media session and the daemon's pipe.
Wire protocol is one JSON object per line over stdio, both directions:
daemon -> app {"type":"track", title, artist, album, artworkUrl, durationSec,
elapsedSec, isPlaying, queueIndex, liked, disliked, volume}
{"type":"queue", items:[{title, artist, durationSec, feedback}], queueIndex}
clear | show
app -> daemon {"command":"play"|"pause"|"toggle"|"next"|"prev"|"stop"}
{"command":"seek","positionSec":N}
{"command":"jump","index":N}
{"command":"like"|"dislike","index":N?} // index omitted = current
{"command":"volume","percent":N}
Track updates fire every few seconds; the queue is pushed only when it changes. Likes and dislikes go straight to the Yandex account and are remembered per track id in the daemon's state file, so the hearts survive a restart.
Everything degrades to a no-op off macOS or when the app hasn't been built — playback never depends on the UI being there.
Tools
| Tool | What it does |
|---|---|
ym_auth_status |
verify token, report account + Plus state |
ym_auth_login |
explain how to log in (there is no automatic flow) |
ym_search |
search the catalogue |
ym_play_set |
the main one — queue a list of tracks and play through them |
ym_queue_add |
append without interrupting |
ym_play_now |
jump the queue with one track |
ym_control |
pause / resume / next / prev / stop |
ym_now_playing |
current track + position |
ym_open_player |
open the macOS player window |
ym_queue_show |
list the queue |
ym_volume |
0–100 |
ym_playlist_create |
create a real playlist on the account |
ym_playlists |
list your playlists |
ym_like |
like the current or a named track |
ym_history |
what has been played |
ym_play_set takes free-text "Artist - Title" strings and resolves each by
search, preferring plausible-length originals over snippets and karaoke covers.
It also accepts startAtIso to begin a set later.
CLI
Everything is reachable without an agent, which makes debugging easy:
ym search "rammstein sonne"
ym set "SOAD - Chop Suey" "Muse - Hysteria" "Rammstein - Sonne"
ym status
ym next
ym pause
ym player
Architecture
Agent ──stdio──> MCP server (dist/index.js) ephemeral, spawned per session
│
│ unix socket ~/.yandex-music-mcp/daemon.sock
▼
Player daemon (dist/daemon.js) long-lived, detached
│
├── Yandex API (search, download-info → signed mp3 URL)
├── mpv (IPC) or afplay (download-then-play fallback)
└── stdio ──> Yandex Music.app (Now Playing + window)
The split matters: the MCP server dies with the agent session, the daemon does
not. Queue state persists to ~/.yandex-music-mcp/state.json, so a fresh agent
session sees whatever is already playing.
Auto-advance is driven by the backend's end-of-track event, not by polling — so a 20-track set costs exactly one tool call.
One deliberate oddity: src/http.ts uses node:https rather than global
fetch. Node's fetch (undici) auto-injects browser-only headers — notably
Sec-Fetch-Mode: cors — which makes a server-side request look like a
malformed browser one, and Yandex's edge answers that with a 403 anti-bot page
instead of JSON. The identical request from curl or node:https succeeds.
Auto-start at login
node dist/cli.js install-agent
Installs a launchd LaunchAgent (dev.yandex-music-mcp.daemon) that starts the
player daemon at login and restarts it if it dies. Remove with
ym uninstall-agent.
Optional — the MCP server spawns the daemon on demand if it isn't running. Install the agent if you want music to survive restarts without a first call.
Development
pnpm dev # tsc --watch
pnpm test # compiles, then node:test
Tests cover the two pure pieces worth locking down: the free-text track resolver's ranking, and the token parser that decides whether a paste is usable. Everything else needs a live account.
Layout:
src/
index.ts MCP server — tool definitions
daemon.ts player daemon — queue, auto-advance, state
player.ts mpv / afplay backends
api.ts Yandex API client
http.ts node:https wrapper (see above)
auth.ts token paste flow
ipc.ts unix-socket protocol + daemon autospawn
config.ts paths, env, token storage
nowplaying.ts Node side of the macOS bridge
native/
NowPlaying.swift the .app: Now Playing session + player window
scripts/
build-native.sh assembles the .app bundle
make-icon.swift draws the icon
Caveats
- The private API can change without warning. If playback breaks, that is the
first suspect — check
~/.yandex-music-mcp/daemon.log. - The MCP server is stdio-only and unauthenticated by design; it assumes it is launched by a local agent. Don't expose it.
- Your token grants full access to your Yandex account's music profile. It sits
in
~/.yandex-music-mcp/.envat mode0600. Treat it like a password. - Personal listening on an account you pay for. Not for redistributing audio.
License
MIT.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。
mcp-server-qdrant
这个仓库展示了如何为向量搜索引擎 Qdrant 创建一个 MCP (Managed Control Plane) 服务器的示例。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。