About
MCP server exposing Roon audio system as AI assistant tools
README
Rust SDK and tools for Roon's proprietary protocols (SOOD discovery + MOO RPC).
roon-api— Standalone SDK. Any Rust program can add it as a dependency to discover, connect to, and control a Roon Core.roon-cli— Command-line tool (roon) for controlling Roon from a terminal.roon-mcp— MCP server exposing Roon as tools for AI assistants (Claude Code, etc.).roon-hub— MQTT bridge binary (not published to crates.io).
Install the CLI
From crates.io
cargo install roon-cli
From GitHub Releases (pre-built binaries)
# roon-cli (the `roon` command)
curl -LsSf https://github.com/shin1ohno/roon-rs/releases/latest/download/roon-cli-installer.sh | sh
# roon-mcp (MCP server)
curl -LsSf https://github.com/shin1ohno/roon-rs/releases/latest/download/roon-mcp-installer.sh | sh
# Windows (PowerShell)
powershell -c "irm https://github.com/shin1ohno/roon-rs/releases/latest/download/roon-cli-installer.ps1 | iex"
Supported targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-apple-darwin, aarch64-apple-darwin, x86_64-pc-windows-msvc.
Install the MCP server
cargo install roon-mcp
# or grab a pre-built binary from GitHub Releases (see above).
Run roon-mcp --transport stdio (default) or --transport sse --http-port 8080 for SSE/HTTP. See crates/roon-mcp/ for details.
Quick Start
# 1. Discover Roon Cores on your network and set the default server.
# Approve "roon-rs CLI" in Roon Settings > Extensions when prompted.
roon discover
# 2. Pick a default zone.
roon zone
# 3. (Optional) Pick a default output. Used by volume/mute when --output is omitted.
# If skipped, volume/mute fall back to the default zone's output when the zone has only one.
roon output
# 4. Play.
roon play # resume playback
roon play -A "Miles Davis" # search artist and play
roon play -a "Kind of Blue" # search album and play
roon play -A "Miles Davis" -s # shuffle all tracks from an artist
roon pause / stop / next / previous
roon volume 30 # uses default output (or default zone's only output)
roon mute on # same fallback chain
# 5. Inspect.
roon status # current zone's now playing
roon zones --json # all zones as JSON
Full command list: roon --help.
Machine-readable integration (watch / browse / search / play-item)
These four commands emit JSON and are designed to be driven by another program (for example, a Neovim plugin or a TUI). The non-trivial idea is that browse/search state lives on the Roon Core and is keyed by a --session string you choose. Pass the same session key to successive calls to navigate a cursor; use different keys for independent cursors (telescope-style incremental search vs. neo-tree-style expand, for example).
roon watch — streaming zone/output changes
Emits one JSON object per line (NDJSON). Default output:
roon watch # all events, seek throttled to 1 Hz per zone
roon watch --no-initial # skip the one-shot initial snapshot
roon watch --seek-hz 0 # disable seek throttle (every tick)
Schema ("schema": 1):
event |
fields (besides schema + ts) |
meaning |
|---|---|---|
initial |
zones: [Zone], outputs: [Output] |
one-shot on start |
zone_added |
zone: Zone |
|
zone_changed |
zone: Zone |
|
zone_removed |
zone_id: String |
|
zone_seeked |
zone_id, seek_position, queue_time_remaining |
throttled per --seek-hz |
output_added |
output: Output |
|
output_changed |
output: Output |
|
output_removed |
output_id: String |
Ctrl-C and a broken pipe both exit 0. A stdout write error (other than broken pipe) exits 2.
roon browse — one-shot browse+load pair
roon browse --session nvim-neotree --hierarchy albums --pop-all
roon browse --session nvim-neotree --item-key <key> # drill
roon browse --session nvim-neotree --offset 100 --count 50 # paginate
Each invocation does browse followed by load against the given session and prints the loaded list:
{
"session": "nvim-neotree",
"list": { "title": "Albums", "level": 1, "count": 1287, "subtitle": null, "hint": null, "image_key": null },
"items": [ { "item_key": "4:28", "title": "In Rainbows", "subtitle": "Radiohead",
"image_key": "...", "hint": "list", "input_prompt": null } ],
"offset": 0,
"total": 1287
}
Roon requires a hierarchy on every browse/load call. The first call that establishes a hierarchy caches it in ~/.config/roon-rs/sessions/<session>.toml, so later drills on the same session do not need --hierarchy.
roon search — thin wrapper
roon search --session nvim-telescope --input "radiohead"
Uses the search hierarchy with pop_all=true so each call returns fresh results. Output schema is identical to browse. The Roon search hierarchy returns header items (hint: "header") mixed with result items; filter them client-side if you want a flat list.
roon play-item — play / queue / start-radio
roon play-item --session nvim-neotree --item-key <key> --zone Qutest
roon play-item --session s --item-key <key> --action queue
<key> is an item_key from a preceding browse/search call in the same session — typically an action_list-hint item such as "Play Album". play-item drills into it to get the action list, then invokes Play Now / Queue / Start Radio per --action (default auto prefers Play Now). The Roon Core needs a zone target, so pass --zone / --zone-id or rely on the default roon zone. Output on success:
{"ok":true,"played":{"title":"Play Now","item_key":"30:0"}}
On no matching action, exit code 3 and:
{"error":"no matching action","available":["Go Back"]}
Session semantics
- Pick your own session keys. One cursor per key; use different keys when you want independent cursors.
- Item keys are only valid within the session that produced them. Pass the same
--sessionto the follow-upbrowse/play-itemcall. - The session's hierarchy is cached on disk; delete
~/.config/roon-rs/sessions/<key>.tomlto reset.
Use the SDK
[dependencies]
roon-api = "0.1"
tokio = { version = "1", features = ["full"] }
use roon_api::{RoonClientBuilder, RoonEvent, ControlAction, FileStateStore};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = RoonClientBuilder::new(
"com.example.myapp",
"My App",
"0.1.0",
"My Name",
"[email protected]",
)
.token_store(FileStateStore::new("tokens.json"))
.require_transport()
.build()?;
let core = client.connect("192.168.1.20", 9330).await?;
let transport = core.transport();
let zones = transport.get_zones().await?;
for z in &zones {
println!("{}: {:?}", z.display_name, z.state);
}
Ok(())
}
Build from source
cargo build --workspace
cargo test --workspace
cargo clippy --workspace --tests
Protocol Documentation
Operational Notes
Latency boundary
- A Roon MOO RPC round-trip depends on Roon Core health. On a LAN-local Core,
Transport::change_volumebuffered-send through ACK is typically <5 ms; when the Core is mid-task (library scan, zone group re-shuffle, network-share reconnect) it can spike past 200 ms. - SOOD discovery is multicast-based and reconverges after network changes (DHCP lease renewal, switch reboot). The discovery window is measured in seconds, not milliseconds — this is why
roon-apicaches the lasthost:portin its state store. - Roon extension pairing is per-
extension_id, per-Core. Reconnect after a Core restart goes through MOO re-handshake, which adds 50–500 ms on top of the TCP handshake. Consumers that care about cold-start latency should keep theFileStateStoretoken file on disk so pairing survives restarts.
Compatibility policy
roon-apiis the public Rust SDK.RoonClient,RoonEvent,ControlAction,Zone,Output, and theTransporttrait form the API surface.- Today's semver rules:
- MINOR — new fields on
Zone/Output/ event payloads (Roon's own protocol evolves this way; serde-side struct additions are additive). - MAJOR — new
RoonEventorControlActionvariant (pattern matches without_ =>break at compile time). Add_ => {}in downstream match arms if forward-compat matters now;#[non_exhaustive]can be revisited later. - MAJOR — MOO method signature change, builder-required-field addition.
- MINOR — new fields on
roon-cli,roon-mcp, androon-hubare consumers ofroon-api, not part of the SDK. They ship on independent version lines. Aroon-apibreaking change does not force a consumer bump — consumers are pinned to aroon-apiminor in their ownCargo.tomland roll forward at their own pace.- The
watch/browse/search/play-itemNDJSON output ("schema": 1) is a separate contract from the Rust SDK, aimed at non-Rust consumers (TUI, Neovim plugin). That contract evolves with its ownschemanumber — new fields are additive within a schema version; incompatible changes bumpschema.
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT License (LICENSE-MIT)
at your option.
Disclaimer
This project is not affiliated with or endorsed by Roon Labs. "Roon" is a trademark of Roon Labs LLC.
Installing Roon
This server has no published package — it is built from source. Open the repository and follow its README.
▸ github.com/shin1ohno/roon-rsFAQ
Is Roon MCP free?
Yes, Roon MCP is free — one-click install via Unyly at no cost.
Does Roon need an API key?
No, Roon runs without API keys or environment variables.
Is Roon hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Roon in Claude Desktop, Claude Code or Cursor?
Open Roon on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
ARA
Generate images, video and audio from any AI agent — one connector.
by ARAOmni Video
An MCP server that transforms LLM-enabled IDEs into professional video editors by pre-processing footage into text proxies, generating motion graphics via HTML/
by buildwithtazaYouTube
Transcripts, channel stats, search
by YouTubeEverArt
AI image generation using various models.
by modelcontextprotocolgpu-bridge/mcp-server
Unified GPU inference API with 30 AI services (LLM, image gen, video, TTS, whisper, embeddings, reranking, OCR) as MCP tools. Pay-per-use via x402 USDC or API k
by gpu-bridgehamflx/imagen3-mcp
A powerful image generation tool using Google's Imagen 3.0 API through MCP. Generate high-quality images from text prompts with advanced photography, artistic,
by hamflxmerterbak/Grok-MCP
MCP server for xAI's [Grok API](https://docs.x.ai/docs/overview) with agentic tool calling, image generation, vision, and file support.
by merterbakSureScaleAI/openai-gpt-image-mcp
OpenAI GPT image generation/editing MCP server.
by SureScaleAIYangLiangwei/PersonalizationMCP
Comprehensive personal data aggregation MCP server with Steam, YouTube, Bilibili, Spotify, Reddit and other platforms integrations. Features OAuth2 authenticati
by YangLiangweiAceDataCloud/MCPFlux
Flux AI image generation and editing (Black Forest Labs) via Ace Data Cloud API.
by AceDataCloudCompare Roon with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All media MCPs
