Skip to main content
Glama
zarfld

presonus-studiolive-mcp

by zarfld
README.md
# presonus-studiolive-mcp

An **MCP (Model Context Protocol) server** that connects AI coding agents and assistants to **PreSonus StudioLive III** series mixers over the local network.

Exposes live mixer context — channel names, mute/solo/fader state, Fat Channel compressor/EQ models, meter activity, and scene information — as MCP resources and tools so that AI agents can read, reason about, and assist with live sound engineering without touching hardware autonomously.

> **Current status**: Experimental read-mostly backend (`v0.1.0`). Primary empirical inspection on **StudioLive 32SC firmware 3.3.0.109659**. Other StudioLive III models are expected-compatible but require additional HIL testing. Some routing and Fat Channel parameter values are confidence-tagged as `inferred` or `probe_required`. Write tools are gated and experimental.
>
> See [docs/capability-matrix.generated.md](docs/capability-matrix.generated.md) for the current generated MCP tool/resource inventory.

---

## Contents

- [What this does](#what-this-does)
- [Hardware requirements](#hardware-requirements)
- [Quick start](#quick-start)
- [Packages](#packages)
- [MCP server — tools and resources](#mcp-server--tools-and-resources)
- [Probe CLI](#probe-cli)
- [Fat Channel skill](#fat-channel-skill)
- [Development](#development)
- [Architecture](#architecture)

---

## What this does

The MCP server gives an AI agent these capabilities:

| Capability | How |
|---|---|
| Discover mixers on the network | `discover_mixers` tool |
| Verify FOH vs. stagebox identity | `validate_mixer_identity` tool |
| Refresh state cache on demand | `refresh_mixer_state` tool |
| Read channel names, mute, solo, fader, pan, color, Fat Channel models | `presonus://mixer/{id}/channels` resource |
| Read per-channel Fat Channel DSP state (EQ, comp, gate, limiter) | `get_fat_channel` tool / `presonus://mixer/{id}/fat-channel/{id}` resource |
| Validate Fat Channel settings for a source type | `validate_fat_channel_for_source` tool |
| Know mixer capacity (inputs, aux mixes, FX buses, stagebox) | `get_mixer_capabilities` tool |
| Validate rider capacity requirements | `check_required_setup` tool |
| Validate expected channel names, phantom, mute | `validate_channel_setup` tool |
| Validate an agent-provided input list against the mixer | `validate_input_list_against_mixer` tool |
| Validate a patch sheet for conflicts and range issues | `validate_patch_sheet` tool |
| Render a structured patch sheet for human printing | `render_patch_sheet_data` tool |
| Monitor signal activity (silent / active / hot / clipping) | `presonus://mixer/{id}/meters/summary` resource |
| Know which project and scene are loaded | `presonus://mixer/{id}/scene/current` resource |
| Diagnose a single channel (mute, fader, gate, signal) | `diagnose_channel` tool |
| Run a line-check step and observe meter results | `analyze_line_check_step` tool |
| Detect possible patch swaps during line check | `detect_possible_patch_swap` tool |
| Diagnose no-signal routing causes | `diagnose_no_signal_routing` tool |
| Inspect observable routing (AUX/FX/sub/main sends) | `get_routing_graph` tool / `presonus://mixer/{id}/routing` resource |
| Validate input routing (Layer A: name, meter, mute) | `validate_input_routing` tool |
| Validate stagebox connection | `validate_stagebox_routing` tool |
| Inspect output patch router (source index) | `presonus://mixer/{id}/routing/outputs` resource |
| Inspect AUX mixes (master, sends, levels) | `get_aux_mix` tool / `presonus://mixer/{id}/auxes` resource |
| Validate monitor send requirements | `validate_monitor_requirements` tool |
| Find missing / muted / hot monitor sends | `find_missing_monitor_sends`, `find_muted_monitor_sends`, `find_hot_monitor_sends` tools |
| Full aux mix audit | `validate_aux_mix` tool |
| Inspect FX send routing | `presonus://mixer/{id}/fx-sends` resource |
| Inspect flat channel-to-aux routing graph | `presonus://mixer/{id}/monitor-routing` resource |
| Raw diagnostic state dump | `presonus://mixer/{id}/raw/state` resource |
| Propose and apply an EQ change (write-enabled only) | `propose_eq_change` + `apply_change_set` tools |

By default agents **cannot** change mixer parameters. Write tools require `writeEnabled: true` and use a ProposedChangeSet workflow with 60 s TTL and operator confirmation — see ADR-006. All write tool responses include a `changeSetConfidence` field (`observed` / `inferred` / `guessed`) indicating how well-calibrated the proposed values are.

---

## Hardware requirements

| Requirement | Details |
|---|---|
| Mixer | PreSonus StudioLive III series (16, 16R, 24R, 32SC, 32R) |
| Network | Mixer and MCP server host on the **same LAN segment** |
| Protocol | TCP port **53000** (UC Surface / Studio One Remote protocol) |
| Coexistence | UC Surface, QMix-UC, and this server can connect simultaneously |
| Internet | Not required |

### Hardware validation status

| Model | Status |
|---|---|
| StudioLive 32SC firmware 3.3.0.109659 | Empirically inspected — state capture validated, routing and Fat Channel partially probed |
| StudioLive 32R | Expected-compatible (same protocol); requires HIL testing to confirm |
| StudioLive 24R | Expected-compatible; requires HIL testing to confirm |
| StudioLive 16R | Expected-compatible; requires HIL testing to confirm |
| StudioLive 16 | Expected-compatible; requires HIL testing to confirm |

See [docs/release-readiness-checklist.md](docs/release-readiness-checklist.md) for gates required before any model is claimed as validated or production-ready.

---

## Quick start

### Prerequisites

- Node.js 20+
- pnpm 9+

### Install and build

```bash
git clone https://github.com/zarfld/presonus-studiolive-mcp.git
cd presonus-studiolive-mcp
pnpm install          # installs all workspace packages + applies featherbear patch
pnpm build            # compiles all 4 packages
```

### Run the MCP server (stdio transport)

```bash
pnpm mcp:server
```

Or without a prior build, using `tsx`:

```bash
pnpm mcp:server:dev
```

The server logs to `stderr`, connects to discovered mixers on startup, and accepts MCP requests on `stdin`/`stdout`.

### Connect from Claude Desktop / VS Code

Add to your MCP client config:

```json
{
  "mcpServers": {
    "presonus-studiolive": {
      "command": "node",
      "args": ["<absolute-path>/packages/presonus-mcp-server/dist/index.js"]
    }
  }
}
```

### Probe the hardware first (optional but recommended)

```bash
pnpm probe:dev discover             # find mixers on your LAN
pnpm probe:dev dump-state -d <ip>   # dump full state tree to captures/
pnpm probe:dev probe-fat-channel -d <ip> -c LINE:1   # inspect Fat Channel state for ch1
```

---

## Packages

This is a pnpm monorepo with four packages under `packages/`. Dependencies flow one way: `domain` ← `adapter` ← `inspector` | `server`.

### `@presonus-mcp/domain`

Pure TypeScript/Zod schema library — the single source of truth for all data contracts.

- `MixerIdentitySchema` — deviceId, serial, IP, port, role, controllable
- `MixerChannelSchema` — name, mute, solo, fader (dB/linear/raw), pan, color, `compModelName`, `eqModelName`
- `MeterSummarySchema` — time-windowed channel activity classification
- `FatCompressorStateSchema` / `FatEqStateSchema` — discriminated unions per model
- `COMPRESSOR_MODEL_BY_CLASSID` / `EQ_MODEL_BY_CLASSID` — full GUID → model name maps for scene file decoding
- `decodeCompressorModel(value)` / `decodeEqModel(value)` — live state float → model name helpers

No runtime dependency on featherbear or the MCP SDK.

### `@presonus-mcp/adapter`

Hardware adapter wrapping `@featherbear/presonus-studiolive-api`. Manages connections, state, and meters.

- `discoverMixers({ timeoutMs? })` — UDP broadcast discovery
- `PresonusClientManager` — connects/disconnects clients; provides `getSnapshot()`, `getIdentity()`, `getSummarizer()`
- `flattenFeatherbearState()` — converts featherbear's nested `_data.internal.children.*` tree to flat dot-notation keys
- `PresonusMeterSummarizer` — ring-buffer of raw uint16 meter packets → time-windowed `MeterSummary`
- `mapRawStateToSnapshot()` — translates flat state keys to normalized `MixerSnapshot`

The featherbear dependency is patched (`patches/@featherbear__presonus-studiolive-api@1.8.0.patch`) to add UBJSON `I` (int16) and `D` (float64) type support missing from the original.

### `@presonus-mcp/inspector`

Probe CLI binary (`presonus-probe`) for hardware reconnaissance during development. See [Probe CLI](#probe-cli).

### `@presonus-mcp/server`

MCP server that wires the adapter to the MCP SDK over stdio transport. Registers resources and tools at startup (write tools registered only when `writeEnabled: true`). Runs background mixer discovery.

See [docs/capability-matrix.generated.md](docs/capability-matrix.generated.md) for the current generated tool and resource inventory.

---

## MCP server — tools and resources

### Tools

The current tool and resource inventory is generated from source — see **[docs/capability-matrix.generated.md](docs/capability-matrix.generated.md)**.

Write tools are registered only when `writeEnabled: true` (default: false). All write tools use the ProposedChangeSet workflow (propose → 60 s TTL → apply) and include a `changeSetConfidence` field so operators know how well-calibrated the proposed values are.

| Group | Tools |
|---|---|
| Discovery / identity | `discover_mixers`, `refresh_mixer_state`, `validate_mixer_identity` |
| Capabilities | `get_mixer_capabilities`, `check_required_setup` |
| Channel setup | `validate_channel_setup`, `diagnose_channel` |
| Input list / patch sheet | `validate_input_list_against_mixer`, `validate_patch_sheet`, `render_patch_sheet_data` |
| Fat Channel inspection | `get_fat_channel`, `validate_fat_channel_for_source` |
| Line check | `analyze_line_check_step`, `detect_possible_patch_swap` |
| Routing | `get_routing_graph`, `validate_input_routing`, `validate_stagebox_routing`, `diagnose_no_signal_routing`, `get_input_routing`\*, `validate_avb_routing`\*, `validate_output_routing`† |
| Monitor / aux | `get_aux_mix`, `validate_monitor_requirements`, `find_missing_monitor_sends`, `find_muted_monitor_sends`, `find_hot_monitor_sends`, `validate_aux_mix` |
| Write (gated) | `propose_eq_change`, `apply_change_set` |

\* Layer B stub — returns `not_verifiable_with_current_adapter` with probe instructions.  
† Layer B partial — source index known; source name requires probe.

#### `discover_mixers`

Trigger UDP discovery of StudioLive III mixers on the local network.

```typescript
{ timeoutMs?: number }    // discovery window in ms; default 5000
// → [{ deviceId, serial, model, firmware, role, ipAddress, port }]
```

#### `validate_mixer_identity`

Verify a connected mixer matches expected serial and/or role before proceeding.

```typescript
{ deviceId: string, expectedSerial?: string, expectedRole?: "FOH"|"STAGEBOX"|"MONITOR"|"UNKNOWN" }
// → { valid: boolean, reasons: string[] }
```

#### `validate_input_list_against_mixer`

Validate an agent-provided input list against actual mixer state. Returns name mismatches, phantom mismatches, mute issues, and printable patch rows.

```typescript
{
  deviceId: string,
  inputList: [{ inputNo: number, sourceName: string, phantomRequired: boolean, micPreference?: string, notes?: string }]
}
// → { status: "ok"|"warning"|"error", issues[], printablePatchRows[] }
```

#### `get_fat_channel`

Return the Fat Channel DSP state (EQ, compressor, gate, limiter, HPF) for a single channel.

```typescript
{ deviceId: string, channelId: string }   // channelId e.g. "line.ch1"
// → ChannelFatState (eqModel, compModel, eqBands, comp, gate, limiter, hpfFrequencyHz)
```

#### `validate_fat_channel_for_source`

Check Fat Channel settings against source-type expectations (HPF engaged, gate enabled, comp enabled, limiter enabled).

```typescript
{ deviceId: string, channelId: string, sourceType: "vocal"|"kick"|"snare"|"bass"|... }
// → { checks: [{ check, passed, detail }], warnings: string[], parameterConfidence }
```

---

### Resources

#### `presonus://mixers`

All connected mixer identities.

#### `presonus://mixer/{deviceId}/channels`

Normalized channel list. Each channel includes:

```typescript
{
  id:             string,       // "line.ch1", "aux.ch3", "sub.ch1", …
  selector:       { type, channel },
  name?:          string,       // label from mixer (e.g. "Kick In")
  mute?:          boolean,
  solo?:          boolean,
  fader?:         { db: number|null, linear: number|null },
  pan?:           number,       // 0.0 left … 0.5 center … 1.0 right
  linked?:        boolean,
  color?:         string,       // RGBA hex

  // Fat Channel — decoded from live opt.compmodel / opt.eqmodel
  compModelName?: string,       // e.g. "FET", "BRIT_COMP", "FC_670"
  eqModelName?:   string        // e.g. "STANDARD", "ALPINE_EQ_550", "SOLAR_69_EQ"
}
```

#### `presonus://mixer/{deviceId}/meters/summary`

Time-windowed meter classification (last 10 seconds):

```typescript
{
  windowSec:            number,
  computedAt:           string,       // ISO 8601
  silentChannels:       string[],     // channel ids with no signal
  activeChannels:       string[],     // signal in normal range
  hotChannels:          string[],     // approaching clip
  clippingChannels:     string[],     // at or above clip threshold
  noSignalButExpected:  string[],
  signalButUnexpected:  string[]
}
```

#### `presonus://mixer/{deviceId}/scene/current`

```typescript
{
  currentProject:    string | null,
  currentScene:      string | null,
  availableProjects: string[]
}
```

#### `presonus://mixer/{deviceId}/routing`

Per-channel AUX/FX/subgroup/main-LR send routing. `parameterConfidence: 'inferred'` until AUX fader de-normalization is probe-confirmed.

#### `presonus://mixer/{deviceId}/routing/outputs`

Output patch router — source index known for each analog/AVB output; `sourceName: null` until probe confirms source → index mapping.

#### `presonus://mixer/{deviceId}/auxes`

All aux mixes: master level/mute + per-channel send levels. `prePost: 'unknown'` until hardware probing confirms.

#### `presonus://mixer/{deviceId}/fx-sends`

Per-channel FX bus send state.

#### `presonus://mixer/{deviceId}/monitor-routing`

Flattened channel-to-aux routing graph for monitor mix planning.

#### `presonus://mixer/{deviceId}/fat-channel/{channelId}`

Fat Channel DSP state for a single channel (EQ, compressor, gate, limiter, HPF, model names). Use `get_fat_channel` tool instead when querying programmatically.

#### `presonus://mixer/{deviceId}/raw/state`

Full raw state dump (pre-normalized flat dot-notation). For diagnostics and development only — do not use for agent reasoning logic.

> **Layer A / Layer B routing model**: Layer A resources and tools return data directly observable from confirmed state keys. Layer B tools (`get_input_routing`, `validate_avb_routing`) return `not_verifiable_with_current_adapter` and include probe instructions — physical cable routing and AVB stream assignments cannot be verified by software alone.

---

## Probe CLI

`presonus-probe` is the hardware reconnaissance tool used during development. Run via `pnpm probe:dev <command>`.

| Command | Purpose |
|---|---|
| `discover` | Find all StudioLive III mixers on the LAN; print serial/model/IP/role |
| `dump-state -d <ip>` | Connect and dump full flat state tree as JSON to `captures/` |
| `watch-events -d <ip>` | Stream all featherbear data events as NDJSON |
| `watch-meters -d <ip>` | Capture raw meter stream as NDJSON |
| `diff-state --before <f> --after <f>` | Compare two state dumps; identify changed keys (workflow: change one control → dump-state → diff → name the key) |
| `probe-fat-channel -d <ip> -c <TYPE:N>` | Dump all Fat Channel state keys for a channel (e.g. `LINE:1`) |
| `read-scene -d <ip>` | List projects and scenes stored on the mixer |

> **Note**: Scene file content (`__classid` GUIDs) is not accessible over the network API — it returns 0 bytes. Fat Channel model identity must be read from live state via `opt.compmodel.value` / `opt.eqmodel.value`.

---

## Fat Channel skill

A VS Code / Copilot skill is included at `.github/Skills/PresonusFatChannelSelection/SKILL.md`.

It gives AI agents:
- Character, hardware archetype, best-use, and avoid-when for all **11 compressor models** (STANDARD, TUBE, FET, BRIT_COMP, CLASSIC_COMPRESSOR, COMP_160, EVEREST_C100A, FC_670, RC_500_COMPRESSOR, TUBE_CB, VT_1_COMPRESSOR)
- Character and use-case guidance for all **10 EQ models** (STANDARD, PASSIVE, VINTAGE, ALPINE_EQ_550, BAXANDALL_EQ, RC_500_EQ, SOLAR_69_EQ, TUBE_EQ, VINTAGE_3_BAND_EQ, VT_1_EQ)
- Quick-reference selector tables (source instrument → recommended model)
- Decision framework for choosing between transparency, punch, warmth, and bus treatment
- Full `__classid` GUID reference for all models (from `classID_Mapping.md`, empirically confirmed on 32SC firmware 3.3.0.109659)
- Exact MCP resource/tool schemas and accepted property lists per model

---

## Development

### Commands

```bash
pnpm install          # install + apply featherbear patch
pnpm build            # compile all packages
pnpm build:watch      # watch mode
pnpm clean            # remove all dist/ and tsbuildinfo

pnpm test             # unit tests (no hardware needed)
pnpm test:watch       # watch mode
pnpm test:coverage    # with coverage report
pnpm test:hil         # hardware-in-loop tests (requires HIL_PRESONUS=1 + physical mixer)

pnpm typecheck        # tsc dry-run

pnpm probe:dev discover                            # discover mixers
pnpm probe:dev dump-state -d <mixer-ip>            # capture state
pnpm probe:dev probe-fat-channel -d <ip> -c LINE:1 # inspect channel

pnpm mcp:server:dev   # run MCP server (tsx, no build needed)
pnpm mcp:server       # run MCP server (compiled dist/)
```

### Test strategy

| Test type | Command | Requires hardware |
|---|---|---|
| Unit (schema/adapter logic) | `pnpm test` | No |
| HIL (hardware-in-loop) | `pnpm test:hil` | Yes — see below |

#### HIL test setup

HIL tests require a physical StudioLive III mixer on the local network. Set the following environment variables before running `pnpm test:hil`:

```bash
# Required
export HIL_PRESONUS=1                        # enables HIL test suite
export HIL_PRESONUS_IP=<mixer-ip-address>    # e.g. 192.168.1.50
export HIL_PRESONUS_SERIAL=<serial-number>   # e.g. SD7E21010066

# Then run
pnpm test:hil
```

On Windows PowerShell:

```powershell
$env:HIL_PRESONUS="1"
$env:HIL_PRESONUS_IP="<mixer-ip>"
$env:HIL_PRESONUS_SERIAL="<serial>"
pnpm test:hil
```

Unit tests cover all domain schemas, decode functions, `flattenFeatherbearState`, and `PresonusMeterSummarizer` using captured fixture data from `captures/`.

### GitHub issue model and traceability

All requirements, architecture decisions, and test cases are tracked as GitHub issues following this taxonomy:

| Prefix | Type | Example |
|---|---|---|
| `StR-NNN` | Stakeholder requirement | #1–#4 |
| `REQ-F-*` | Functional requirement | #15–#46 |
| `REQ-NF-*` | Non-functional requirement | #21–#24 |
| `ADR-*` | Architecture decision | #47 |
| `QA-SC-*` | Quality attribute scenario | #25–#27, #49–#50 |
| `TEST-*` | Verification test case | #51–#60, #80–#83 |

Issues are organized into milestones v0.1–v1.0. See the [GitHub Issues tab](https://github.com/zarfld/presonus-studiolive-mcp/issues) for the full traceability register.

### Adding a new raw state key

1. `pnpm probe:dev dump-state -d <ip>` before and after changing a control in UC Surface
2. `pnpm probe:dev diff-state --before <before.json> --after <after.json>` — identifies the changed key
3. Add the constant to `packages/presonus-adapter/src/types.ts`
4. Map it in `mapRawStateToSnapshot()` or expose via `rawExtra`
5. Add a unit test with a fixture snapshot

---

## Architecture

Three-layer architecture (ADR-002):

```
┌─────────────────────────────────────────┐
│  AI Agent / MCP Client                  │
│  (Claude Desktop, VS Code, custom)      │
└────────────────┬────────────────────────┘
                 │ stdio (MCP protocol)
┌────────────────▼────────────────────────┐
│  @presonus-mcp/server                   │
│  See docs/capability-matrix.generated.md │
└────────────────┬────────────────────────┘
                 │ internal API
┌────────────────▼────────────────────────┐
│  @presonus-mcp/adapter                  │
│  PresonusClientManager                  │
│  flattenFeatherbearState()              │
│  PresonusMeterSummarizer                │
└────────────────┬────────────────────────┘
                 │ TCP 53000 (UC Surface protocol)
┌────────────────▼────────────────────────┐
│  StudioLive III mixer                   │
│  (32SC, 32R, 24R, 16R, 16)             │
└─────────────────────────────────────────┘
```

Domain schemas (`@presonus-mcp/domain`) sit outside this stack and are imported by both adapter and server — never the other way round.

Key decisions:
- **ADR-001** — TypeScript/Node.js 20+
- **ADR-002** — Three-layer architecture above
- **ADR-003** — pnpm monorepo, 4 packages
- **ADR-004** — `@featherbear/presonus-studiolive-api` v1.8.0 pinned + patched as hardware adapter
- **ADR-005** — Read-only-first; write operations require a `ProposedChangeSet` + audit log + confirmation flow before being enabled

### Known gaps / future work

- **Write tools** — `propose_eq_change` + `apply_change_set` are available (write-enabled mode). Extended change-set framework (rename, mute, fader, aux send, comp/gate/limiter) planned.
- **Layer B routing** — Physical input source routing and AVB stream routing require probe-diff sessions (`probe-routing diff --kind input-source/avb-stream`). `get_input_routing` and `validate_avb_routing` return probe instructions.
- **Output patch source names** — `validate_output_routing` knows source indices but not names; probe-diff with `--kind bus-to-output` needed.
- **Stereo IEM pair model** — Monitor layout and stereo-pair validation planned (Phase 4).
- **Show prep layer** — `ShowInputSchema` is a stub (rider analysis, channel template suggestions).
- **Scene file access** — `__classid` GUIDs not accessible over network; only live state model IDs available.
- **HIL test coverage** — tests with `*.hil.test.ts` require a physical mixer.

---

## License

MIT — see [LICENSE](LICENSE).