Skip to content

Repository files navigation

kef-mcp

An MCP server for controlling KEF wireless speakers (LSX II / LS50 Wireless II / LS60) and playing Spotify on them. Exposes tools any MCP client can call to power the speaker on/off, switch inputs, set volume, control transport, and start Spotify playback — with one combined tool that does the whole "wake → switch to wifi → play" flow.

Built to be driven by Poke (see poke/), but it's a standard MCP server (streamable HTTP) usable by any MCP client.

MCP client ──► kef-mcp ──┬─ HTTP ─► KEF speaker (LAN)
                         └─ HTTPS ► Spotify Web API

Tools

Tool What it does
play_on_kef(query, volume?) Main one. Wake KEF → source=wifi → find on Spotify → play on KEF.
kef_power_on / kef_standby Power on / off.
kef_set_source(source) wifi, bluetooth, tv, optic, coaxial, analog.
kef_set_volume(level) / kef_get_volume Hardware volume 0-100.
kef_play_pause / kef_next / kef_previous Transport control.
kef_status Power, source, volume, now playing.
spotify_pause / spotify_skip Spotify-side control.
spotify_set_volume(level) Volume on the active Spotify device.
spotify_now_playing Track / artist / device.
cast_url(url, volume?) Cast a direct audio/radio stream URL (non-Spotify).
play_radio(query_or_url, volume?) Internet radio via a direct stream URL.
play_from_youtube(query, volume?) Play YouTube/SoundCloud audio (search or URL).
cast_stop / cast_pause / cast_resume Cast transport control.
cast_status What's playing via Cast.

Playing non-Spotify audio (Google Cast)

The KEF has Chromecast built-in. The cast_* / play_radio / play_from_youtube tools cast an HTTP(S) audio URL to the speaker (it pulls the stream itself and auto-wakes to wifi, like Spotify Connect). play_from_youtube uses yt-dlp to turn a query or page URL into a stream URL first.

  • Same-LAN requirement: the server reaches the speaker's Cast channel on TCP 8009 by IP (no mDNS). In Docker this works over the same bridge the KEF HTTP API already uses — but confirm 8009 is reachable from the container.
  • YouTube can break: site changes occasionally break extraction. Fix = bump yt-dlp in requirements.txt and rebuild the image.
  • UPnP/DLNA does NOT work on this hardware — the speaker firmware blocks SetAVTransportURI, so generic DLNA casting fails. Google Cast is the supported non-Spotify path.

Prerequisites

  • Spotify Premium (the Web API blocks playback control on Free accounts).
  • A KEF LSX II / LS50 Wireless II / LS60 on your LAN, ideally with a DHCP reservation so its IP is stable.
  • Docker (to run the server), or just Python 3.12+ to run it directly.

Setup

1. Configure

cp .env.example .env

Fill in .env:

  • KEF_HOST — the speaker's LAN IP (from the KEF Connect app or your router).
  • KEF_DEVICE_NAME — substring of its Spotify Connect name (e.g. KEF).
  • SPOTIFY_CLIENT_ID / SPOTIFY_CLIENT_SECRET — from a Spotify app (Redirect URI: http://127.0.0.1:8888/callback).
  • MCP_AUTH_TOKEN — a random string clients must send as a Bearer token. Generate: python -c "import secrets; print(secrets.token_urlsafe(32))"

2. Get a Spotify refresh token (once)

pip install requests
export SPOTIFY_CLIENT_ID=...      # PowerShell: $env:SPOTIFY_CLIENT_ID="..."
export SPOTIFY_CLIENT_SECRET=...
python auth_spotify.py

Log in, approve, and copy the printed SPOTIFY_REFRESH_TOKEN=... line into .env.

3. Run

Docker (recommended):

docker compose up -d --build
docker compose logs -f kef-mcp

docker-compose.yml also runs a poke-tunnel sidecar that forwards the MCP server up to your Poke agent (see poke/README.md). Set POKE_TUNNEL_TOKEN in .env to enable it (run npx poke@latest login once to get it); it's only needed for Poke.

Or directly:

pip install -r requirements.txt
python server.py     # serves streamable HTTP at http://0.0.0.0:8000/mcp

4. Connect a client

  • Poke: set POKE_TUNNEL_TOKEN in .env (from npx poke@latest login) and run docker compose up -d — the poke-tunnel container connects it automatically. Full guide + the recipe: poke/README.md.
  • Other MCP clients: point them at http://<host>:8000/mcp. (If you expose the server beyond the tunnel, set MCP_AUTH_TOKEN and send Authorization: Bearer <token>.)

Sanity checks

KEF reachable from the container:

docker compose exec kef-mcp python -c \
  "import asyncio, os; from kef_client import KefClient; \
   print(asyncio.run(KefClient(os.environ['KEF_HOST']).get_status()))"

Should print the speaker status (e.g. standby / powerOn).

Notes & troubleshooting

  • "KEF Spotify Connect device not found" — the KEF only advertises as a Connect target once it's awake and has played Spotify since boot. play_on_kef wakes it first; on a cold boot, cast to it once from the Spotify app, then retry.
  • Spotify 404 / "No active device" — same cause; ensure KEF_DEVICE_NAME matches the name Spotify shows.
  • Container can't reach the KEF — verify KEF_HOST and that the speaker has a DHCP reservation.
  • KEF API — writes are POST /api/setData (JSON body), reads are GET /api/getData. Verified against LSX II / LS50 W II / LS60 firmware. Very old firmware used GET-only; update firmware if writes fail.

Project layout

kef-mcp/
├── server.py             # MCP server: defines the tools
├── kef_client.py         # KEF local HTTP API client
├── spotify_client.py     # Spotify Web API client
├── cast_client.py        # Google Cast (Chromecast built-in) client
├── youtube_resolver.py   # yt-dlp: query/URL -> direct stream URL
├── auth_spotify.py       # one-time Spotify OAuth helper
├── Dockerfile
├── docker-compose.yml    # server + poke-tunnel sidecar
├── .env.example
├── tunnel/               # headless poke-tunnel container
│   ├── Dockerfile
│   └── entrypoint.sh
└── poke/                 # Poke-specific: connection guide + recipe
    ├── README.md
    └── RECIPE.md

Security

  • .env holds your Spotify secret/refresh token and auth token — gitignored, never commit it.
  • Anyone with MCP_AUTH_TOKEN + the URL can control your speakers and Spotify. Keep it secret; consider a Cloudflare Access policy for remote exposure.

Credits

KEF local-API payload formats verified against pykefcontrol (MIT).

About

MCP server for KEF speakers

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages