{"id":50853309,"url":"https://github.com/jenreh/sonos-py","last_synced_at":"2026-06-14T16:05:01.755Z","repository":{"id":358081873,"uuid":"1239789515","full_name":"jenreh/sonos-py","owner":"jenreh","description":"Local Sonos controller: CLI and MCP server, no cloud required","archived":false,"fork":false,"pushed_at":"2026-05-15T15:57:31.000Z","size":194,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-15T18:27:21.863Z","etag":null,"topics":["cli-app","mcp-server","python","sdk","sonos-controller"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jenreh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-15T12:49:57.000Z","updated_at":"2026-05-15T15:57:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jenreh/sonos-py","commit_stats":null,"previous_names":["jenreh/sonos-py"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/jenreh/sonos-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenreh%2Fsonos-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenreh%2Fsonos-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenreh%2Fsonos-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenreh%2Fsonos-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jenreh","download_url":"https://codeload.github.com/jenreh/sonos-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenreh%2Fsonos-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34326262,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-14T02:00:07.365Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli-app","mcp-server","python","sdk","sonos-controller"],"created_at":"2026-06-14T16:05:00.880Z","updated_at":"2026-06-14T16:05:01.747Z","avatar_url":"https://github.com/jenreh.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# sonos-py\n\n![Version](https://img.shields.io/badge/version-0.3.2-blue)\n[![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE.md)\n[![Python](https://img.shields.io/badge/python-3.14%2B-orange)](https://www.python.org)\n\n\u003e Local Sonos controller — CLI and MCP server. No cloud, no account, no internet required.\n\nControl your Sonos speakers directly over your LAN via a full-featured CLI or as an [MCP server](https://modelcontextprotocol.io) that exposes every capability as an AI tool.\n\n## Features\n\n- **Full CLI** — discover, status, volume, playback, groups, favorites, radio, Apple Music, queue, alarms, snapshots, sleep timer\n- **MCP server** — all features available as tools for LLM agents (stdio and streamable-HTTP transports)\n- **Favorites \u0026 playlists** — play Sonos favorites, radio stations, and Apple Music by name (fuzzy match)\n- **Radio Browser** — search and play any station from the [radio-browser.info](https://www.radio-browser.info) directory\n- **Apple Music** — catalog search, share-link playback, alias bookmarks\n- **Snapshots** — save and restore volume/source/group state\n- **Policy engine** — volume caps, URL allowlists, confirmation guards\n- **Zero cloud** — every command hits your speakers directly over UPnP/SoCo\n\n## Requirements\n\n- Python 3.14+\n- [uv](https://docs.astral.sh/uv/) (recommended) or pip\n- Sonos speakers on the same LAN (S1 or S2 firmware)\n\n## Installation\n\n```bash\n# from PyPI (once published)\npip install sonos-py\n\n# from source\ngit clone https://github.com/jenreh/sonos-py\ncd sonos-py\nuv sync\n```\n\nThe `sonos` command is available after install. Run `sonos discover` to verify your speakers are reachable.\n\n## Quick start\n\n```bash\n# Find all speakers on the network\nsonos discover\n\n# Show playback state for all rooms\nsonos status\n\n# Play a Sonos favorite (fuzzy name match)\nsonos favorites play Büro \"Lieblingstitel\"\n\n# Play a radio station by alias\nsonos radio play Büro 1LIVE\n\n# Adjust volume\nsonos volume up Büro --step 10\n\n# Pause / resume\nsonos playback pause Büro\nsonos playback play Büro\n```\n\n## CLI reference\n\n```text\nsonos [OPTIONS] COMMAND [ARGS]\n\nOptions:\n  --config-dir PATH   Override config directory\n  --json              Output JSON instead of rich tables\n  --dry-run           Preview action without executing\n  --log-level TEXT    Logging level  [default: WARNING]\n  --refresh           Force topology refresh before command\n```\n\n| Command group | Description |\n| --- | --- |\n| `discover` | Scan LAN for Sonos speakers |\n| `rooms` | List speakers and their network info |\n| `status [ROOM]` | Playback state for one room or all |\n| `volume get/set/up/down` | Volume control (room / group / all scopes) |\n| `mute / unmute` | Mute control |\n| `playback play/pause/stop/next/previous` | Transport controls |\n| `favorites list/play/refresh` | Sonos favorites |\n| `radio search/play/bind/aliases` | Internet radio via Radio Browser |\n| `apple auth/search/play/bind/aliases/enqueue` | Apple Music |\n| `groups list/join/ungroup/isolate` | Group management |\n| `queue list/clear/play` | Queue management |\n| `alarms list/enable/disable/update/set` | Alarm clock management |\n| `snapshot save/restore/list` | State snapshots |\n| `sleep [ROOM] [SECONDS]` | Sleep timer (omit seconds to clear) |\n| `config show` | Display current configuration |\n| `doctor` | Diagnose config, storage, and network |\n\n## MCP server\n\n```bash\n# stdio (default — use with Claude Desktop or any MCP host)\nsonos-mcp\n\n# streamable-HTTP\nsonos-mcp --transport streamable-http --port 8765\n```\n\nAdd to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"sonos\": {\n      \"command\": \"sonos-mcp\"\n    }\n  }\n}\n```\n\n### Available MCP tools\n\n| Tool | Description |\n| --- | --- |\n| `sonos_list_speakers` | List all speakers |\n| `sonos_list_groups` | List speaker groups |\n| `sonos_get_state` | Playback state (one or all) |\n| `sonos_set_volume` / `sonos_adjust_volume` | Volume control |\n| `sonos_set_mute` | Mute / unmute |\n| `sonos_transport` | play / pause / stop / next / previous |\n| `sonos_play_favorite` | Play a Sonos favorite by name |\n| `sonos_search_radio` / `sonos_play_radio` | Radio Browser search and playback |\n| `sonos_search_apple_music` / `sonos_play_apple_music` | Apple Music |\n| `sonos_group` / `sonos_ungroup` / `sonos_isolate` | Group management |\n| `sonos_queue` | List, clear, or play from queue |\n| `sonos_snapshot_save` / `sonos_snapshot_restore` | State snapshots |\n| `sonos_sleep_timer` | Set or clear sleep timer |\n| `sonos_list_alarms` | List household alarms |\n| `sonos_discover` | Force network rediscovery |\n\n### MCP resources\n\n| Resource URI | Content |\n| --- | --- |\n| `sonos://speakers` | All speaker metadata |\n| `sonos://groups` | Current group topology |\n| `sonos://state` | Live playback state |\n| `sonos://capabilities` | Enabled features and transports |\n| `sonos://config/policies` | Active policy limits |\n| `sonos://radio/aliases` | Saved radio aliases |\n| `sonos://apple-music/aliases` | Saved Apple Music aliases |\n\n## Configuration\n\nConfig lives at `~/.config/sonos-local/config.toml` (created automatically on first run). Override the directory with `SONSO_LOCAL_CONFIG_DIR`.\n\n```toml\n[network]\nhosts = []                        # static IPs — leave empty for auto-discovery\ndiscovery_timeout_seconds = 5\nrequest_timeout_seconds = 9.5\n\n[policies.volume]\nmax_room_volume = 70\nmax_group_volume = 60\nmax_all_volume = 40\n\n[policies.playback]\nallow_arbitrary_urls = false      # block arbitrary stream URLs\nblock_private_network_urls = true\n\n[policies.radio]\ndefault_countrycode = \"DE\"\nmin_bitrate = 64\npreferred_codecs = [\"MP3\", \"AAC\", \"AAC+\"]\n\n# Optional room aliases\n[rooms.buero]\nsonos_names = [\"Büro\"]\naliases = [\"büro\", \"buero\", \"office\"]\n\n# Optional radio aliases\n[radio.aliases.einslive]\nstationuuid = \"9606f727-0601-11e8-ae97-52543be04c81\"\npreferred_name = \"1LIVE\"\naliases = [\"einslive\", \"1live\"]\n```\n\n### Apple Music\n\nApple Music playback works in two modes:\n\n| Mode | How to set up |\n| --- | --- |\n| **Share links** (default) | No credentials needed — paste `music.apple.com` share links |\n| **Catalog search** | Requires a Developer Token and User Token — set `apple_music.developer.enabled = true` and configure keys |\n\n```bash\n# Check auth status\nsonos apple auth\n\n# Play via share link\nsonos apple play Büro --url \"https://music.apple.com/de/album/...\"\n\n# Search and play\nsonos apple search \"Olivia Rodrigo GUTS\"\nsonos apple play Büro \"GUTS\" --type album\n```\n\n\u003e [!NOTE]\n\u003e For catalog search and library access, add your Apple Developer team credentials under `[apple_music.developer]` in `config.toml`. The user token is read from the env var `SONSO_APPLE_MUSIC_USER_TOKEN` or from the system keychain.\n\n## Using as a Python library\n\n`SonsoLocalService` is the single facade used by the CLI and MCP server. You can use it directly in your own async code:\n\n```python\nimport asyncio\nfrom sonos.core.app import SonsoLocalService\nfrom sonos.core.models import Scope, TransportCommand\n\nasync def main() -\u003e None:\n    svc = SonsoLocalService()        # reads ~/.config/sonos-local/config.toml\n    await svc.startup()\n\n    try:\n        # discover speakers\n        topology = await svc.discover()\n        print(f\"Found {len(topology.speakers)} speaker(s)\")\n\n        # list current state\n        states = await svc.get_state()\n        for s in states:\n            print(f\"{s.name}: {s.playback_state}, vol={s.volume}\")\n\n        # volume\n        await svc.set_volume(\"Büro\", 20, Scope.ROOM)\n        await svc.adjust_volume(\"Büro\", -5, Scope.GROUP)\n\n        # transport\n        await svc.transport(\"Büro\", TransportCommand.PAUSE, Scope.GROUP)\n        await svc.transport(\"Büro\", TransportCommand.PLAY, Scope.GROUP)\n\n        # play a Sonos favorite (fuzzy name match)\n        result = await svc.play_favorite(\"Büro\", \"1LIVE\", Scope.GROUP, isolate=False)\n        print(result.ok, result.action)\n\n        # play a radio station by alias or search term\n        await svc.play_radio(\"Büro\", \"1LIVE\", Scope.GROUP, isolate=False)\n\n        # group management\n        await svc.group(coordinator=\"Büro\", members=[\"Schlafzimmer\"])\n        await svc.ungroup([\"Schlafzimmer\"])\n\n        # snapshot: save and restore\n        snap = await svc.save_snapshot([\"Büro\"], name=\"before-party\")\n        await svc.restore_snapshot(snap.snapshot_id)\n\n        # alarms\n        alarms = await svc.list_alarms()\n        for a in alarms:\n            print(f\"alarm {a.alarm_id}: {a.time} enabled={a.enabled}\")\n\n    finally:\n        await svc.shutdown()\n\nasyncio.run(main())\n```\n\nPass a custom config directory or a pre-built `SonosLocalConfig` object to the constructor:\n\n```python\nfrom pathlib import Path\nfrom sonos.core.app import SonsoLocalService\n\nsvc = SonsoLocalService(config_dir=Path(\"/etc/myapp/sonos\"))\n```\n\nAll methods raise `sonos.core.errors.SonosError` subclasses on failure — never raw SoCo or network exceptions:\n\n| Exception | Code | Meaning |\n| --- | --- | --- |\n| `TargetNotFoundError` | `target_not_found` | Room or favorite not found |\n| `NetworkError` | `network_error` | Speaker unreachable |\n| `PlaybackError` | `playback_error` | UPnP playback failure |\n| `InvalidInputError` | `invalid_input` | Bad argument (time format, etc.) |\n| `PolicyError` | `policy_error` | Volume cap or URL policy blocked |\n| `AmbiguousTargetError` | `ambiguous_target` | Name matches multiple speakers |\n\n## Development\n\n```bash\n# Set up environment\ntask init\n\n# Run tests\ntask test\n\n# Lint and format\ntask lint\ntask format\n\n# Type check\ntask typecheck\n```\n\nTests require no real hardware. Live integration tests (marked `sonos_live`) need speakers on the network:\n\n```bash\ntask test:live\n```\n\n## Architecture\n\n```text\nsonos/\n├── cli/          # Typer CLI — one file per command group\n├── core/\n│   ├── app.py    # SonsoLocalService — the single application facade\n│   ├── sonos/    # SoCo backend (async wrapper + discovery + favorites)\n│   ├── radio/    # Radio Browser client and resolver\n│   ├── apple_music/  # Apple Music client (catalog + share links)\n│   ├── config.py # Pydantic config model, TOML load/save\n│   └── policy.py # Volume/playback/URL policy enforcement\n├── mcp_server/   # FastMCP server — tools and resources\n└── storage/      # aiosqlite — snapshots, radio aliases, Apple Music aliases\n```\n\nThe `SonsoLocalService` is the single entry point used by both the CLI and the MCP server. All SoCo calls run in a thread pool via `asyncio.to_thread`; all domain exceptions are `SonosError` subclasses so callers never see raw library exceptions.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjenreh%2Fsonos-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjenreh%2Fsonos-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjenreh%2Fsonos-py/lists"}