Skip to main content
Glama
MCP ETC Nomad

MCP ETC Nomad

by eva-akselrad

MCP ETC Nomad

CI

GitHub: https://github.com/eva-akselrad/mcp-etc-nomad

TypeScript Model Context Protocol server for ETC Eos Family lighting consoles — including ETCnomad on PC/Mac.

Control ETCnomad and Eos desks over OSC so an LLM can operate cues, levels, subs, macros, and the full Eos command line.

See PLAN.md for the full roadmap to operator parity.
Lighting-ops pack API: mcp-etc-nomad-specs/LIGHTING_OPS_SPEC.md (LOCKED).

Phase status

Phase

Status

0 Foundation

Implemented

1 Playback parity

Implemented

2 Programming parity

Implemented

2.5 Lighting Expert pack

Implemented

3 Show & system admin

Implemented

4 Hardening & distribution

Implemented

Related MCP server: Eos MCP

Phase 1 (playback)

Live-write tools require confirm=true when EOS_REQUIRE_CONFIRM=true (default) and allow_live=true when the console is LIVE (or state is unknown) and EOS_ALLOW_LIVE=false (default). Read get_console_state / eos://playback/state first.

Intensity scales: channel/group levels and color_set_rgb / channel_set_param are 0–100 (percent, converted at TX). fader_set_level stays 0.0–1.0 (OSC native). Grand master and submaster tool APIs are 0–100 (mapped to OSC 0.0–1.0). Cue-fire rate limit (default 12/min) is bypassed with override_rate_limit=true — confirm does not bypass rate limits.

Group

Tools

Playback

go_to_cue (CLI GTC preferred), cue_fire (alias: Assert+GTC, not /eos/cue/.../fire), cue_select, cue_go, cue_hold, cue_back, cue_resume, cue_stop (deprecated→hold), cue_list_go, get_active_cue, get_pending_cues

GM / BO

grandmaster_set_level (0–100 → fader 0/1), blackout (BO key — state default on)

Channel check

highlight / rem_dim (state + channels/ranges), timing_disable (state), sneak (optional selection — omit = current; optional time), home (selection required — no whole-rig)

Park

park, unpark (park_channel / unpark_channel deprecated aliases), get_parked

Cue list banks

cue_list_bank_config, cue_list_bank_page, cue_list_bank_select, cue_list_bank_reset

Faders / subs

fader_set_level (0.0–1.0), fader_bank_config, fader_load / _unload / _stop / _fire, fader_bank_page, fader_bank_reset, submaster_set_level (0–100), submaster_bump (submaster_fire alias), submaster_select

Palettes / presets

palette_select, palette_recall (palette_fire alias), preset_select, preset_recall (preset_fire alias)

Keys / macros

key_press, softkey_press, macro_select, macro_fire, staging_mode_toggle, list_osc_keys

Direct selects

direct_select_bank_create, direct_select_bank_page, direct_select_press

Command line

eos_command, eos_new_command, eos_event

Levels

channel_select (Thru/+), channel_set_level (0–100), channel_set_dmx, group_select, group_set_level, at_set_level

Color / params

color_set_hs, color_set_rgb (r/g/b 0–100), channel_set_param (value 0–100)

Queries

get_console_state, get_command_line, get_fader_labels_levels, get_direct_selects, wait_for_osc, osc_reset, magic_sheet_open

Resources: eos://playback/active, eos://playback/pending, eos://playback/state, eos://playback/faders, eos://console/keys

Prompts: eos-operator, eos-live

OSC addresses follow the ETC OSC Dictionary. Go is go_0; Stop/Back is stop. Create fader / cue-list / direct-select banks before paging or reading labels.

Phase 2 (programming)

Programming writes use the same confirm / allow_live gates as playback. Destructive deletes also require confirm_delete=true when EOS_REQUIRE_CONFIRM=true.

Group

Tools

Record / update

record_cue, update_cue, make_manual, set_cue_timing, record_group, record_preset, record_palette

Copy / move / delete

copy_target, move_target, delete_target (+ confirm_delete)

OSC set

label_target, group_set_channels (/eos/set/...; Thru as >)

Patch

patch_channel, patch_copy_to, patch_move, unpatch_channel

Sync / get

sync_show_targets (optional patch=true), get_groups, get_cuelists, get_cues, get_presets, get_palettes, get_patch

Command line

eos_command, eos_new_command (typed tools use newcmd); no /eos/record verb

Resources: eos://show/groups, eos://show/cuelists, eos://show/cues/{list}, eos://show/patch, eos://show/presets, eos://show/palettes/{type}

Prompts: eos-programmer, eos-patch

Sync uses OSC /eos/get/* request/response (node-eos-console / EosSyncLib pattern): count → index → cache in listener state. Subscribe with /eos/subscribe + int arg 1 on sync (default).

Phase 3 (show & system admin)

Eos OSC domain rules: no OSC Save/Load verbs — Browser + key_press + CLI only. Never invent usb1:/ or .esf paths.

Group

Tools

Show files

show_save (priority: confirm_save + path echo), show_load, show_merge, show_export; get_show_path

Patch extras

attach_patch_device, detach_patch_device

Troubleshoot

identify_fixture, channel_check, highlight_channels

Network

get_session_info, osc_set_user, network_session_join, network_session_leave

Gates: user_intent for load/merge/join; confirm_save / confirm_path when EOS_REQUIRE_CONFIRM=true. Prefer Blind for load/merge. After load/merge, sync_show_targets + reconfigure banks.

TCP transport (real TCP OSC, not UDP retarget): EOS_PROTOCOL=tcp. 3032 = OSC TCP 1.0 length headers (bidirectional); 3037 = Third Party OSC 1.1 SLIP (~realtime /eos/out). Custom ports OK (4703–4727+). Enable OSC RX+TX in Setup. UDP remains default; ETC prefers TCP.

Resources: eos://console/info, eos://console/session, eos://console/version, eos://show/path

Prompts: nomad-setup, eos-showfile

Phase 4 (hardening & distribution)

Item

Status

OSC address + CLI test suite

test/addresses-full.test.ts, test/cli-tools.test.ts, test/golden-replay.test.ts

Golden trace replay

test/fixtures/golden-traces.json → listener state parser

CI (no hardware)

GitHub Actions — npm run typecheck, build, test with mock OSC peer

npm package

mcp-etc-nomad@1.0.0 — bin, files, prepublishOnly

Cloudflare Worker relay

Not implemented — no prior sketch; documented as future remote-desk option

Install

npm install -g mcp-etc-nomad
# or as a project dependency:
npm install mcp-etc-nomad

Run the MCP server (stdio):

mcp-etc-nomad
# equivalent: npx mcp-etc-nomad

From source

npm install
npm run build
npm start

Development:

npm run dev

ETCnomad OSC setup

Setup → System Settings → Show Control → OSC:

Setting

Value

OSC RX

On

OSC TX

On

String RX

On

UDP RX Port

8000 (match EOS_PORT_TX)

UDP TX Port

9001 (match EOS_PORT_RX)

Use the console IP from Nomad Shell (not always 127.0.0.1 when MCP runs on another machine).

Copy .env.example to .env and adjust.

Testing

Mock OSC (no Nomad hardware): test/harness.ts + test/mock-osc-peer.ts listen on a local UDP port, capture MCP tool TX packets, and send canned /eos/out/* replies (see PLAN.md §11.1).

npm test

File

Coverage

test/osc-harness.test.ts

Address builders, assertLiveAllowed gates, fader/cue bank TX sequencing

test/addresses-full.test.ts

Full addresses.ts Dictionary path coverage + user prefix

test/command.test.ts

CLI Enter/#/none terminators

test/keys.test.ts

OSC hardkey aliases (go → go_0, etc.)

test/cli-tools.test.ts

eos_command, keys, palettes, macros, user prefix, mock CLI echo

test/lighting-expert.test.ts

Lighting-ops blockers: go_to_cue/cue_fire/cueZero, BO≠GM, park/unpark, highlight/home selection, timing, subs/GM/RGB 0–100

test/golden-replay.test.ts

Anonymized /eos/out/* trace replay (PLAN §11.3)

test/programming.test.ts

CLI programming builders (Copy/Delete Thru), tool TX, sync_show_targets mock-peer integration

test/show-admin.test.ts

Show save/load/merge/export gates (user_intent, LIVE refuse, confirm_path, needsManual); network_session_leave user_intent; TCP framing

test/mock-osc-peer.ts

Canned /eos/get/*, /eos/out/cmd, active cue, preset/palette replies

test/fixtures/golden-traces.json

Recorded Nomad-style OSC captures for regression

Fader level tests assert TX only — Eos echoes /eos/out/fader after ~3s, so the harness does not expect an immediate echo.

Programming tools (record_cue, update_cue, etc.) refuse LIVE/unknown console state unless allow_live=true (mock tests cover this). sync_show_targets + get_groups / get_cuelists / get_cues populate listener cache; MCP resources eos://show/* read that cache.

Automated gates in test/show-admin.test.ts: show_load / show_merge require user_intent (≥8 chars), refuse LIVE/unknown without allow_live, and require confirm_path when EOS_REQUIRE_CONFIRM=true. Default path is needsManual (no unverified Browser OSC keys); opt-in via press_unverified_browser_keys. network_session_leave requires user_intent when gating is on and returns needsManual only (Stop Mirroring / ALT+F2 — no invented /eos/key/exit).

Nomad offline smoke (manual): with ETCnomad running and OSC enabled (see above):

  1. Playback / programming: channel level, cue fire, group+cue record via CLI (record_cue), delete with confirm_delete

  2. Lighting-ops: go_to_cue (CLI Go To Cue N via /eos/newcmd — not /eos/key/go_0); confirm blackout (/eos/key/blackout) is separate from grandmaster_set_level(0) (BO≠GM); highlight / home with channel selection (reject bare calls without selection)

  3. Show files (Browser): show_save (quick save + path echo); show_load and show_merge with user_intent + confirm_path — complete the CIA Browser wizard on the desk (tools return needsManual; no auto-load/merge)

  4. Network: network_session_leave with user_intent — complete mirror exit on desk via Stop Mirroring softkey or ALT+F2 (tool returns needsManual; no OSC key TX)

  5. Gates: verify show_merge / show_load refuse LIVE without allow_live; network_session_leave requires user_intent when gating is on

Full checklist: PLAN.md §11.2.

Cursor / Claude Desktop

{
  "mcpServers": {
    "etc-nomad": {
      "command": "mcp-etc-nomad",
      "args": [],
      "env": {
        "EOS_HOST": "192.168.1.50",
        "EOS_PORT_TX": "8000",
        "EOS_PORT_RX": "9001",
        "EOS_ALLOW_LIVE": "false",
        "EOS_REQUIRE_CONFIRM": "true"
      }
    }
  }
}

When installed from source instead of npm, use "command": "node" with "args": ["/absolute/path/to/mcp-etc-nomad/dist/index.js"].

Project layout

src/
├── index.ts              # MCP stdio entry
├── config.ts             # Environment config
├── eos/
│   ├── client.ts         # OSC TX
│   ├── listener.ts       # OSC RX + state cache (+ multipart /list/0)
│   ├── state.ts          # Typed desk state
│   ├── addresses.ts      # OSC path builders
│   ├── keys.ts           # OSC Dictionary hardkey map + aliases
│   ├── command.ts        # CLI terminators
│   ├── context.ts        # Shared context + live/confirm gates
│   ├── sync.ts           # sync_show_targets (/eos/get/* cache)
│   ├── show-admin.ts     # Phase 3 show-file workflow builders
│   └── programming.ts    # Phase 2 CLI builders
├── tools/                # MCP tools (Phase 0–3)
├── resources/            # MCP resources (playback + show + console)
└── prompts/              # eos-operator, eos-live, eos-programmer, eos-patch, nomad-setup, eos-showfile

License

MIT

Available Tools

20 tools
at_set_levelA
Destructive

Set level for the current OSC selection via /eos/at

Inputs: level (required), confirm.

channel_selectB

Select an Eos channel (leaves command line unterminated)

Inputs: channel (required).

channel_set_dmxB
Destructive

Set a channel DMX level (0-255)

Inputs: dmx (required), channel (required), confirm.

channel_set_levelC
Destructive

Set a channel intensity (0-100)

Inputs: level (required), channel (required), confirm.

cue_fireA
Destructive

Fire a cue (does not follow GO sequencing unless using cue_go)

Inputs: cue (required), confirm, cueList.

cue_goB
Destructive

Press the console Go key (sequential cue advance)

Inputs: confirm.

cue_selectC

Select a cue, optionally within a cue list

Inputs: cue (required), cueList.

eos_commandA
Destructive

Send an Eos command-line instruction via OSC (/eos/cmd). Use for record, patch, copy, save, and any CLI-only operations.

Inputs: text (required), confirm, terminator.

eos_eventC

Send a console event via /eos/event (same syntax as eos_command)

Inputs: text (required), confirm, terminator.

eos_new_commandC
Destructive

Clear the command line, then send an instruction via /eos/newcmd

Inputs: text (required), confirm, terminator.

get_active_cueA
Read-only

Return the active cue text and completion percent from OSC cache

Inputs: no named parameters.

get_command_lineA
Read-only

Return the current command line text from OSC cache

Inputs: no named parameters.

get_console_stateA
Read-only

Return cached console state (blind/live, user, connection)

Inputs: no named parameters.

group_set_levelC
Destructive

Set a group intensity (0-100)

Inputs: group (required), level (required), confirm.

key_pressA
Destructive

Press an Eos hardkey by OSC name (e.g. 'go', 'clear', 'stop')

Inputs: key (required), edge, confirm.

macro_fireB
Destructive

Run a macro by number

Inputs: macro (required), confirm.

magic_sheet_openB

Open a magic sheet, optionally a specific view

Inputs: sheet (required), view.

osc_resetC

Send /eos/reset to clear OSC switches and refresh implicit output

Inputs: confirm.

submaster_set_levelC
Destructive

Set a submaster level (0.0-1.0)

Inputs: sub (required), level (required), confirm.

wait_for_oscB
Read-only

Wait for an incoming OSC address pattern (timeout in ms)

Inputs: address (required), useRegex, timeoutMs.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 20 tools

    Disambiguation2/5

    Several tools overlap heavily: eos_command, eos_new_command, and eos_event are easily confused, especially since eos_event is described as using 'same syntax as eos_command.' Also, cue_fire, cue_go, and key_press all relate to the GO action, while channel_set_level and at_set_level both set intensity, creating boundary ambiguity.

    Naming Consistency3/5

    Naming is not chaotic and consistently uses snake_case, but conventions vary: object-first names like channel_set_level and cue_fire sit alongside verb-first names like get_console_state and wait_for_osc, with oddities like eos_command and eos_new_command that have no clear action verb. It is readable but not predictable.

    Tool Count3/5

    20 tools is on the heavy/borderline side. The Eos Nomad control domain is fairly broad and most tools target distinct console resources, but several overlapping command/event/Go tools could be consolidated, making the count feel higher than necessary.

    Completeness4/5

    The set covers core console workflows well: channels, groups, cues, submasters, macros, command-line entry, key presses, and state read-back. The generic eos_command tool helps fill CLI-only gaps, though there are minor missing read-back operations such as querying submaster or channel levels directly.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables natural language control of OSC (OpenSoundControl) messages, allowing users to generate, interpret, validate, and debug OSC messages through an LLM interface for parametric control of multimedia devices.
      3
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Transforms an ETC Eos lighting console into a service controllable by AI assistants and automation tools via MCP and OSC, enabling cue management, preset recall, and channel level control.
      4
      AGPL 3.0