fmod-mcp
An MCP server that lets an AI coding agent drive FMOD Studio: import audio, create events, wire instruments, place effects on buses, set up sends/returns, manage parameters, and build banks. It does this by speaking to Studio's built-in JavaScript scripting terminal over TCP (port 3663).
v0.2: 35 tools, 143 tests passing (106 unit + 37 live, plus 1 gated persist test). See CLAUDE.md for the tool catalog, docs/effects.md for the effect-parameter reference, docs/troubleshooting.md when something breaks, and examples/ for worked walkthroughs.
Built for use with Claude Code, but works with any MCP-compatible client.
Requirements
- FMOD Studio 2.02+ (the scripting terminal has been stable across recent versions)
- Python 3.11+
- A running FMOD Studio instance with the target project open
Install
git clone https://github.com/jmperez127/fmod-mcp.git
cd fmod-mcp
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
Register with Claude Code
Run this from inside the cloned fmod-mcp directory (so $(pwd) resolves correctly):
claude mcp add fmod -s user -- "$(pwd)/.venv/bin/python" -m fmod_mcp
The -- ends claude mcp add's flag parsing so -m fmod_mcp is treated asarguments to Python. -s user registers the server for every Claude Codesession on your machine; drop it for a project-local registration.
Alternatively, if you prefer a globally-callable shim, install via pipx:
pipx install .
claude mcp add fmod -s user fmod-mcp
Verify:
claude mcp list
Configure host/port (optional)
Defaults to 127.0.0.1:3663. Override with environment variables:
export FMOD_MCP_HOST=127.0.0.1
export FMOD_MCP_PORT=3663
Enable TCP scripting in Studio
FMOD Studio's scripting terminal listens on TCP/3663 by default. If it's disabled:
- Open FMOD Studio
Edit→Preferences(orFMOD Studio→Preferenceson macOS)- Look for
Scripting/Terminalsettings - Enable TCP listener, note the port
Quick smoke test
With FMOD Studio running and a project open:
> ping
{ ok: true, version: { productVersion: 2, majorVersion: 2, minorVersion: 20, ... } }
> list_banks
[ { path: "bank:/Master", guid: "...", name: "Master" }, ... ]
Worked examples
The examples/ directory has prompt-style walkthroughs you can paste into a Claude Code session:
| File | Scenario |
|---|---|
| add_one_sfx.md | Minimum pipeline: import → event → sound → save → build |
| batch_import_sfx.md | Loop the above over a folder of wavs |
| add_reverb_to_bus.md | Reverb return + global send (with parameter reference) |
| sidechain_compression.md | Music-ducks-to-VO routing |
| parameter_driven_volume.md | Local game parameter for an event |
| cleanup_unused_audio.md | Find and delete orphan audio in the bin |
Files it writes
~/.cache/fmod-mcp/commands.log: every JS snippet sent to Studio, timestamped by request ID. Audit trail + replay source.
Testing
Unit tests run without Studio (mock-based):
pytest -q
# 106 passed, 38 skipped (live tests)
Live tests require FMOD Studio running with any project open:
FMOD_MCP_LIVE=1 pytest -q
# 143 passed, 1 skipped (persist-gated)
The persist-gated save+build verification additionally writes to disk; opt in explicitly:
FMOD_MCP_LIVE=1 FMOD_MCP_LIVE_PERSIST=1 pytest tests/live/test_save_build_live.py
Live tests use a per-test scratch folder under event:/__mcp_test_scratch__/<hex>/ and clean up via studio.project.deleteObject on tear-down. A session-scoped sweep also runs at start AND end so an interrupted run doesn't leave orphans.
When things break
See docs/troubleshooting.md for the common failure modes and their fixes. The short version:
- Check
~/.cache/fmod-mcp/commands.logfor the exact JS that went out. - Paste it into Studio's own Scripting window (
Window→Scriptingin the Studio GUI) and iterate. - Once working, patch the corresponding JS template in
fmod_mcp/tools/*.py.
For anything not covered by a named tool, use run_js:
> run_js return studio.project.model.Event.findInstances().length;
42
Layout
fmod_mcp/
├── studio_client.py # TCP/3663 client. Single persistent connection, sentinel-framed IIFE protocol
├── server.py # FastMCP tool registration, stdio transport
├── __main__.py # python -m fmod_mcp
└── tools/
├── discovery.py # ping, list_banks, list_events, list_buses, get_event
├── audio.py # import_audio, delete_audio, move_audio
├── events.py # create_event, add_single_sound, add_multi_sound,
│ # set_event_property, assign_to_bank, assign_to_bus,
│ # delete_event, delete_folder, rename_event, move_event,
│ # add_local_parameter, add_global_parameter
├── effects.py # list_effect_types, add_effect, list_effects, get_effect,
│ # set_effect_param, remove_effect, bypass_effect
├── routing.py # add_return, remove_return, list_returns,
│ # add_send, list_sends, remove_send, set_send_level
├── project.py # save_project, build_banks
└── escape.py # run_js
tests/
├── (mock-based unit tests)
└── live/ # opt-in via FMOD_MCP_LIVE=1; auto-skip when port 3663 isn't reachable
License
MIT © 2026 jmperez127.