yandex-music-mcp

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.

Category
Visit Server

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 .app that 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 launchd is 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 to afplay, 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:

  1. sign is an HMAC whose key lives in the obfuscated web bundle. Recovering it means lifting a secret out of their client.
  2. transports=encraw — the enc is 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/.env at mode 0600. Treat it like a password.
  • Personal listening on an account you pay for. Not for redistributing audio.

License

MIT.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured