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`). Verified on **StudioLive 32SC** (firmware 3.3.0.109659 and 3.4.0.111374) and **StudioLive 32R** (firmware 3.4.0.111374) — read-only baseline observed on both. Channel mute write is HIL-verified on 32SC only. Other StudioLive III models are unverified/experimental. Some routing and Fat Channel parameter values are confidence-tagged as `inferred` or `probe_required`. Write tools are disabled by default.
>
> 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 |
| Prepare and apply a channel mute change (write-enabled, 32SC only) | `prepare_mute_change_set` + `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 | `observed` — state capture validated, routing and Fat Channel partially probed |
| StudioLive 32SC firmware 3.4.0.111374 | `observed` — mute write HIL verified (T1–T9); fader taper and preamp gain probed |
| StudioLive 32R firmware 3.4.0.111374 | `observed` — discovery, TCP, 32-channel state, capabilities, AVB/AUX read, disconnect/reconnect lifecycle all confirmed (49/49 HIL smoke tests passed 2026-10-01) |
| StudioLive 24R | Unverified — no HIL evidence; may be protocol-compatible |
| StudioLive 16R | Unverified — no HIL evidence; may be protocol-compatible |
| StudioLive 16 | Unverified — no HIL evidence; may be protocol-compatible |
See [docs/release-readiness-checklist.md](docs/release-readiness-checklist.md) for gates required before any model is claimed as validated or production-ready.
### v0.1 capability matrix
| Capability | StudioLive 32SC | StudioLive 32R |
|---|---|---|
| Discovery / configured fallback | `observed` | `observed` |
| Serial-stable identity | `observed` | `observed` |
| TCP connection | `observed` | `observed` |
| State synchronization | `observed` | `observed` |
| Core channel-state read (mute, fader, name, pan) | `observed` | `observed` |
| Mixer capabilities | `observed` | `observed` |
| `refresh_mixer_state` lifecycle | `observed` | `observed` |
| Disconnect / reconnect lifecycle | `observed` | `observed` |
| AVB stream-routing read | `observed` | `observed` |
| AUX/send read | `observed` | `observed` |
| Output-patch source index | `observed` | `observed` |
| Output-patch source name mapping | `observed` | `inferred` — calibration not independently confirmed on 32R |
| Fat Channel model name read | `observed` | `observed` |
| Fat Channel parameter values (EQ/comp) | `calibrated_inferred` | `calibrated_inferred` |
| Channel mute write | `observed` / HIL-verified (32SC only) | not claimed for v0.1 |
Confidence labels follow the repository vocabulary: `observed` = confirmed from live hardware; `calibrated_inferred` = formula calibrated on at least one device but not independently confirmed on this model; `inferred` = derived from code/pattern without calibration; `probe_required` = requires targeted hardware probe before promotion.
---
## 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 --frozen-lockfile # installs all workspace packages
pnpm build # compiles all 4 packages
```
### Configure your mixer
Set environment variables before starting the server:
```bash
# The mixer's IP address (required)
export PRESONUS_IP=192.168.1.50
# TCP port (default: 53000 — same as UC Surface)
export PRESONUS_PORT=53000
# Strongly recommended: serial number to lock identity
# Run `pnpm probe:dev discover` to find the serial for your mixer
export PRESONUS_SERIAL=<id.hidden>
# Optional: expected role (FOH | STAGEBOX | MONITOR | UNKNOWN)
export PRESONUS_ROLE=FOH
```
On Windows PowerShell:
```powershell
$env:PRESONUS_IP="192.168.1.50"
$env:PRESONUS_PORT="53000"
$env:PRESONUS_SERIAL="<id.hidden>"
$env:PRESONUS_ROLE="FOH"
```
Setting `PRESONUS_SERIAL` is strongly recommended. It prevents the server from silently using a different mixer if discovery returns multiple devices or the IP changes.
### Connect from Claude Desktop (primary tested MCP client)
Add to `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`, Windows: `%APPDATA%\Claude\claude_desktop_config.json`):
```json
{
"mcpServers": {
"presonus-studiolive": {
"command": "node",
"args": ["<absolute-path-to-repo>/packages/presonus-mcp-server/dist/index.js"],
"env": {
"PRESONUS_IP": "192.168.1.50",
"PRESONUS_SERIAL": "<id.hidden>"
}
}
}
}
```
Replace `<absolute-path-to-repo>` with the actual path. Claude Desktop launches the server via stdio on startup.
### VS Code / GitHub Copilot
Add to `.vscode/mcp.json` in your workspace:
```json
{
"servers": {
"presonus-studiolive": {
"type": "stdio",
"command": "node",
"args": ["<absolute-path-to-repo>/packages/presonus-mcp-server/dist/index.js"],
"env": {
"PRESONUS_IP": "192.168.1.50",
"PRESONUS_SERIAL": "<id.hidden>"
}
}
}
}
```
### Read-only workflow
Once connected, an AI agent can follow this sequence:
```
discover_mixers → find mixer on LAN (or use configured fallback)
validate_mixer_identity → confirm expected serial matches
refresh_mixer_state → force fresh state from mixer
presonus://mixer/{deviceId}/channels → read channel names, mute, fader, Fat Channel models
get_mixer_capabilities → know mixer capacity (inputs, aux mixes, FX buses)
```
### Write opt-in (32SC channel mute only — disabled by default)
> ⚠️ **Writes are disabled by default.** Enabling writes allows the server to change real mixer state.
> v0.1 supported mutation: **channel mute on StudioLive 32SC only**.
> Do not enable writes during live performance unless you understand the change workflow.
Enable writes by setting:
```bash
export PRESONUS_WRITE=1
```
In your MCP client config `env` block:
```json
"env": {
"PRESONUS_IP": "192.168.1.50",
"PRESONUS_SERIAL": "<id.hidden>",
"PRESONUS_WRITE": "1"
}
```
Supported write workflow:
```
prepare_mute_change_set → propose mute state change (no mixer write yet)
validate_change_set → confirm proposal is valid and not expired
apply_change_set (dryRun: true) → preview without writing
apply_change_set (confirmationNote) → apply to hardware; returns fresh state + rollbackHint
```
The change set expires after 60 seconds. Only one proposal per channel is active at a time. Every applied change is written to the audit log on stderr.
### Probe the hardware (optional but recommended for first use)
```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`
Uses `@featherbear/presonus-studiolive-api` 1.9.1. No active patch — 1.9.1 includes `I` (int16) UBJSON type support natively. The stale `patches/@featherbear__presonus-studiolive-api@1.8.0.patch` artifact remains in the repository for historical reference but is not applied.
### `@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) | `prepare_mute_change_set`, `apply_change_set` (v0.1: channel mute only, 32SC HIL-verified) |
| Write (internal/experimental) | `prepare_channel_rename_change_set`, `prepare_sub_group_membership_change_set`, `prepare_aux_assignment_change_set` — implemented but outside v0.1 public contract |
| Write (hard-disabled) | `propose_eq_change`, `prepare_fader_change_set`, `prepare_fat_channel_change_set` — deferred pending HIL |
\* 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 workspace packages
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. <id.hidden>
# 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` 1.9.1 as hardware adapter (no active patch; v1.8.0 patch artifact in `patches/` is stale)
- **ADR-005** — Read-only-first; write operations require a `ProposedChangeSet` + audit log + confirmation flow before being enabled
### Known gaps / future work
- **Write tools (v0.1)** — `prepare_mute_change_set` + `apply_change_set` are available when `PRESONUS_WRITE=1`. Only channel mute is HIL-verified on 32SC. EQ, fader, and Fat Channel writes are hard-disabled pending calibration.
- **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).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessWithin a week