sfx-gen-mcp
by JimCline
README.md
# sfx-gen-mcp
An [MCP](https://modelcontextprotocol.io) server that lets LLM agents (Claude
Code, etc.) generate game sound effects **locally** with Stability AI's
[Stable Audio Open](https://huggingface.co/stabilityai/stable-audio-open-1.0).
No API keys, no per-generation cost — your agent asks for "a coin pickup
sound in assets/sounds/" and gets a .wav file.
## Tools
- **`generate_sfx`** — text prompt → .wav file(s). Parameters: `prompt`,
`duration_seconds` (0.5–47), `steps`, `cfg_scale`, `seed`,
`negative_prompt`, `variations` (1–4 takes per call), `output_dir`,
`filename`. Returns JSON with saved file paths and the seed used (so a
liked sound can be reproduced or varied).
- **`sfx_server_status`** — model/device/load state.
The **`sfx-gen-ctl`** control server (stdio, see [Control server](#control-server))
adds three more:
- **`sfx_ensure_server`** — start the daemon if it is not listening, retrying
on failure. No-op when it is already up.
- **`sfx_status`** — daemon reachability, launchd supervision state, and the
loaded model/device when it is running.
- **`sfx_troubleshoot`** — ok/warn/fail checklist with copy-pasteable fixes.
## Requirements
- Python 3.10+
- ~10 GB disk for model weights, ~12 GB RAM while generating
- GPU strongly recommended: CUDA or Apple Silicon (MPS is supported and
patched at runtime — upstream stable-audio-tools uses float64 which MPS
lacks)
- Hugging Face access to the **gated** model: accept the license at
[stabilityai/stable-audio-open-1.0](https://huggingface.co/stabilityai/stable-audio-open-1.0),
then `hf auth login`
This server is the local-SFX half of
[game-audio-kit](https://github.com/JimCline/game-audio-kit), which bundles it
with a music/voice MCP server and a Claude Code audition-workflow skill — but
it stands alone: any MCP client can use it directly.
## Install
Not yet on PyPI — install from this repo:
```sh
uv tool install git+https://github.com/JimCline/sfx-gen-mcp
# or: pip install git+https://github.com/JimCline/sfx-gen-mcp
```
## Run
Two modes:
**Shared daemon (recommended)** — one resident model serves every client;
sessions connect over streamable HTTP and skip the per-session model load:
```sh
PYTORCH_ENABLE_MPS_FALLBACK=1 sfx-gen-mcp --transport http --port 8756
```
```sh
claude mcp add --transport http --scope user sfx-gen http://127.0.0.1:8756/mcp
```
**Per-session (stdio)** — simplest, but each client process loads its own
copy of the model:
```sh
claude mcp add --scope user --env PYTORCH_ENABLE_MPS_FALLBACK=1 -- sfx-gen sfx-gen-mcp
```
Concurrent requests to the daemon are serialized with a lock — a second
client queues instead of contending for the GPU.
The model lazy-loads on the first `generate_sfx` call (~20–40s), then stays
resident. On an Apple M-series GPU a 50-step clip takes roughly 15–30s.
### Env vars
| Variable | Meaning |
| ------------------ | ---------------------------------------------------- |
| `SFX_MODEL` | HF model name (default `stabilityai/stable-audio-open-1.0`) |
| `SFX_OUTPUT_DIR` | Default output directory (default `<cwd>/sfx-output`) |
| `SFX_IDLE_TIMEOUT` | Seconds of inactivity before the server exits to free model memory (default 1800; 0 disables) |
### Idle shutdown
Once the model has been loaded, the server exits after `SFX_IDLE_TIMEOUT`
seconds (default 30 min) without a generation, releasing the ~10 GB of model
memory. Run it under a supervisor that restarts it (launchd `KeepAlive`,
systemd `Restart=always`, docker `--restart`) and it respawns instantly as a
small model-free listener; the next `generate_sfx` call reloads the model.
A server that has never loaded the model never exits.
## Control server
The daemon's tools are reached over HTTP, so when it is down they are gone —
a "restart yourself" tool hosted on it could never be called. `sfx-gen-ctl` is
a second, dependency-light MCP server that solves this by running over
**stdio**: the client spawns it per session, so it answers whether or not the
daemon is alive. It never imports torch and never loads the model.
If you installed the package (so the console script is on PATH):
```sh
claude mcp add --scope user -- sfx-gen-ctl sfx-gen-ctl
```
Straight from a checkout, with no install — the control server needs only
`mcp`, never torch:
```sh
claude mcp add --scope user -- sfx-gen-ctl \
uv run --no-project --with 'mcp>=1.9,<2' python /path/to/sfx-gen-mcp/src/sfx_gen_mcp/control.py
```
Register it alongside the HTTP daemon and an agent that finds `generate_sfx`
missing can call `sfx_ensure_server` to bring the service back, then
`sfx_troubleshoot` if it will not start.
`sfx_ensure_server` prefers `launchctl kickstart` over spawning its own
process, so it never races the `KeepAlive` supervisor for port 8756; it falls
back to a detached spawn only when no launchd job is installed. Readiness is a
TCP connect, not an HTTP status check — FastMCP answers a bare `GET /mcp` with
4xx, so polling for 200 would never see a healthy daemon.
Note that bringing the daemon up does not necessarily re-establish an MCP
connection that already failed in an open session; run `/mcp` to reconnect, or
start a new session.
It honours the same `SFX_HOST` / `SFX_PORT` as the daemon, plus
`SFX_LAUNCHD_LABEL` (default `com.sfx-gen-mcp`) and `SFX_LOG`.
### Pinning `mcp`
`mcp` 2.0 removed the `mcp.server.fastmcp` module this server imports, so the
dependency is capped at `mcp>=1.9,<2`. If you launch the server via
`uv run --with ...`, pin it there too — an unpinned `--with mcp` will silently
start resolving to 2.x and the daemon will crash-loop on
`ModuleNotFoundError: No module named 'mcp.server.fastmcp'`. In a launchd
plist, remember to escape `<` as `<`.
### macOS: run at login
See [`launchd/com.sfx-gen-mcp.plist`](launchd/com.sfx-gen-mcp.plist) for a
LaunchAgent template: copy to `~/Library/LaunchAgents/`, adjust paths, then
`launchctl load ~/Library/LaunchAgents/com.sfx-gen-mcp.plist`. Idle memory
is small — the model only loads when the first generation is requested.
## Prompting tips
Concrete, physical descriptions work best:
- `"sword clashing against metal shield, sharp ring"` not `"battle sound"`
- `"footsteps on gravel, slow walking pace"` not `"walking"`
- Use `negative_prompt: "music, voices"` to keep ambiences clean
- Impacts: 1–2s. UI blips: ~1s. Ambient loops: 10–30s.
## License
MIT for this server. Model weights are governed by the
[Stability AI Community License](https://huggingface.co/stabilityai/stable-audio-open-1.0);
outputs are usable in commercial projects for organizations under $1M
annual revenue — review the license for your situation.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues