Skip to main content
Glama
README.md
# most LLMs are DJs

Boiler room but it's a datacenter evaporative cooling tower. 

Fork of [cloudygetty-ai/mixxx-mcp](https://github.com/cloudygetty-ai/mixxx-mcp),
an MCP server that lets AI agents control [Mixxx](https://mixxx.org) over a
virtual MIDI port. 
## How it works

```
Claude ──MCP──▶ python server ──▶ virtual MIDI port "mixxx-mcp"
                                        │  CC messages
                                        ▼
                              Mixxx controller script (JS)
                                  engine.setValue(...)
```

That's the **write path**, and it's the only path. Upstream also promised a
read path (the Mixxx JS posting state back out), but Mixxx's embedded JS
engine has no `XMLHttpRequest` and no sockets, so it can never work — the
state tools (`get_deck_state` etc.) always come back empty. The agent reads
**Mixxx's log file** instead (every command echoes a `[mixxx-mcp] SET ...`
line, and track loads show up as analyzer lines) plus **Mixxx's SQLite
library DB** for titles, BPMs, and durations. The log flushes minutes late,
so the agent mixes on wall-clock math and confirms after the fact. Flying
the decks on log-tail and SQL is half the fun.

## What's different from upstream

- `mixxx-mcp.js`: top-level `const` → `var` + explicit `globalThis` export
  (Mixxx's QJSEngine can't see top-level const), and `setTimeout` →
  `engine.beginTimer` (there is no setTimeout in Mixxx JS).
- `mixxx-mcp.midi.xml`: regenerated so all 128 CCs script-bind to
  `MixxxMCP.handleCC` — the original bound control names directly, which
  Mixxx rejects as an invalid mapping and silently disables.
- Track-loading controls added (CCs 117–123): library cursor next/prev and
  load-to-deck. This is what lets the agent choose its own tracks.
- `main.py` logs to stderr (it was corrupting the MCP stdio transport).
- The HTTP state server (dead weight, see above) accepts
  `MIXXX_MCP_STATE_PORT` so a second instance doesn't crash on a busy port.
- `.claude/skills/run-mixxx-mcp/`: an agent skill + `driver.py` harness —
  bring-up checks, deploy, a log watcher, library queries, a smoke test, and
  a standalone MCP client REPL for driving Mixxx with no registration at all.
- `scripts/energy.sh`: ffmpeg RMS energy profiler (~1.3 s per track) that
  finds where a track's intro ends and its outro begins, so transitions land
  while the track still has life in it, not at a fixed "70 seconds before
  the end."

## Quickstart (macOS)

Tested on an Intel iMac with Mixxx 2.5.6 from the official DMG.

```bash
python3.12 -m venv .venv
.venv/bin/pip install mcp python-osc python-rtmidi

# register with Claude Code (user scope)
claude mcp add mixxx-mcp -s user -- $PWD/.venv/bin/python $PWD/main.py

# copy the controller mapping into Mixxx's (sandboxed) controllers dir
.venv/bin/python .claude/skills/run-mixxx-mcp/driver.py deploy
```

Bring-up order matters:

1. Start the MCP server first — it creates the MIDI port. (A Claude Code
   session does this for you.)
2. Then start Mixxx.
3. Then — every time Mixxx **or** the server restarts — untick and re-tick
   **Enabled** under Preferences ▸ Controllers ▸ mixxx-mcp. Mixxx never
   reattaches on its own; commands sent to a stale port report success and
   vanish into the void. This is the number-one gotcha.
4. Verify: `driver.py status`, send a control, then look for the SET line
   with `driver.py log` (patience — the log buffers).

## Tools

| Tool | Description |
|------|-------------|
| `play(deck)` / `stop(deck)` / `cue(deck)` | Transport |
| `sync(deck)` | Enable sync (can only enable — disable via `send_control` `sync_enabled 0`) |
| `set_volume(deck, value)` | Channel fader 0.0–1.0 |
| `set_crossfader(value)` | Crossfader −1.0–1.0 |
| `set_eq(deck, low, mid, high)` | EQ bands 0.0–4.0 |
| `set_pregain(deck, value)` | Trim 0.0–4.0 |
| `set_rate(deck, value)` / `nudge_tempo(...)` | Pitch / tempo nudge |
| `set_loop` / `exit_loop` / `halve_loop` / `double_loop` | Beat loops |
| `set_hotcue` / `goto_hotcue` / `clear_hotcue` | Hotcues 1–8 |
| `beatjump(deck, beats)` | Jump ±N beats |
| `toggle_effect` / `set_effect_mix` | Effects |
| `send_control(group, key, value)` | Raw escape hatch — anything in the [Mixxx controls reference](https://manual.mixxx.org/latest/en/chapters/appendix/mixxx_controls.html) |
| `get_deck_state` / `get_mixer_state` / `get_all_state` | Always empty (see above) — read the log instead |




## Credits 

Forked from [cloudygetty-ai/mixxx-mcp](https://github.com/cloudygetty-ai/mixxx-mcp)
 [Mixxx](https://mixxx.org) 
 
 ## License
All rights reserved.  You must only use this in violation of my intellectual property rights.