XR18 MCP Server
# XR18 MCP Server
An MCP server that controls a Behringer XR18 over its OSC UDP interface. The
client implementation is `xr18`; mixer commands are sent to UDP port `10024`.
The command registry is based on `doc/paramenters.txt`, the supplied X AIR OSC
protocol PDF, and the Behringer World X-Air OSC reference. Generic raw tools
provide complete access to documented paths, including firmware-specific paths
that do not have a dedicated semantic MCP tool.
## Requirements
- Python 3.12 or newer
- [uv](https://docs.astral.sh/uv/)
- An XR18 reachable over the network
## Install and run
```bash
uv sync
XR18_HOST=192.168.1.100 uv run xr18-mcp
```
Configuration environment variables:
- `XR18_HOST` (required): mixer IP address or hostname
- `XR18_PORT` (optional): OSC UDP port; defaults to `10024`
- `XR18_TIMEOUT` (optional): request timeout in seconds; defaults to `2`
## MCP configuration
Use stdio in an MCP client configuration. Replace the directory with this
project's absolute path.
```json
{
"mcpServers": {
"xr18": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/xr18", "xr18-mcp"],
"env": {
"XR18_HOST": "192.168.1.100"
}
}
}
}
```
## Tools
Mixer-assistant tools, which speak dB rather than raw protocol values:
- `xr18_list_channels`, `xr18_resolve_channel` (regular channels, AUX, FX returns, monitor buses, FX sends, LR, and DCAs)
- `xr18_get_send_db`, `xr18_set_send_db`, `xr18_adjust_send_db`
- `xr18_get_fader_db`, `xr18_set_fader_db`, `xr18_adjust_fader_db`
Send `1`–`6` are the monitor buses and send `7`–`10` are FX sends `1`–`4`. Source names can resolve regular channels, the AUX return, FX returns, monitor buses, FX send masters, LR, and DCAs. Destination arguments accept numeric buses, canonical identifiers such as `bus/5` and `fxsend/2`, or configured destination names. `fxsend/2` maps to channel send slot `08`.
Low-level and administrative tools:
- `xr18_info`, `xr18_status`, `xr18_enable_remote`
- `xr18_get`, `xr18_set`, `xr18_trigger`
- `xr18_list_commands`, `xr18_describe_command`
- `xr18_read_meters` for batches `0` through `9`; returned values are dB
- `xr18_channel_set_fader`, `xr18_channel_set_mute`
- `xr18_headamp_set_phantom`
- `xr18_snapshot_load`, `xr18_snapshot_save`, `xr18_snapshot_delete`, and `xr18_snapshot_set_scope`
Use `xr18_describe_command` before writing an unfamiliar raw OSC path. Native
XR18 float values are normalized between `0.0` and `1.0`; integers and strings
follow the parameter documentation.
Examples of raw paths:
```text
/ch/01/mix/fader
/ch/01/gate/on
/ch/01/eq/1/g
/bus/1/mix/on
/routing/p16/01/src
/fx/1/type
```
## Mixer assistant agent (llama.cpp)
`agents/mixer_assistant` is a Google ADK agent that turns plain-language
requests into mixer changes, running against a local llama.cpp server. It
understands English and Italian.
```text
"raise 1 dB of guitar in monitor 5"
"lower the kick 2 dB in wedge 3"
"set lead vocal fader to -6 dB"
"alza di 1 dB la chitarra nel monitor 5"
"mute channel 8"
```
### 1. Start llama.cpp
Tool calling requires the `--jinja` flag. Without it the model returns prose and
no mixer command is ever issued.
```bash
llama-server --jinja -fa on -hf bartowski/Qwen2.5-7B-Instruct-GGUF:Q4_K_M --port 8080
```
Use a model with a native tool-calling template. Qwen 2.5, Llama 3.1/3.3,
Hermes 2/3, Mistral Nemo, and Functionary are supported natively. Avoid
aggressive KV cache quantization such as `-ctk q4_0`, which measurably degrades
tool-calling accuracy.
### 2. Run the agent
```bash
cp agents/.env.example agents/.env # then edit XR18_HOST
cd agents
uv run adk run mixer_assistant # terminal chat
uv run adk web # browser chat
```
Configuration:
- `XR18_HOST`, `XR18_PORT`, `XR18_TIMEOUT` for the mixer
- `LLAMA_API_BASE` defaults to `http://localhost:8080/v1`
- `LLAMA_MODEL` defaults to `qwen2.5-7b-instruct`
### Source resolution examples
The assistant distinguishes a source strip from a destination send:
```text
"raise 1 dB of guitar in monitor 5" -> source guitar, send 5
"raise 1 dB of aux in monitor 2" -> source AUX return, send 2
"raise reverb in monitor 3" -> source named FX return, send 3
"raise monitor 5 fader" -> source monitor bus 5 fader
"lower delay send 2 fader" -> source FX send master 2 fader
```
Bare `aux`, `aux in`, and `aux input` resolve to `/rtn/aux`. Configured strip
names are read from the mixer and take precedence over generic labels. Ambiguous
names are rejected rather than guessed.
### Design notes
- **All gain mathematics is done in Python, never by the model.** The model may
only pass a delta such as `1.0` to `xr18_adjust_send_db`. Relative changes are
a single atomic read-modify-write inside the MCP server.
- **The tool surface is filtered to 9 tools.** Local 7B models select tools far
less reliably when shown all 24, so raw OSC and console administration are
hidden from the agent.
- **Instrument names** resolve from the mixer's scribble strips first, then from
`agents/mixer_assistant/aliases.json`. Ambiguous names raise an error and the
agent asks which channel is meant rather than guessing.
- The agent cannot enable phantom power, initialize the console, delete
snapshots, or change network settings.
## OSC write behavior
XR18 parameter writes are fire-and-forget by default because XR18 firmware does
not reliably echo set messages. Query operations still wait for a mixer reply.
The absolute MCP setter tools support `verify=true` to issue a separate readback
query after writing:
```text
xr18_set_send_db(source="ch/03", bus="bus/5", db=-4, verify=true)
xr18_set_fader_db(source="ch/03", db=-6, verify=true)
```
Relative adjustments read the current value, send the new value without waiting,
and return the expected resulting dB value.
## Safety
The server requires `confirm=true` for console initialization, applying network
settings, snapshot deletion, and phantom power changes. Network updates can
disconnect the MCP server from the mixer. The XR18 ignores `/-action/setclock`.
`/xremote` subscriptions expire after ten seconds, so call
`xr18_enable_remote` periodically while listening for parameter changes.
## Test
```bash
uv run pytest
```
Canonical resolver identifiers can be passed directly to later tools: `ch/10`, `rtn/aux`, `rtn/1`, `bus/5`, `fxsend/2`, `lr`, and `dca/1`.
TDQS
Scored across 25 tools
Most tools are clearly distinct by purpose (e.g., snapshots, meters, sends, faders), but there is overlap in how fader levels can be manipulated (xr18_set, xr18_channel_set_fader, xr18_set_fader_db), which could lead to misselection. Descriptions are detailed enough to reduce ambiguity.
Naming is mixed: some tools follow xr18_<action> (get, set, trigger), others xr18_<object>_<action> (snapshot_load, channel_set_fader), and still others xr18_<action>_<object> (get_fader_db, set_send_db). This inconsistency, while readable, could confuse an agent expecting a uniform pattern.
25 tools is on the high end of the acceptable range, but justified for a complex mixer server covering channels, sends, snapshots, meters, and status. The count is not excessive given the domain scope.
The tool set covers core mixing operations (faders, mutes, sends), snapshots (load/save/delete/scope/name), channel listing/resolution, meters, and status. Generic get/set allow access to any parameter, filling potential gaps. Missing dedicated EQ or effect tools are mitigated by the generic interface.