valknar 7125a2ce4c Fix clip_record_notes: use /record + isRecording polling instead of overdub
Switch from the launcher-overdub approach (which never actually recorded notes)
to arming the track and sending /track/N/clip/N/record, then waiting for
isRecording=1 before starting the MIDI event timer. Adds all-notes-off at loop
end to prevent stuck notes, and reports recording_confirmed in the return value.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 19:01:53 +02:00

bitwig-mcp

A full-featured Model Context Protocol (MCP) server for controlling Bitwig Studio via the DrivenByMoss OSC extension. Exposes 204 MCP tools covering every OSC command DrivenByMoss supports — transport, tracks, devices, clips, scenes, browser, project, layout, MIDI, and more.


Table of Contents


Prerequisites

  • Bitwig Studio 4.x or later
  • DrivenByMoss installed as a Bitwig controller extension (download)
  • Python 3.10+

Installation

# Clone or navigate to the project
cd bitwig-mcp

# Create a virtual environment
python -m venv .venv

# Activate it (Windows)
.venv\Scripts\activate

# Install the package and all dependencies
pip install -e .

Dependencies installed:

  • mcp[cli] — Model Context Protocol SDK (FastMCP server)
  • python-osc — OSC send/receive over UDP

DrivenByMoss OSC Setup

  1. Open Bitwig Studio → Preferences → Controllers
  2. Click Add controller → search for DrivenByMoss → select Open Sound Control (OSC)
  3. Configure the OSC extension settings:
DrivenByMoss Setting Value Meaning
Port to receive on 8000 DrivenByMoss listens here — bitwig-mcp sends to this port
Host to send to 127.0.0.1 Where DrivenByMoss sends state updates
Port to send to 9000 DrivenByMoss sends here — bitwig-mcp listens on this port
  1. Enable the controller and restart Bitwig if prompted.

Important: "Port to receive on" is where DrivenByMoss accepts commands (bitwig-mcp sends TO it). "Port to send to" is where DrivenByMoss pushes state updates (bitwig-mcp listens ON it). These are from DrivenByMoss's perspective — the opposite of what you might expect.


Configuration

All settings are controlled via environment variables with sensible defaults:

Variable Default Description
BITWIG_HOST 127.0.0.1 Host where Bitwig is running
BITWIG_SEND_PORT 8000 Port to send OSC commands to (DrivenByMoss receive port)
BITWIG_RECEIVE_PORT 9000 Port to listen for state updates on (DrivenByMoss send port)
BITWIG_TIMEOUT 5.0 Seconds to wait when using wait_for_state()
BITWIG_REFRESH_WAIT 0.5 Seconds to wait after sending /refresh for state to populate

Example — connecting to Bitwig on another machine:

BITWIG_HOST=192.168.1.10 BITWIG_SEND_PORT=8000 bitwig-mcp

Running the Server

# With venv activated
bitwig-mcp

# Or directly via the venv executable (Windows)
.venv\Scripts\bitwig-mcp.exe

# Or via Python
python -m bitwig_mcp.server

The server starts and listens for MCP connections on stdio (standard MCP transport). It also starts the OSC listener thread in the background immediately.


Registering with Claude Code

Global (available in all sessions)

claude mcp add bitwig-mcp "C:\path\to\bitwig-mcp\.venv\Scripts\bitwig-mcp.exe"

Project-scoped (.claude/settings.json)

{
  "mcpServers": {
    "bitwig-mcp": {
      "command": "C:\\path\\to\\bitwig-mcp\\.venv\\Scripts\\bitwig-mcp.exe",
      "args": []
    }
  }
}

Verify it's working

Once registered, ask Claude:

"Check if Bitwig is connected" → calls ping_bitwig "Get the current transport state" → calls transport_get_state


Architecture

Push-Based OSC Protocol

DrivenByMoss uses a push-based (publish-subscribe) model rather than request/response. Bitwig proactively sends OSC messages to notify about every state change. This means:

  • No polling needed — state arrives automatically when anything changes in Bitwig
  • State storeosc_client.py maintains an in-memory dict (address → last received args) updated by every incoming OSC message
  • /refresh — sending this special command triggers Bitwig to dump its full current state immediately (used on startup and by refresh_state)

Data Flow

Claude / LLM
    │
    │ MCP tool calls
    ▼
bitwig-mcp (FastMCP server)
    │                    ▲
    │ cmd(addr, *args)   │ OSC state updates
    │ (UDP to port 8000) │ (UDP from port 9000)
    ▼                    │
DrivenByMoss OSC extension (inside Bitwig Studio)
    │                    ▲
    │ Bitwig Java API    │
    ▼                    │
Bitwig Studio ──────────┘

Key Components

File Purpose
src/bitwig_mcp/server.py FastMCP server entry point; registers all tool modules
src/bitwig_mcp/config.py Environment variable configuration
src/bitwig_mcp/osc_client.py Thread-safe OSC singleton: send, receive, state store
src/bitwig_mcp/tools/ One file per domain, each exports register(mcp)

OSC Client API

from bitwig_mcp.osc_client import get_client

c = get_client()

# Send a command to Bitwig (fire-and-forget)
c.cmd("/play", 1)
c.cmd("/track/1/volume", 100)

# Read cached state (returns tuple or None)
state = c.get_state("/tempo/raw")   # e.g. (128.0,)
value = c.get_state_value("/play")  # e.g. 1

# Wait for a state update (blocks up to timeout)
c.wait_for_state("/track/1/volume", timeout=2.0)

# Trigger full state dump from Bitwig
c.refresh()

# Get all known state as a dict
all_state = c.get_all_state()

# Check connection
connected = c.ping()

Value Ranges

DrivenByMoss uses the following conventions (default resolution: 128 steps):

Parameter Range Notes
Volume 0127 ~100 = unity gain (0 dB)
Pan 0127 64 = center
Send volume 0127 ~100 = unity
Boolean (on/off) 0 or 1 Omitting triggers a toggle
Normalized floats 0.01.0 Device parameters, EQ settings
MIDI note 0127 60 = C4 (middle C)
MIDI velocity 0127 0 = note off
Track/clip indices 18 1-indexed in OSC paths
Colors "rgb(r,g,b)" e.g. "rgb(255,128,0)"

Tool Reference

Transport

Tools for controlling playback, recording, tempo, loop, and automation.

Tool Parameters Description
transport_play value=1 Start/stop playback. 1 = play, 0 = stop
transport_stop Stop playback (rewinds if already stopped)
transport_restart Restart playback from the beginning
transport_toggle_record value? Toggle/set arranger record mode
transport_toggle_overdub value? Toggle/set arranger overdub
transport_toggle_launcher_overdub value? Toggle/set clip launcher overdub
transport_toggle_loop value? Toggle/set loop (repeat) mode
transport_toggle_punch_in value? Toggle/set punch-in recording
transport_toggle_punch_out value? Toggle/set punch-out recording
transport_toggle_click value? Toggle/set metronome
transport_set_click_volume value (0100) Set metronome volume
transport_toggle_click_ticks value? Toggle/set metronome tick marks
transport_toggle_click_preroll value? Toggle/set pre-roll click
transport_quantize Quantize the cursor clip
transport_set_tempo bpm (float) Set tempo in BPM
transport_tap_tempo Tap tempo (call repeatedly)
transport_adjust_tempo amount, direction ("+"/"-") Nudge tempo up or down
transport_set_position value (float) Set timeline position in beats
transport_step_position direction ("+"/"-"), size ("small"/"large") Step position forward/backward
transport_goto_start Jump to beginning of arrangement
transport_set_crossfade value (0127) Set crossfader position (64 = center)
transport_reset_crossfade Reset crossfader to center
transport_toggle_autowrite value? Toggle/set arranger automation write
transport_toggle_launcher_autowrite value? Toggle/set launcher automation write
transport_set_automation_mode mode ("OFF"/"LATCH"/"TOUCH") Set automation write mode
transport_set_preroll measures (int) Set pre-roll measure count
transport_set_post_recording_action action (string) Set launcher post-recording action
transport_set_post_recording_offset beats (float) Set post-recording time offset in beats
transport_set_launch_quantization quantization (string) Set default clip launch quantization
transport_get_state Get complete transport state snapshot

Post-recording action values: "OFF", "PLAY_RECORDED", "RECORD_NEXT_FREE_SLOT", "STOP", "RETURN_TO_ARRANGEMENT"

Launch quantization values: "NONE", "1/32", "1/16", "1/8", "1/4", "1/2", "1", "2", "4", "8"


Track

Tools for mixer controls, clips, sends, and navigation across the 8-track bank window. All track_index parameters are 1-indexed (18).

Tool Parameters Description
track_set_volume track_index, value (0127) Set track volume
track_reset_volume track_index Reset volume to unity gain
track_set_pan track_index, value (0127) Set track pan (64 = center)
track_reset_pan track_index Reset pan to center
track_toggle_mute track_index, value? Toggle/set mute
track_toggle_solo track_index, value? Toggle/set solo
track_toggle_recarm track_index, value? Toggle/set record arm
track_toggle_monitor track_index, value? Toggle/set input monitoring
track_toggle_auto_monitor track_index, value? Toggle/set auto-monitoring
track_select track_index Select/focus a track
track_set_crossfade_mode track_index, mode ("A"/"B"/"AB") Set crossfade assignment
track_set_send_volume track_index, send_index, value (0127) Set a send level
track_reset_send_volume track_index, send_index Reset send to default
track_toggle_send track_index, send_index, value? Toggle/set send active
track_clip_launch track_index, clip_index Launch a clip slot
track_clip_launch_alt track_index, clip_index Launch clip (alternative mode)
track_clip_record track_index, clip_index Record into a clip slot
track_clip_create track_index, clip_index, length_beats=4.0 Create empty clip
track_clip_duplicate track_index, clip_index Duplicate a clip
track_clip_remove track_index, clip_index Delete a clip
track_clip_set_name track_index, clip_index, name Rename a clip
track_clip_set_color track_index, clip_index, color Set clip color (rgb string)
track_navigate_next Select next track
track_navigate_prev Select previous track
track_bank_next Scroll track bank forward one page
track_bank_prev Scroll track bank backward one page
track_navigate_parent Navigate to parent group track
track_enter_group track_index Enter a group track
track_add_audio Add a new audio track
track_add_instrument Add a new instrument track
track_add_effect Add a new effect/FX track
track_stop_clips track_index Stop all clips on a track
track_toggle_bank Toggle between main and effect track bank
track_set_param param_index (18), value (0.01.0) Set a remote control parameter
track_reset_param param_index Reset remote control parameter
track_navigate_params direction ("+"/"-"/"page+"/"page-") Navigate remote control parameters
track_indicate_volume all_tracks=True Enable volume indication
track_indicate_pan all_tracks=True Enable pan indication
track_indicate_send send_index Enable send indication
track_toggle_vu_meters value? Toggle/set VU meter feedback
track_get_info track_index Get full state for one track
track_get_all Get state for all 8 tracks in bank
track_selected_get_state Get cursor (selected) track state
track_selected_set_volume value (0127) Set cursor track volume
track_selected_set_pan value (0127) Set cursor track pan
track_selected_toggle_mute value? Toggle/set cursor track mute
track_selected_toggle_solo value? Toggle/set cursor track solo
track_selected_toggle_recarm value? Toggle/set cursor track record arm

Master Track

Tool Parameters Description
master_set_volume value (0127) Set master track volume
master_reset_volume Reset master volume to unity
master_set_pan value (0127) Set master track pan
master_reset_pan Reset master pan to center
master_toggle_mute value? Toggle/set master mute
master_select Select/focus the master track
master_get_state Get complete master track state

Device

