A Model Context Protocol (MCP) server that gives Claude, ChatGPT-compatible MCP clients, or any MCP-capable agent direct control of Behringer/Midas mixers via OSC. The default and most complete mode targets Behringer X32 / Midas M32 consoles; an optional OSCXR mode adds partial XAir/XR-compatible addressing for the command families mapped in PROTOCOL.md. Recommended to be used together with the LLM agent https://github.com/infrafast/LiveStageAssistant
This is a rewrite/fork of anteriovieira/osc-mcp-server and carries ideas from the X32 MCP fork lineage, with substantially expanded direct OSC coverage and several bug fixes verified against live hardware (firmware 2.07+). This repository does not include the later schema-driven /node, meter snapshot, deterministic scene-audit, or FX-algorithm-schema layers described by some upstream forks; see Not Implemented Here.
For developpers: https://deepwiki.com/infrafast/XMSeries-MCP
MCP tools organized into groups. Highlights beyond the original small MCP server:
- Focused channel, bus, aux, FX-return, and main coverage — faders, mutes, names, sends, returns, and status tools for common live operations
- FX return control — read and mute/unmute FX return state without exposing low-level FX parameter editing
- dB-aware level helpers —
osc_db_to_fader_level,osc_fader_level_to_db, and factorized fader/send tools withunit:"db"use the X32/M32 161-point pseudo-log Level table (0.7500 = 0 dB,1.0000 = +10 dB) - Timed automation — background ramps/fades, delayed OSC actions, and temporal macros through
osc_automation_*tools, so agents do not perform timing-sensitive work with repeated LLM tool calls
- LLM-assisted mixer inspection — ask the agent to inspect routing, channel strips, bus sends, FX returns, DCA state, and obvious setup inconsistencies using the bulk read tools.
- Controlled fixes — common readable direct-control parameters are exposed as dedicated typed MCP tools rather than raw OSC escape hatches.
- Volunteer-friendly operation — natural-language commands can cover common worship, rehearsal, broadcast, and small-venue tasks without requiring the operator to remember OSC paths.
- Protocol experimentation —
OSCXRmode makes the XAir/XR-compatible subset explicit and fails fast for unmapped features instead of silently sending lossy commands.
Prereqs: Node 18+, an MCP-capable client, and a supported mixer on your network with OSC enabled. X32/M32 uses OSCX32M32 by default; XAir/XR-compatible mixers can use OSCXR for the currently mapped subset.
cd /Users/ts/Documents/PlatformIO/Projects/XMSeries-MCP
npm install
npm run buildAdd to your Claude Desktop config (%APPDATA%\Claude\claude_desktop_config.json on Windows, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"osc": {
"command": "node",
"args": ["C:\\path\\to\\XMSeries-MCP\\dist\\index.js"],
"env": {
"OSC_HOST": "192.168.1.70",
"OSC_PORT": "10023",
"OSC_PROTOCOL": "OSCX32M32",
"MCP_PROMPT_FILE": "/Users/ts/Documents/PlatformIO/Projects/XMSeries-MCP/PROMPT.md"
}
}
}
}Replace the IP with your mixer's (on the X32: Setup -> Network). Restart Claude Desktop.
OSC_PROTOCOL is optional. Use OSCX32M32 for Behringer X32 / Midas M32 consoles, or OSCXR for XAir/XR-compatible addressing. If omitted, the server defaults to OSCX32M32. MCP_PROMPT_FILE is also optional; it lets you point the server at a custom prompt file. If omitted, the server exposes the repository PROMPT.md.
The server starts with these environment values, then the active mixer can be changed at runtime with osc_configure_mixer. Omitted fields keep their current values. Changing host, port, or protocol closes the current OSC client and reconnects to the new mixer. For count-only updates, use osc_set_mixer_counts; it updates resolver and bulk-read limits without reconnecting. If counts are included in osc_configure_mixer, they are applied together with the connection change.
Example runtime change:
{
"host": "192.168.0.160",
"port": 10024,
"protocol": "XR"
}Example runtime limit update:
{
"channelCount": 32,
"busCount": 16,
"fxCount": 5,
"dcaCount": 3
}Use osc_set_mixer_counts for that count-only update.
See INSTALLATION.md, QUICKSTART.md, and AGENTS.md for additional client wiring, including Cline, Continue.dev, and other MCP-compatible agents.
Both MCP transports read these values at startup:
| Variable | Default | Purpose |
|---|---|---|
OSC_HOST |
192.168.1.17 |
Mixer IP address |
OSC_PORT |
10023 |
Mixer OSC UDP port |
OSC_PROTOCOL |
OSCX32M32 |
Address mapping mode: OSCX32M32 or OSCXR |
MCP_PROMPT_FILE |
repository PROMPT.md |
Optional absolute path to the prompt exposed through MCP |
XMS_SPEAKER_MAP |
empty | Optional JSON map used by osc_get_speaker_context to translate a recognized voice speaker into monitor bus/channel names |
XMS_SPEAKER_MAP is intentionally server-side. A voice agent may pass a neutral speaker value, but this MCP server decides how that speaker maps to the mixer. Example:
XMS_SPEAKER_MAP='{"laurent":{"bus":"Laurent","channel":"Talk Laurent"},"marie":{"bus":"Marie"}}'If a known speaker has no explicit entry, osc_get_speaker_context defaults busName to the speaker name and leaves channelName empty. Use explicit entries when mixer labels differ from speaker names.
In HTTP mode, the /mcp admin page exposes this same speaker mapping as XMS_SPEAKER_MAP in the configuration form. Saving it updates the running HTTP server immediately; for stdio mode, set XMS_SPEAKER_MAP in the MCP client config env before launching the server.
The full MCP server can run either as the original local stdio server or as a Streamable HTTP MCP server. Both transports use the same reusable MCP server factory and expose the same tools, prompts, and resources.
stdio mode remains the default and is unchanged:
npm startClient configs that launch node dist/index.js continue to work as before.
HTTP mode exposes the MCP endpoint on the network:
HTTP_HOST=0.0.0.0 HTTP_PORT=8787 MCP_AUTH_TOKEN=change-me npm run start:httpHTTP mode reads these additional variables:
| Variable | Default | Purpose |
|---|---|---|
HTTP_HOST |
0.0.0.0 |
Interface for the HTTP MCP server. Use 0.0.0.0 to accept connections from other machines on the LAN. |
HTTP_PORT |
8787 |
HTTP MCP port |
HTTP_PUBLIC_HOST |
auto-detected | Optional LAN IP or hostname to print in the agent JSON config. Useful in Docker, where auto-detection may otherwise find the container IP. |
MCP_AUTH_TOKEN |
unset | Optional bearer token required on /mcp and /health when set |
OSC_CHANNEL_COUNT |
32 |
Initial number of mixer input channels to scan for name resolution and overview reads. Can be changed at runtime with osc_configure_mixer. |
OSC_BUS_COUNT |
16 |
Initial number of mix buses to scan/use for name resolution and all-bus commands. Can be changed at runtime with osc_configure_mixer. |
OSC_FX_COUNT |
8 |
Initial number of FX slots/returns to scan for name resolution and FX reads. Can be changed at runtime with osc_configure_mixer. |
OSC_DCA_COUNT |
8 |
Initial number of DCA groups to scan for name resolution and overview reads. Can be changed at runtime with osc_configure_mixer. |
The HTTP MCP endpoint is /mcp; a health endpoint is available at /health. Browser GET /mcp requests without an MCP session show a small admin page with the live mixer status and editable runtime connection/count settings. The same page uses GET /mcp/status and POST /mcp/config; Streamable HTTP agent traffic on /mcp is unchanged. If MCP_AUTH_TOKEN is set, remote agents and browser/admin requests must send Authorization: Bearer <token> or x-mcp-auth-token: <token>.
Example remote-agent configuration:
{
"mcpServers": {
"xmseries-http": {
"type": "streamable-http",
"url": "http://192.168.1.50:8787/mcp",
"headers": {
"Authorization": "Bearer change-me"
}
}
}
}Replace 192.168.1.50 with the IP address of the computer running XMSeries-MCP. A copy of this example is provided in mcp_http_agent_config.example.json.
Because this server can control live mixer state, avoid exposing HTTP mode directly to the public internet. Prefer a trusted LAN, VPN, or authenticated reverse proxy.
Docker / Synology Container Manager
Build and run locally:
docker build -t xmseries-mcp:latest .
docker run --rm -p 8787:8787 \
-e HTTP_PUBLIC_HOST=192.168.1.50 \
-e MCP_AUTH_TOKEN=change-me \
-e OSC_HOST=192.168.0.1 \
-e OSC_PORT=10023 \
-e OSC_PROTOCOL=OSCX32M32 \
-e OSC_CHANNEL_COUNT=32 \
-e OSC_BUS_COUNT=16 \
-e OSC_FX_COUNT=8 \
-e OSC_DCA_COUNT=8 \
-e DEBUG=false \
xmseries-mcp:latestOr use the included docker-compose.yml as a starting point. On Synology, set HTTP_PUBLIC_HOST to the NAS LAN IP or DNS name that agents should use. Set DEBUG=true when you want [OSC READ] and [OSC WRITE] traces in the container logs. The official Node base image supports common Synology architectures such as linux/amd64 and linux/arm64; build on the target NAS or publish a multi-architecture image with docker buildx.
OSCX32M32 is the complete/default mode. OSCXR is now partially effective for the command families currently mapped in PROTOCOL.md: channel fader/mute/name, channel sends to bus level, bus fader/mute/name, main LR, FX return, aux return via /rtn/aux, DCA fader/mute/name, and headamp gain.
When OSC_PROTOCOL is OSCXR, commands that are still X32-only or not yet mapped return an explicit Unsupported for OSCXR: ... error instead of waiting for an OSC timeout. This includes routing/user routing, matrices, console overview, colors/icons, gate/compressor, pan, EQ frequency/Q/type, and other features not covered by PROTOCOL.md yet. Bus-specific source mute operations are also guarded: X32 can mute channel/FX/aux sends to one bus, while XR exposes only global source mute paths, so those lossy translations are rejected instead of silently muting the whole source.
Windows MSIX note: if you installed Claude Desktop from the Microsoft Store, the config path is
%LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude\claude_desktop_config.json, not the standard%APPDATA%\Claude\path.
A few X32/M32 quirks that will bite you if you do not know them. These mostly apply to the default OSCX32M32 mode. In OSCXR mode, unsupported X32-only tools fail fast with Unsupported for OSCXR: ....
1. Routing: block-level vs. per-channel (firmware 4.0+). On modern X32 firmware, inputs have two layers:
- Block-level (
/config/routing/IN/1-8etc.) picks which 8-channel source group feeds each range of channels. Legacy style. - User In (
/config/userrout/in/NN, 32 slots) patches each individual channel to any physical source — Local, AES50A/B, Card, AuxIn. This only takes effect if the corresponding block is set to "User In".
Routing tools are not exposed in this server profile.
2. FX racks are user-configurable. Do not assume slot 1 is always a reverb or slot 5 is always a GEQ. This focused profile exposes FX return level/send/mute state, not full FX algorithm introspection.
3. FX slots have no /on or /mix addresses. FX are always instantiated on X32. "Turn off FX 3" really means "mute the FX 3 return channel." osc_set_effect_on does this automatically. Wet/dry varies by FX algorithm and lives in the per-slot params, not a global mix.
4. FX slot numbers are unpadded. /fx/1/type works; /fx/01/type silently fails. Every other numeric address in X32 uses zero-padded 2-digit numbers (/ch/05/..., /bus/12/...) — FX is the exception.
5. FX parameters are intentionally not exposed. This focused profile avoids low-level normalized FX parameter writes because it does not include a named FX algorithm schema.
6. OSC types are strict. X32 silently drops messages where the type tag does not match. This server now exposes only dedicated typed tools for supported operations; raw OSC custom writes are intentionally not available.
At startup, the server exposes the recommended agent instructions from PROMPT.md in three MCP-compatible ways: standard prompt agent_prompt, standard resource agent://prompt/system, and standard fallback tool get_agent_prompt. The MCP client or host agent must still decide to fetch and inject that content into the LLM context; the server cannot force system-prompt injection by itself.
For a custom prompt, set MCP_PROMPT_FILE in the MCP server env to an absolute path.
For any question about the current mixer state, call the relevant read/get tool before answering. Do not reuse prior conversation context as the source of truth for live levels, mutes, routing, FX state, or other console values.
For broad inspection, start with low-risk read tools:
osc_get_mixer_status
For live mix state questions, read the relevant focused fader, mute, send, or FX-return tool directly.
Routing tools are not exposed in this server profile. For XAir/XR-compatible targets, expect unsupported X32-only requests to return Unsupported for OSCXR: ....
Once wired up to LLM, natural language works:
"Why isn't channel 5 working?"
"Compare channel 1 and channel 2 using their strip reads."
"Review my FX setup — anything redundant?"
"Mute all channels except kick, snare, and overheads."
"What's plugged into the console right now?"
"Fade out Voc-Claude in 10 seconds."
"In 5 seconds, mute the main LR."
"Fade Kick on Laurent down a little over 3 seconds."
"Mute all buses."
"Mute Mike and Laurent buses."
"Set Laurent, Mike, and front panel to -3 dB."
This server supports grouped operations so the agent can execute one intent across several targets without manually iterating one tool call per target.
- All bus masters (for example:
"mute all buses"):- Uses
osc_mute_all_busesto mute/unmute every bus master in one batch.
- Uses
- Selected bus master lists (for example:
"mute Mike and Laurent buses"):- Resolve each bus name with
osc_find_named_target, then useosc_mute_buses.
- Resolve each bus name with
- Selected bus send-level lists (for example:
"set kick to -3 dB on Laurent and Mike"):- Resolve bus names, then use
osc_send_to_buses_db.
- Resolve bus names, then use
- All bus send levels (for example:
"set kick to -3 dB on all buses"):- Use
osc_send_to_all_buses_db.
- Use
- Mixed destination command including main LR (for example:
"set Laurent, Mike and the front panel to -3 dB"):- The bus list (
Laurent,Mike) is applied withosc_send_to_buses_db, and front panel/façade/main LR is included in the same batch intent viaincludeMain: true.
- The bus list (
These grouped tools are preferred over issuing many per-bus tool calls because they keep intent explicit, reduce round-trips, and avoid inconsistent partial execution.
osc_find_named_target recognizes channel labels that follow an <instrument>-<owner> convention from natural French ownership phrases. It removes articles and ownership connectors, maps guitare to the common mixer label prefix guitar, and uses limited French phonetic normalization only for the owner token. Examples include guitare de Claude -> guitar-clode, basse de Mike -> basse-mike, and saxophone de Luc -> saxophone-luc.
The resolver returns these as structured matches. Only a unique structured match is safe to use; multiple structured matches require clarification, and ordinary fuzzy matches still require confirmation. In a phrase such as monte la guitare de Claude sur Laurent, resolve the complete ownership phrase in the channel family and resolve Laurent separately in the bus family.
For source-to-return commands, prefer osc_resolve_channel_to_bus. It accepts separate source and destination strings, resolves the source only among channels and the destination only among buses, and returns safeToWrite:true only when both sides are unique non-fuzzy matches. For example, monte la batterie sur Anthony becomes { "source": "batterie", "destination": "Anthony" }; never merge it into a channel lookup for batterie de Anthony.
Run the offline name-resolution checks with npm run test:name-resolution. They do not connect to a mixer or send OSC writes.
Full list is visible to Claude; high-level groupings:
| Group | Coverage |
|---|---|
| Channel strips | headamp/preamp context, fader, mute, name, source, bus sends |
| Bus / Matrix / Aux / FX-Return / DCA / Main | faders, mutes, names, focused strip reads |
| Identity / status | osc_get_mixer_status uses /xinfo for network address, mixer network name, console model, and console version |
| FX | all-effects overview and FX return on/off plus parameter writes |
| Bulk reads | channel_strip, bus_strip, aux_strip, matrix_strip, fx_return_strip, main_strip, dca, headamp, console_overview |
| Fader dB conversion | osc_db_to_fader_level, osc_fader_level_to_db, factorized fader/send tools with unit:"db" |
| Automation | osc_automation_ramp, osc_automation_delayed_command, osc_automation_macro, osc_automation_list, osc_automation_cancel for background fades, delayed actions, and timed sequences |
Dedicated direct-control write tools use transactional OSC write-back verification where the target address is readable: the server writes one value, reads the same OSC address back, and verifies the returned value. Numeric values use a small tolerance and a few short read retries to tolerate mixer update latency. If the mixer does not answer, the tool reports Le mixeur est deconnecté; if the value read back differs, the tool reports that the OSC command was not executed correctly.
Batch bus mute tools verify each bus write and report partial failures instead of silently claiming success. Ramp automations do not read after every step, but they verify the final value before marking the job completed.
The raw OSC fader values are normalized floats from 0.0 to 1.0. For user-facing dB commands, the server now uses the X32/M32 "Appendix - Level Table - 161 pseudo-log scale Level values" from the unofficial OSC reference:
osc_db_to_fader_level({"db": 0})-> normalized level0.75osc_fader_level_to_db({"level": 0.75})->0 dBosc_channel_faderwithunit:"db"for channelsosc_bus_faderwithunit:"db"for busesosc_aux_faderwithunit:"db"for aux returnsosc_main_faderwithunit:"db"for main LR
For safety, every fader/send action:"set" must include an explicit unit. Read actions still default to dB. If you pass a normalized fader level such as 0.575, set unit:"level"; if you pass a dB value such as -7, set unit:"db".
The conversion snaps to the nearest point in the 161-entry table. Values below -87 dB map to -inf/0.0; values above +10 dB clip to +10 dB/1.0.
The MCP server includes a small background automation engine for timing-sensitive work. The LLM should start one automation job and let the server handle the clock, rather than trying to perform fades with many repeated tool calls.
Available tools:
osc_automation_rampstarts a fade/ramp on one numeric target and returns immediately with a job id.osc_automation_delayed_commandschedules one delayed supported mixer command. Prefer structuredtarget+toDb/toLevelfor delayed level writes.osc_automation_macroruns a sequence of waits, allowlisted raw commands, and structured ramps. Eachrampstep must include its own structuredtarget; usetype:"wait"for delays inside macros (type:"delay"is accepted as a compatibility alias).osc_automation_listlists running, completed, failed, and cancelled jobs.osc_automation_cancelcancels a running job by id.
Supported ramp targets include channel faders, channel sends to bus, bus faders, main LR, FX-return faders, FX sends to bus, aux faders, aux sends to bus, matrix faders, and allowlisted raw numeric OSC addresses.
Examples:
{
"target": { "kind": "channel_fader", "channel": 1 },
"toDb": -120,
"durationSeconds": 10,
"curve": "ease_out",
"label": "Fade out channel 1"
}For a named bus/monitor fader, use kind:"bus_fader":
{
"target": { "kind": "bus_fader", "bus": 2 },
"toDb": 0,
"durationSeconds": 12,
"label": "Raise Claude bus to 0 dB"
}For a delayed main LR/façade level write, use a structured target instead of a raw OSC address:
{
"delaySeconds": 5,
"target": { "kind": "main_fader" },
"toDb": 0,
"label": "Set main LR to 0 dB later"
}{
"target": { "kind": "channel_send", "channel": 6, "bus": 1 },
"toDb": -6,
"durationSeconds": 3,
"label": "Fade Kick on Laurent"
}{
"delaySeconds": 5,
"command": { "address": "/main/st/mix/on", "args": [0], "osctype": "int" },
"label": "Mute main LR later"
}Raw automation commands are rejected unless the address is in the server's protocol-aware allowlist for the active mixer protocol. Do not invent OSC paths; use structured targets for known level writes.
For write-heavy ramps, the server sends timed OSC writes without probing /xinfo at every step, then verifies the final target value. This keeps fades smooth while still detecting failed end states.
Works. Tested against:
- X32 Producer, firmware 2.07 (primary dev target)
- Should work on any X32 variant (full, Compact, Rack, Core) and M32 family — the OSC surface is identical
- Firmware-4.0+ User In/User Out routing paths are implemented and decoded; some output-source labels are marked best-effort in code where less thoroughly verified.
OSCXRsupport is intentionally partial and followsPROTOCOL.md; useOSC_PROTOCOL=OSCXR npm testfor the protocol-aware smoke path.
Some related upstream forks document features that are not present in this repository. Do not expect these tool names or behaviors unless they are added later:
osc_capabilities- Schema-driven
/nodetools such asosc_node_get,osc_node_set, andosc_list_nodes - Deterministic scene snapshot/audit tools such as
osc_scene_snapshotandosc_scene_audit - Signal-flow tracing tools such as
osc_trace_signalandosc_find_routing - Binary meter snapshots or streaming meter subscriptions such as
osc_meter_snapshot - Named FX algorithm parameter schemas such as
osc_fx_get,osc_fx_set,osc_fx_set_type, orosc_fx_list_algorithms - Insert GEQ/TEQ helpers such as
osc_insert_eq_get,osc_insert_eq_set, andosc_find_geq_slots - Scene comparison/copy helpers such as
osc_compare_scenes,osc_compare_channels, andosc_copy_channel
Other out-of-scope mixer areas:
- Talkback (
/config/talk/*) - Monitor / headphone (
/-stat/monitor/*) - Custom user-assignable controls (
/config/userctrl/*) - Meters (
/meters/*— uses a different subscribe-based binary protocol) - Show/library file management (
/-show/*,/-libs/*, deeper/-snap/*management) - Console preferences (
/-prefs/*) - USB recorder and file browser operations
- DP48 personal mixer workflows
npm run build # compile
npm run dev # watch mode
npm start # run directly (for debugging outside Claude Desktop)
npm run start:http # run the full MCP server over Streamable HTTP
npm test # protocol-aware smoke test through test-connection.js
npm run test:llm-tools # LLM natural-language -> MCP tool-call benchmarkFor XR/XAir-compatible smoke testing:
OSC_HOST=192.168.0.16 OSC_PORT=10024 OSC_PROTOCOL=OSCXR npm testsrc/osc-client.ts — all the mixer I/O, path selection for OSCX32M32 vs. OSCXR, type coercion helpers, User In/User Out decoders, and the OSC connection (binds UDP on 0.0.0.0 so the mixer's replies actually arrive — upstream bound localhost and silently got nothing).
src/index.ts — the MCP tool surface and reusable createOscMcpServer() factory. Every tool has a name, description, inputSchema, and a handler case. The CLI entry point still uses StdioServerTransport.
src/automation.ts — the background automation engine used by osc_automation_* tools for ramps, delayed actions, and temporal macros.
src/http.ts — full Streamable HTTP MCP transport for the same server created by createOscMcpServer(). src/openai-remote.ts remains as a compatibility wrapper for the older start:openai script.
PROTOCOL.md — logical path mapping notes for X32/M32 and XAir/XR-compatible addresses.
test-connection.js — protocol-aware smoke test used by npm test.
test-llm-tools.js — LLM tool-selection benchmark that feeds natural-language commands plus the agent prompt to a model and verifies the expected MCP tool names/arguments. It uses mocked tool results for relative commands and never connects to the mixer.
- MCP framework:
@modelcontextprotocol/sdk - OSC transport:
osc-jsDatagramPluginover UDP - HTTP bridge dependencies:
expressandcors - Language/tooling: TypeScript, Node 18+
- Transactional writes: dedicated readable write tools write the value, read the same OSC address back, and verify the result
- Offline detection: if the write-back read times out, write tools return
Le mixeur est deconnecté - Reply handling: stores one pending callback per OSC address and times out reads after 1 second
Tools do not appear in the MCP client
- Confirm
npm run buildhas produceddist/index.js. - Check that the MCP config uses an absolute path to
dist/index.js. - Validate the JSON config and fully restart the MCP client.
- Check client logs. Claude Desktop logs are typically in
~/Library/Logs/Claude/on macOS and%APPDATA%\Claude\logs\on Windows.
Timeout waiting for response
- Verify the mixer IP: on X32/M32, press
SETUPand checkNetwork. - Test network reachability with
ping YOUR_MIXER_IP. - Confirm the mixer and computer are on the same network.
- Confirm OSC is enabled on the mixer.
- Check that UDP traffic to
OSC_PORTis not blocked by a firewall. - Run
npm testagainst the sameOSC_HOST,OSC_PORT, andOSC_PROTOCOL.
Command appears to run but the mixer does not change
- Recheck
OSC_PROTOCOL; X32/M32 should normally useOSCX32M32. - For raw commands, verify the OSC address spelling and zero-padding rules.
- For strict int addresses, send
osctype: "int"instead of relying on JSON type inference. - In
OSCXRmode, read the returned error. Unmapped X32-only operations should reportUnsupported for OSCXR: ....
- Patrick-Gilles Maillot's unofficial X32 OSC protocol PDF — the closest thing to an authoritative address reference. Verify against live hardware before trusting any address; some paths in the doc don't exist on current firmware.
- Upstream: anteriovieira/osc-mcp-server
MIT (inherited from upstream).