mcp server
opensonos
Sonos core in Swift: any speaker as a soundbar satellite (incl. fronts), every room as an AirPlay target (S1 too), plus an MCP server.
Set up
Runs on your computer. Pick your agent and operating system.
This project does not publish an install method we can turn into a config. Follow the README instead.
README
synced Oct 29, 2026OpenSonos
The core of a Sonos controller, in Swift, with no app attached. Two things it does that the official app can't:
- Any speaker as a satellite. Bond any Sonos speaker to a soundbar as sub, rear or front left/right (for example two Play:1s as fronts). The soundbar can be an old Playbar.
- Every room as an AirPlay target, S1 included. Each room shows up on its own in the AirPlay menu of an iPhone or Mac, including S1-only and gen-1 players that never had AirPlay. One sender can play in rooms on different systems (S1 + S2) at the same time.
It also ships an MCP server, so an AI assistant (Claude Code, Claude Desktop, or any MCP client) can list rooms, plan a satellite layout and run the AirPlay bridge.
There are no apps in this repo. It is the logic, as a SwiftPM package you can drop into your own project.
| Product | What it is |
|---|---|
SonosKit | Library: discovery, topology, bonding planner and runner, transport/volume/EQ, backups, AirPlay receiver and bridge |
opensonos-mcp | MCP server on stdio |
opensonos | Command line: discover, airplay |
Requires macOS 15 / iOS 18 and Swift 6. Everything talks to the speakers directly on the local network (UPnP on port 1400). No cloud, no account.
Quick start
swift build -c release
.build/release/opensonos discover # list systems, rooms, devices, roles
.build/release/opensonos airplay # every room appears in the AirPlay menu
As a library:
.package(url: "https://github.com/omerkunul/opensonos.git", branch: "main")
// target dependency: .product(name: "SonosKit", package: "opensonos")
import SonosKit
let client = SOAPClient()
let systems = await Discovery.systems(await Discovery.discover(client: client))
let hh = try await client.loadHousehold(systems[0].seed)
// Two Play:1s as front left/right of the Playbar, Sub kept:
let bar = hh.rooms.values.first { $0.kind == .homeTheater }!.primary
let steps = Planner.layout(hh, bar: bar, assignments: [
subUUID: .sub, leftUUID: .frontLeft, rightUUID: .frontRight,
])
for step in steps { print(step.describe(hh)) } // read-only so far
try BackupStore().save(hh, label: "before fronts") // always back up first
_ = try await Bonder(client: client).run(steps, on: hh)
// AirPlay for every room, as long as the process runs:
let hub = AirPlayHub()
await hub.refresh()
hub.keepFresh()
Docs
- Architecture: how the pieces fit
- Adding satellites: channel tokens, the planner, the bonding quirks
- AirPlay to Sonos: RAOP receiver, decryption, ALAC, re-streaming, gen-1 MP3
- MCP server: setup and tools
Safety
Bonding rebuilds a home theater. Speakers drop out for a few seconds while they rebond,
and the bar's surround settings get reset; the bonder restores them afterwards. Save a
BackupStore snapshot first so Planner.restore can put things back. The MCP tools do
that for you, refuse to bond without confirm: true, and cap volume at
OPENSONOS_MAX_VOLUME.
License
MIT, see LICENSE. Sources/CShine is the shine MP3 encoder under LGPL v2; see
NOTICE.
Not affiliated with or endorsed by Sonos, Inc. or Apple Inc. "Sonos" and "AirPlay" are trademarks of their owners and are used here only to describe compatibility.