Tools for the cursor device (the currently focused device in Bitwig's device chain). Also covers the primary instrument (primary_*) and EQ device (eq_*).

Tool Parameters Description
device_toggle_bypass value? Toggle/set device bypass
device_toggle_expand value? Toggle/set device expanded view
device_toggle_parameters value? Toggle/set parameters panel
device_toggle_window value? Toggle/set plugin window
device_toggle_pinned value? Toggle/set cursor pin
device_navigate_next Move cursor to next device in chain
device_navigate_prev Move cursor to previous device
device_set_param param_index (18), value (0.01.0) Set device parameter
device_reset_param param_index Reset parameter to default
device_navigate_params direction ("+"/"-"/"page+"/"page-") Navigate parameter pages
device_select_page page_index Select a parameter page
device_select_sibling sibling_index Select a sibling device in chain
device_layer_select layer_index Select a device layer
device_layer_set_volume layer_index, value (0127) Set layer volume
device_layer_set_pan layer_index, value (0127) Set layer pan
device_layer_toggle_mute layer_index, value? Toggle/set layer mute
device_layer_set_send_volume layer_index, send_index, value (0127) Set layer send volume
device_layer_enter layer_index Enter a layer to view its devices
device_layer_navigate direction ("+"/"-"/"page+"/"page-") Navigate layers
device_drumpad_select pad_index Select a drum pad
device_drumpad_set_volume pad_index, value (0127) Set drum pad volume
device_drumpad_set_pan pad_index, value (0127) Set drum pad pan
device_drumpad_toggle_mute pad_index, value? Toggle/set drum pad mute
device_drumpad_toggle_solo pad_index, value? Toggle/set drum pad solo
primary_set_param param_index (18), value (0.01.0) Set primary instrument parameter
primary_toggle_bypass value? Toggle/set primary instrument bypass
primary_navigate_params direction ("+"/"-"/"page+"/"page-") Navigate primary instrument params
eq_set_type band_index, eq_type (string) Set EQ band filter type
eq_set_gain band_index, value (0.01.0) Set EQ band gain
eq_set_frequency band_index, value (0.01.0) Set EQ band frequency (normalized)
eq_set_q band_index, value (0.01.0) Set EQ band Q factor (normalized)
eq_add Add an EQ+ device to the selected track
device_get_state Get full device state (params, pages, layers, siblings)

EQ band types: "OFF", "LP1", "LP2", "HP1", "HP2", "LS" (low shelf), "HS" (high shelf), "BELL", "NOTCH"


Clip

Tools for the cursor clip — the clip currently in focus in Bitwig's clip editor.

Tool Parameters Description
clip_quantize Quantize the cursor clip
clip_set_name name Rename the cursor clip
clip_set_color color (rgb string) Set cursor clip color
clip_toggle_pinned value? Toggle/set clip pin (keeps focus on this clip)
clip_launch Launch the cursor clip
clip_launch_alt Launch cursor clip (alternative mode)
clip_record Record into the cursor clip
clip_create length_beats=4.0 Create a new empty clip
clip_stop Stop clips on the cursor track
clip_stop_alt Stop clips (alternative mode)
clip_stop_all Stop all playing clips globally
clip_stop_all_alt Stop all clips (alternative mode)
clip_navigate direction ("+"/"-") Navigate to next/previous clip
clip_get_state Get cursor clip state (name, color, pinned)

Scene

Tools for the clip launcher scene bank. All scene_index parameters are 1-indexed.

Tool Parameters Description
scene_launch scene_index Launch a scene (triggers all clips in that row)
scene_launch_alt scene_index Launch scene (alternative mode)
scene_select scene_index Select a scene
scene_duplicate scene_index Duplicate a scene
scene_remove scene_index Delete a scene
scene_set_name scene_index, name Rename a scene
scene_set_color scene_index, color (rgb string) Set scene color
scene_navigate direction ("+"/"-"/"bank+"/"bank-") Scroll scene bank
scene_add Add a new empty scene
scene_create_from_playing Create scene from all currently playing clips
scene_get_all Get state of all 8 scenes in current bank

Browser

Tools for navigating Bitwig's content browser (presets, devices, samples).

Tool Parameters Description
browser_open_preset Open browser to replace current device with a preset
browser_insert_device_after Open browser to insert device after cursor
browser_insert_device_before Open browser to insert device before cursor
browser_navigate_tab direction ("+"/"-") Navigate content type tabs
browser_navigate_filter column_index, direction ("+"/"-") Navigate a filter column
browser_reset_filter column_index Reset a filter column to show all
browser_navigate_results direction ("+"/"-") Navigate results list
browser_commit Load the selected browser item
browser_cancel Close browser without loading
browser_get_state Get browser state (tab, filters, results)

Project

Tool Parameters Description
project_save Save the current project
project_navigate direction ("+"/"-") Switch between open projects
project_toggle_engine value? Toggle/set the Bitwig audio engine
project_set_param param_index (18), value (0.01.0) Set a project remote control parameter
project_reset_param param_index Reset project parameter to default
project_navigate_params direction ("+"/"-"/"page+"/"page-") Navigate project parameters
project_select_page page_index Select a project parameter page
project_get_state Get project state (name, engine, params, pages)

Layout & Panels

Tools for controlling Bitwig's UI layout and panel visibility.

Tool Parameters Description
layout_set layout_name Set the panel layout (e.g. "SINGLE", "DOUBLE", "THREE")
panel_toggle_note_editor value? Toggle/set note editor panel
panel_toggle_automation_editor value? Toggle/set automation editor panel
panel_toggle_devices value? Toggle/set devices panel
panel_toggle_mixer value? Toggle/set mixer panel
panel_toggle_fullscreen value? Toggle/set fullscreen mode
arranger_toggle_cue_markers value? Toggle cue marker visibility in arranger
arranger_toggle_playback_follow value? Toggle playback follow (auto-scroll)
arranger_toggle_track_height value? Toggle double track row height
arranger_toggle_clip_launcher value? Toggle clip launcher section in arranger
arranger_toggle_timeline value? Toggle timeline visibility
arranger_toggle_io_section value? Toggle I/O section in arranger
arranger_toggle_effect_tracks value? Toggle effect tracks visibility
mixer_toggle_clip_launcher value? Toggle clip launcher in mixer
mixer_toggle_crossfade_section value? Toggle crossfade section in mixer
mixer_toggle_device_section value? Toggle device section in mixer
mixer_toggle_sends_section value? Toggle sends section in mixer
mixer_toggle_io_section value? Toggle I/O section in mixer
mixer_toggle_meter_section value? Toggle meter/VU section in mixer
layout_get_state Get full layout and visibility state

MIDI (Virtual Keyboard)

Tools for sending MIDI messages through DrivenByMoss's virtual keyboard. Channels are 1-indexed (116).

Tool Parameters Description
midi_note channel, note (0127), velocity (0127) Send a note on/off message
midi_note_with_array channel, note, velocity Send note using array format
midi_note_octave channel, direction ("+"/"-") Shift note keyboard octave
midi_drum channel, note (0127), velocity (0127) Send a drum pad note
midi_drum_octave channel, direction ("+"/"-") Shift drum pad octave
midi_cc channel, cc (0127), value (0127) Send a MIDI CC message
midi_channel_aftertouch channel, pressure (0127) Send channel aftertouch
midi_poly_aftertouch channel, note (0127), pressure (0127) Send polyphonic aftertouch
midi_pitchbend channel, value (0127) Send pitch bend (64 = center)
midi_set_fixed_velocity velocity (0127) Set fixed accent velocity (0 = disable)
midi_note_repeat_toggle value? Toggle/set note repeat mode
midi_note_repeat_period period (string) Set note repeat period (e.g. "1/16")
midi_note_repeat_length length (string) Set note repeat length (e.g. "1/16")
midi_get_state Get MIDI/note repeat state

Note repeat period/length values: "1/32", "1/16", "1/8T", "1/8", "1/4T", "1/4", "1/2", "1"


Miscellaneous

Tool Parameters Description
undo Undo the last action
redo Redo the last undone action
action_execute action_index (120) Execute a DrivenByMoss assignable custom action
marker_launch marker_index Jump to and play from a cue marker
marker_navigate direction ("bank+"/"bank-") Navigate marker bank pages
marker_get_all Get all markers in current bank
refresh_state Send /refresh and return full state dump
state_dump Return current state store without refreshing
ping_bitwig Check if Bitwig+DrivenByMoss is reachable

Usage Examples

Check connection and get initial state

ping_bitwig
→ {"connected": true, "message": "Bitwig + DrivenByMoss OSC is active"}

refresh_state
→ {"/play": (0,), "/tempo/raw": (128.0,), "/track/1/name": ("Drums",), ...}

Transport control

transport_set_tempo(bpm=120.0)
transport_toggle_loop(value=1)
transport_play()

transport_get_state()
→ {"playing": 1, "tempo_bpm": 120.0, "loop": 1, ...}

transport_stop()

Mixing

track_get_all()
→ [{"index": 1, "name": "Drums", "volume": 100, "mute": 0, ...}, ...]

track_set_volume(track_index=1, value=90)
track_toggle_mute(track_index=2)
track_toggle_solo(track_index=1)

master_set_volume(value=110)

Launching clips and scenes

scene_get_all()
→ [{"index": 1, "name": "Verse", ...}, {"index": 2, "name": "Chorus", ...}]

scene_launch(scene_index=2)

track_clip_launch(track_index=3, clip_index=1)

clip_stop_all()

Device control

device_get_state()
→ {"name": "Polysynth", "bypass": 0, "params": [...], "pages": [...]}

device_set_param(param_index=1, value=0.75)
device_select_page(page_index=2)
device_toggle_bypass()

MIDI input

# Play a C major chord on channel 1
midi_note(channel=1, note=60, velocity=100)   # C4
midi_note(channel=1, note=64, velocity=100)   # E4
midi_note(channel=1, note=67, velocity=100)   # G4

# Release
midi_note(channel=1, note=60, velocity=0)
midi_note(channel=1, note=64, velocity=0)
midi_note(channel=1, note=67, velocity=0)

# Drum hit on pad 1 (usually kick drum)
midi_drum(channel=10, note=36, velocity=127)

Browser

browser_open_preset()          # Opens browser for current device
browser_navigate_tab("+")      # Switch to next content type
browser_navigate_results("+")  # Move to next result
browser_commit()               # Load the selection

Troubleshooting

ping_bitwig returns connected: false

  1. Is Bitwig running? Make sure Bitwig Studio is open and a project is loaded.
  2. Is DrivenByMoss active? Go to Preferences → Controllers and confirm the OSC controller is enabled (green checkmark).
  3. Check ports: DrivenByMoss "receive port" must be 8000 and "send to port" must be 9000 (or set BITWIG_SEND_PORT/BITWIG_RECEIVE_PORT to match your custom values).
  4. Firewall: Ensure UDP ports 8000 and 9000 are not blocked by a firewall.
  5. Port conflict: Check nothing else is using those ports: netstat -ano | findstr :8000 (Windows).

State values all return None

The state store is empty, meaning no messages have been received from Bitwig. Call refresh_state to trigger a full dump:

refresh_state()

If it still returns mostly empty, see the connectivity steps above.

track_get_all shows only 8 tracks but I have more

DrivenByMoss uses a track bank window of 8 tracks. Use track_bank_next / track_bank_prev to scroll to other groups of tracks.

Changes not reflected in state after a command

DrivenByMoss sends state updates asynchronously. For fast sequences of commands, wait briefly or call refresh_state to re-sync. The BITWIG_REFRESH_WAIT env var controls how long refresh() waits (default 0.5 s).

Server exits immediately

Make sure you are running the server directly, not piping to it. The MCP server communicates over stdio — it is meant to be launched by a MCP client (like Claude Code), not run interactively in a terminal. To test it manually, use the MCP Inspector.


License

MIT

S
Description
A full-featured Model Context Protocol (MCP) server for controlling Bitwig Studio via the DrivenByMoss OSC extension.
Readme 72 KiB
Languages
Python 100%