bigskymx-mcp
by levinelson
README.md
# bigskymx-mcp
Control a Strymon BigSky MX over USB MIDI, from a command line or from Claude.
## Getting started
You need:
- [Nixie 2](https://www.strymon.net/support/nixie-2/), Strymon's free editor
- Python 3.10 or newer
- git
Developed and tested on macOS. It should work anywhere `python-rtmidi` does; on Linux that means ALSA development headers if pip has to build it from source.
### 1. Connect the pedal to your computer
### 2. Open Nixie 2 and leave it running
A freshly connected pedal sends no MIDI until an editor session exists. Nixie does not interfere with this tool.
### 3. Install
```bash
git clone https://github.com/levinelson/bigskymx-mcp.git
cd bigskymx-mcp
python3 -m venv .venv
.venv/bin/pip install -e ".[mcp]"
```
Drop `[mcp]` if you only want the command line. This puts `bigsky` in `.venv/bin`, not on your PATH; `uv tool install .` or `pipx install .` gets you a plain `bigsky` anywhere. Examples below use the bare name.
### 4. Check it works
```bash
.venv/bin/bigsky ports # BigSkyMX should be listed and auto-detected
.venv/bin/bigsky state # prints the loaded preset and both engines
```
| Symptom | Cause or fix |
| ----------------------------------------------------- | ----------------------------- |
| `ports` does not list `BigSkyMX` | cable or connection |
| `ports` finds it, `state` says it is not broadcasting | Nixie 2 is not open |
| the stream stops after working | `bigsky preset 0` restarts it |
| still stopped with Nixie open | quit Nixie |
## Commands
### Everyday
```bash
bigsky ports # list MIDI ports, show auto-detect
bigsky state # read the loaded preset
bigsky state --config # read global settings
bigsky param TYPE NONLINEAR --engine 1 # switch reverb machine
bigsky param SHAPE BOUNCE --engine 1 # machine-specific, by name
bigsky param Decay 100 --engine 1 # 0-127, scaled to the full range
bigsky param DUAL "SERIES 1 >> 2" # routing
bigsky param "INF MODE" INFINITE --engine 1
bigsky preset 42 # recall a slot, 0-299
bigsky load "presets/PAD.json" # restore a sound to the edit buffer
bigsky backup out/ # every slot to JSON plus a .syx
bigsky machines # every machine and its parameters
bigsky monitor # print incoming MIDI
```
Choice parameters take names, not numbers. `--channel` defaults to `auto` and reads the channel off the pedal.
### Offline, no pedal needed
| Command | Does |
| ----------------------------- | --------------------------- |
| `bigsky show FILE.syx` | decode a preset bank |
| `bigsky export FILE.syx DIR/` | bank to editable JSON files |
| `bigsky build DIR/ FILE.syx` | JSON files back to a bank |
| `bigsky diff A.syx B.syx` | byte-diff two banks |
### Working on the protocol
| Command | Does |
| --------------------- | ------------------------------------------------------ |
| `bigsky probe` | sweep the SysEx opcode space (needs `--yes`) |
| `bigsky dump` | send one SysEx request, save the reply (needs `--yes`) |
| `bigsky discover-cc` | rebuild the CC map by watching the pedal |
| `bigsky verify-types` | walk every reverb machine |
| `bigsky emulate` | impersonate the pedal to capture what Nixie sends |
| `bigsky proxy` | sit between Nixie and the pedal |
`scripts/full_review.py` sets every parameter of every machine on both engines and checks the pedal's report. It passes 299/303; the four failures are `INF MIX`, which has no CC.
## Use with Claude
```bash
claude mcp add bigsky -- /full/path/to/.venv/bin/bigsky-mcp
```
Claude Desktop, in `claude_desktop_config.json`:
```json
{
"mcpServers": {
"bigsky": {
"command": "/full/path/to/.venv/bin/bigsky-mcp"
}
}
}
```
The path must be absolute. The client launches the server and does not inherit your shell.
| Tool | Does | Read-only | Destructive |
| -------------------------- | --------------------------------------------- | --------- | --------------------------- |
| `bigsky_get_state` | read the loaded preset and both engines | yes | no |
| `bigsky_list_machines` | the 12 reverb machines | yes | no |
| `bigsky_describe_machine` | one machine's parameters, ranges and choices | yes | no |
| `bigsky_set_params` | set any number of parameters on either engine | no | no |
| `bigsky_recall_preset` | recall a slot, 0-299 | no | yes, discards unsaved edits |
| `bigsky_list_preset_files` | saved sound files | yes | no |
| `bigsky_load_preset_file` | restore one to the edit buffer | no | yes, discards unsaved edits |
| `bigsky_save_preset_file` | write the current sound to a file | no | yes, can replace a file |
`bigsky_set_params` takes a whole change at once and applies it in the order the pedal requires:
1. Shared globals, because `DUAL` and `PERSIST` reload the preset
2. The machine type
3. That machine's own parameters
4. `OUTPUT LEVEL`, `PAN` and `INF MODE`, which survive a type change
5. The continuous knobs
`bigsky_save_preset_file` refuses to replace an existing file unless asked twice.
Set `BIGSKY_PRESETS` in the server's `env` to keep sounds somewhere other than `presets/`.
## What it cannot do
- **Store to a numbered slot.** I haven't found a MIDI message that does this. Dial a sound in and then save it manually on the pedal or in Nixie.
- **Set `INF MIX`.** It is in Strymon's schema and in no CC table.
- **Assign PARAM 1 and PARAM 2.** CC 19-22 drive whatever is already assigned; nothing addresses the assignment. Presets store it as a name, so it looks settable. `load` lists these separately from real failures.
## The CC map
Transcribed in `cc.py` from the manual (Rev B, pages 70-76). **Scaled** means CC 0-127 spans a wider internal range; **direct** means the CC value is the value and clamps at the maximum.
| Parameter | Engine 1 | Engine 2 | Range | Kind |
| -------------------- | -------- | -------- | ----- | -------------------------------- |
| Type | 1 | 2 | 0-11 | direct, machine index |
| Decay | 3 | 4 | 0-127 | scaled, ms internally |
| Pre-Delay | 5 | 6 | 0-127 | scaled, ms internally |
| Output Level | 7 | 8 | 0-16 | direct |
| Pan | 9 | 10 | 0-16 | direct |
| Tone | 11 | 12 | 0-127 | scaled to 0-255 |
| Mod | 13 | 14 | 0-127 | scaled to 0-255 |
| Mix | 15 | 16 | 0-127 | scaled to 0-255 |
| INF MODE | 17 | 18 | 0-2 | direct, freeze / infinite / off |
| Param1 | 19 | 20 | 0-127 | scaled, drives the assigned knob |
| Param2 | 21 | 22 | 0-127 | scaled, drives the assigned knob |
| Low End | 23 | 24 | 0-20 | direct |
| MIDI Clock (Magneto) | 31 | 32 | 0-1 | direct |
Machine-specific parameters are in `cc.MACHINE_CC`.
| Shared control | CC | Takes |
| -------------- | --- | ------------------------------------------------------------------------------------- |
| Boost | 79 | 0-60 |
| Persist | 84 | `ON`, `OFF` |
| INF LATCH | 98 | `MOMENTARY`, `LATCHING` |
| DUAL | 99 | `OFF`, `PARALLEL`, `SERIES 1 >> 2`, `SERIES 1 << 2`, `SPLIT 1L * 2R`, `SPLIT 1R * 2L` |
| Hardware control | CC | Notes |
| ------------------- | ------- | ----------------------------- |
| Bank up / down | 80 / 81 | also the A and B footswitches |
| Infinite footswitch | 82 | acts as the physical switch |
| **Value encoder** | **83** | **changes the loaded preset** |
| Infinite on / off | 97 | |
| Expression | 100 | |
| Tap | 101 | |
| Bypass | 102 | |
Gotcha: sweeping CCs blindly hits 83 and silently swaps the preset out from under whatever you are testing.
Two corrections to the manual, both confirmed on hardware:
- **Persist is inverted.** The manual says 0 = Off. The pedal reports CC 84 = 0 as ON.
- **Shimmer Shift 2 is CC 73/74**, not the printed 72/73, which collides with Shift 1.
## The frame format
Presets and state are UTF-8 JSON inside SysEx.
```
F0 00 01 55 18 01 39 00 01 01 01 00 00 LL LL {"...json..."} F7
|________| | | | | | | |___|
Strymon | | | | | | total length, 7-bit big endian
mfr ID | | | | | payload type: 01=state 02=config 06=preset
| | | | verb: 01=data 02=request 4A=select a slot
| | | 7F on a preset, 00 on state/config
| | opcode 0x39 = data (0x43 = firmware update; avoid)
| device id 0x01
product 0x18 = BigSky MX
```
Byte order is product, then device, then opcode.
**The JSON starts at byte 14, always.** Do not search for the first `{`: bytes 12-13 carry the length, and a low byte of `0x7B` is itself an ASCII `{`, so the search starts one byte early and the parse fails. This happens whenever the total length mod 128 is 123. Use `sysex.payload_json`.
**The two directions use different header lengths.** Pedal-to-host is 14 bytes before the JSON. Host-to-pedal is 10, with no length bytes: `00 01 55 18 01 39 00 01 01 01` then the JSON.
State frames can be partial, one parameter at a time:
```json
{ "state": { "channel": [{ "TYPE": "CLOUD", "LOW END": 10 }, {}] } }
```
## Opcodes
The pedal answers `0x46` to anything it does not implement.
| Opcode | Reply | What it is |
| ------------- | ------------- | ----------------------------------------------------------- |
| `0x21` | 14 bytes | Read ESN. Nixie's device probe. |
| `0x32` | `00 01 00 00` | Unidentified. Version or capability word. |
| `0x39` | _(silent)_ | Data. Silence means "send a payload". |
| `0x56` | `0x45` (ACK) | Accepted and executed, no front-panel change. Unidentified. |
| `0x00` | _(silent)_ | Recognised, wants a payload. |
| anything else | `0x46` | Not implemented. |
The gen-1 command set is gone. These all NAK:
| Opcode | Was |
| ------ | ----------------- |
| `0x62` | write patch |
| `0x63` | request patch |
| `0x64` | enter save mode |
| `0x22` | get current state |
Only `0x21` carried over. All four are documented for TimeLine, Mobius and gen-1 BigSky in Strymon's own librarian, [strymon/spl](https://github.com/strymon/spl), in `spl/DcMidiDevDefs.h`.
`0x4A` selects a slot. It is not a save, and Nixie sends the identical message when you click a preset in its list.
## Gotchas
- The pedal ignores every message on a MIDI channel other than its own, with no error. A wrong channel looks exactly like a working connection.
- Writes go over CC. Sending the pedal its own state frame back also works and reaches full internal resolution, but stops applying after a while and silently reverts. `live.set_param` still exposes it.
- A JSON state write ignores a value of `0`, leaving the old value.
- Machine type cannot be changed by a JSON write. It needs CC 1/2.
- `DUAL` and `PERSIST` make the pedal reload the preset, reverting edits made before them. Set globals first.
- The pedal echoes what you sent before applying its own limits, so verifying too quickly reports success for a value that then gets clamped.
- Reading the stream without draining returns a stale snapshot.
- Re-sending a program change for the current preset does not reload it.
- Decay and Pre-Delay are milliseconds on a non-linear per-machine curve, so the CC value is searched for rather than computed. Mix, Tone and Mod land within one CC step of the request.
## Safety
Firmware updates use opcode `0x43`. Everything here stays on `0x39`, so none of it can trigger a flash. `probe` and `dump` can send undocumented opcodes and are gated behind `--yes`.
Run `bigsky backup out/` before experimenting.
## Your own sounds
`presets/` ships empty and is gitignored. Anything you put there is picked up by `bigsky load` and by the MCP preset tools.
## Not affiliated with Strymon
This is independent interoperability work, not authorized or endorsed by Strymon. Strymon, BigSky and Nixie are trademarks of their owner. Everything here came from observing a pedal I own and from the published manual, except `bigsky/schema.json`, which was derived from the Nixie 2 binary.
Written by Claude Code.
## License
MIT. See [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues