- Python 100%
Six separate play_sound calls never built a queue: the round trip per call was slower than the sounds themselves, so each one finished before the next arrived. Add play_sounds(list) and a count parameter on play_random_sound so a batch goes out back-to-back — four sounds now take 0.2s and leave three of them actually queued. Only the first play honours `mode`; the rest append, since interrupt or play_next on every item would have each cancel or reorder the ones before it. play_sounds resolves every name before playing anything, so a bad name aborts the whole call instead of half-queueing it. Reuse one keep-alive httpx client for the process rather than opening one per request, so the batch shares a connection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| .env.example | ||
| .gitignore | ||
| README.md | ||
| requirements.txt | ||
| server.py | ||
soundboard-mcp
An MCP server that lets Claude drive the soundboard at https://soundboard.vsidot.fr.
The server never plays audio itself. It keeps a room (default main) with one
queue and one playhead; every browser attached to that room plays in sync. So
"play" means "broadcast to the room" — if nobody is attached the sound waits in
the queue, and if three people are attached, three people hear it.
Tools
| Tool | Kind | What it does |
|---|---|---|
list_sounds |
read | Search the library by text or tag; sort by title / plays / recency |
get_sound |
read | Details for one sound, by id or name |
list_tags |
read | Tags in use, most common first |
get_playback_state |
read | Now playing, queue, and who is actually listening |
list_rooms |
read | The rooms (main is the shared one) |
get_statistics |
read | Library totals and the most-played sounds |
play_sound |
write | Play a sound by id or name |
play_random_sound |
write | Play a random sound, optionally filtered by tag/text/favorites |
set_favorite |
write | Star / unstar a sound (per-account) |
stop_playback |
destructive | Silence the room and drop the queue |
clear_queue |
destructive | Drop the queue, leave the current sound playing |
remove_queue_item |
destructive | Remove one queued item |
Names are resolved leniently: exact id → exact title → unique substring. An ambiguous name is refused with the candidate list rather than guessed at.
Play modes: enqueue (append — the default here, so a call never cuts anyone
off), interrupt (stop everything and play now), play_next (jump the queue).
Deliberately not exposed: deleting sounds, imports (YouTube / MyInstants / upload / scan), the librarian's accept-and-rewrite endpoints, and user / invite / token administration. Those are destructive or admin-level and shouldn't be one model mistake away. Add them if you want them.
Setup
Needs uv (the system Python 3.9 is too old for the MCP SDK, which wants 3.10+).
uv venv --python 3.11
uv pip install -r requirements.txt
cp .env.example .env # then put a real sb_... token in it
Mint a token under Account in the web app, or POST /api/v1/auth/tokens.
Verify config and connectivity without starting the server:
set -a; . ./.env; set +a
.venv/bin/python server.py --check
Registering with Claude Code
Already registered at user scope. To redo it elsewhere:
claude mcp add soundboard -s user \
-e SOUNDBOARD_TOKEN=sb_... \
-e SOUNDBOARD_URL=https://soundboard.vsidot.fr \
-e SOUNDBOARD_ROOM=main \
-- "$HOME/Documents/dev/s/soundboard-mcp/.venv/bin/python" \
"$HOME/Documents/dev/s/soundboard-mcp/server.py"
The token lives in the MCP config (~/.claude.json) and in .env for local
runs — never in server.py. .env is gitignored and mode 600.
Config
| Variable | Default | Notes |
|---|---|---|
SOUNDBOARD_TOKEN |
— | Required. sb_... API token. |
SOUNDBOARD_URL |
https://soundboard.vsidot.fr |
Base URL. |
SOUNDBOARD_ROOM |
main |
Room used when a tool gets no session. |
The library is cached for 60s (SOUNDS_TTL in server.py) so searches don't
refetch 314 sounds on every call.