Protocol and lifecycle
On this page
The extension is the authority for the live protocol. Protocol 3 accepts one
UTF-8, whitespace-delimited command line, terminated by \n, and returns
one JSON object followed by \n. Requests are not JSON. The Python MCP adapter
adds JSON tool schemas, authorization and workflow state above this protocol.
Endpoint and request path
- Host:
127.0.0.1; port:8765; local machine only. - No network authentication: do not forward or expose the listener.
- The deprecated OSC compatibility listener is not started by the MCP adapter.
- The Python client bounds commands to 1 MiB and responses to 8 MiB and uses a 65-second socket timeout. Direct clients should also bound their input/output.
For a live operation, the adapter validates the arguments and any required
confirm/cooperative authorization, then sends a command. The extension
serializes dispatch and schedules host/UI work. Each received request has one
30-second deadline spanning the queue and native submission. Expired queued
writes are rejected; a timeout cannot retract an operation already submitted to
Bitwig. Read state before deciding what happened; never blindly retry a write.
The response is JSON with operation-specific fields, usually ok. MCP tools
return this JSON as text, or Error: ... for an exception. A valid JSON response
or ok: true is not proof of audible output or arranger content. Offline plan,
export and Nitro operations need not contact the extension at all.
Copying a replacement .bwextension does not prove it has loaded: Java
extension reload is asynchronous. Wait for read-only capability/readiness
responses from the expected extension, Bitwig 6.1.1 and API 25 before mutation.
Refresh all live identities after reload; never use a mutation as a readiness
probe or replay one after a timeout.
Core commands
The notation below is descriptive: substitute observed values without angle brackets. UUIDs, module instance IDs, native parameter IDs and action IDs are different identifiers. Do not substitute one for another.
| Command | Meaning |
|---|---|
ping |
Connection check |
capabilities |
Protocol, extension/host versions and nested grid_graph capabilities |
state |
Selected device's exposed controls, indexes 1–8, normalized values 0–1 |
inspect |
Selected device and container flags |
history |
Observed project name/modified state and host undo/redo availability |
set INDEX VALUE |
Write one exposed control, native protocol range 0–1 |
batch INDEX=VALUE ... |
Serialized exposed-control writes; not an all-or-nothing transaction |
tracks / track INDEX |
List/select the observed flat track bank, indexes 0–63 |
track-first / track-next / track-previous |
Track navigation |
next / previous / parent / device-first |
Device navigation; device-first works from an empty selection |
device-primary / device-editor |
Host device selection/editor operations |
device-catalog [QUERY] |
Search installed Bitwig device UUIDs |
devices |
Selected device's 16-sibling window, native identities, selection_token, window_complete; not a whole recursive chain |
insert POSITION UUID SELECTION_TOKEN |
Guarded insertion at before, after, start, end or replace; all require a selected track, before/after/replace also a selected device |
device-remove SELECTION_TOKEN |
Guarded removal of the exact selected native device |
actions [QUERY] / action ID |
Discover actions, then invoke the exact returned wire ID |
undo / redo |
Latest global host operation, not a session-specific rollback |
The MCP exposed-control scale is 0–128; its adapter converts to the native
protocol's 0–1 scale. Neither is a Grid module's native parameter domain.
Action IDs are percent-escaped wire tokens: pass the returned token unchanged.
The flat track bank explicitly uses ALL_CHANNELS: it includes hidden children
of collapsed groups, FX and master tracks. A bank index is not the native track
position and not a main-track-only ordinal.
Device edits require the current 64-hex selection_token from selected-device
properties or devices. This is distinct from a project token or graph revision.
instance_id/track_id identify current native objects, not persistent UUIDs.
Native edits revalidate selection and exact identities on the document thread.
Public state/inspect can return null private identities and
selection_token: null with selection_guard_available: false and a reason;
these reads remain usable, but missing native guards cannot authorize edits.
Grid commands and revision guards
| Command | Meaning |
|---|---|
graph-capabilities / graph-state |
Capability gate and selected graph snapshot |
graph-catalog [QUERY] / graph-modulators [QUERY] |
Installed module/modulator metadata |
graph-host-modulators |
Selected device's host-level modulation sources |
graph-insert UUID X Y / graph-insert-modulator UUID X Y |
Insert at explicit integer coordinates, −4096 through 4096 |
graph-set MODULE PARAMETER_B64 VALUE / graph-set-modulator MODULE PARAMETER_B64 VALUE |
Write an exact native parameter ID and native number/boolean value |
graph-resolve-value MODULE PARAMETER_B64 DISPLAY_B64 |
Non-mutating musical-unit resolution through Bitwig's native scale |
graph-connect SOURCE SOURCE_PORT TARGET TARGET_PORT / graph-connect-modulator SOURCE SOURCE_PORT TARGET TARGET_PORT |
Connect live numeric ports |
graph-disconnect TARGET TARGET_PORT |
Disconnect a live input |
graph-move MODULE X Y |
Direct-protocol move, not a general MCP patch-session operation |
graph-clear |
Destructive selected-graph clear; MCP grid_clear_graph requires reviewed expected_revision, authorization and retains a before snapshot |
guard REVISION GRAPH_COMMAND... |
Require the exact reviewed graph revision before a graph mutation |
PARAMETER_B64 and DISPLAY_B64 are URL-safe base64 of UTF-8 strings. Encode
each separately, including parameter IDs such as C# or IDs containing spaces.
MCP callers pass raw exact strings; the adapter performs this encoding. There
is no uppercase-only parameter-ID convention.
Graph snapshots provide graph_id and a 64-lowercase-hex revision. Guarded
writes check the selected graph identity and revision again at the native
submission boundary. MCP grid_clear_graph requires expected_revision; other
direct MCP graph writers accept it optionally. If omitted there, the adapter
reads a fresh revision, which guards dispatch but does not bind the write to an
older human-reviewed snapshot. Supply the reviewed revision explicitly.
A graph guard accepts only graph mutations, not device, transport or song commands.
Clear replies retain the full native payload through MCP, including ok: false
and any before snapshot on failure. Preserve the caller's snapshot before
acting anyway: transport loss may provide no reply. Inspect ok and resulting
state; a returned payload is not necessarily successful.
Value resolution returns value, current_value, current_display,
requested_display, display_unit and revision, with mutates_project: false.
It supports editable continuous native parameters and compatible musical units;
it does not write or promise a proposed round-trip display. Write the returned
value against that revision, then read the actual display/value. Resolve enums
and booleans from the live parameter's options/type instead.
Private Grid/device-catalog/native-arranger bindings support Bitwig 6.1.1
exactly. Unknown private runtimes fail closed: no guessed fallback. The public
extension minimum, current host and compile-time Controller API are 25.
When graph_available is false, exposed controls may still work, but no graph
identity, topology or native parameter metadata may be inferred.
Song commands and observations
| Command | Meaning |
|---|---|
song-state |
Project token, selected target, flat inventory, transport, conservative recording state, meters and capabilities |
song-import TOKEN SHA256 NOTE_TRACK_COUNT PATH_B64 |
Submit a reviewed type-1 MIDI artifact to new arranger tracks after the selected native target at beat 0 |
song-import-status RECEIPT_ID |
Receipt plus current inventory observation; not arranger-note verification |
song-save TOKEN DIRECTORY_B64 |
Save the controller-owned project through Bitwig into a caller-allocated fresh empty directory; native asynchronous completion, not MIDI export |
song-arranger INDEX |
Read one track's bounded primary arranger clips and stored source-note summaries; index 0–63 |
song-clip-loop TOKEN INDEX CLIP_ID EXPECTED_ENABLED ENABLED |
Set only the exact clip's loop flag; signed 32-bit native clip ID and literal true/false booleans |
song-transport-play TOKEN / song-transport-stop TOKEN |
Explicit transport operation |
song-transport-tempo TOKEN BPM |
Explicit finite tempo, 40–220 BPM |
song-transport-meter TOKEN METER |
Explicit meter, numerator 2–12 and denominator 4 or 8 |
song-transport-seek TOKEN POSITION_BEATS |
Explicit finite seek, 0–24576 quarter-note beats; refuses recording; no implicit tempo/meter change |
song-transport-loop TOKEN ENABLED |
Set global arranger-loop enabled state using literal true/false; refuses recording; leaves loop range and clip flags unchanged |
song-track-state TOKEN INDEX MUTED ARMED |
Each state is true, false or keep; index 0–63 |
TOKEN is the exact observed 64-hex project_token, not a stable project UUID.
PATH_B64 is URL-safe base64 UTF-8 of an absolute local MIDI path. Native
validation checks the regular, non-symlink file, size, source digest, type-1
structure and note-track count. MCP import adds the stronger TrackWorkflow
contract: current session revision, workflow-owned export and one import
attempt, including ambiguous timeouts. Prefer that reviewed workflow over
constructing raw native imports.
The target must be an observed Group, Instrument, Audio or Hybrid track. Import
requires an unscrolled, fully represented bank, capacity for note tracks plus
one group slot, no recording, the same project token and the exact selected
native target through submission. The import uses an arranger insertion point,
not the public track-header insertFile path, which excluded clips.
The full exported MIDI retains tempo and meter metadata. Native submission uses
a private derived MIDI without those global metadata events to avoid the
observed tempo-import modal; the receipt reports artifact_sha256 and distinct
submitted_sha256. Set global tempo/meter only through the explicit transport
operations with a fresh token. Import never starts playback.
For compiler-generated exports this is the notes/markers delivery scope. The transformation removes tempo/meter events; it is not a general sanitizer for arbitrary raw MIDI event types.
Receipts can be queued, submitted, native_rejected or
submission_error_observe_before_retry. verified remains false;
requires_observation remains true. Track-count deltas, launcher slots, native
acceptance and meter values cannot prove arranger-note placement, instrument
assignment or a complete audible song. Inspect the actual arranger and listen.
Playback refuses recording or armed tracks; tempo/meter, seek, arranger-loop
and track-state changes also refuse recording. Stop remains an explicit
token-guarded action. Arranger-loop returns status: "submitted",
operation: "transport_loop", the requested enabled and verified: false.
Read back the three arranger_loop_* song-state fields; enabled/start/duration
are part of the token, unlike position/playing or project_save_pending.
song-arranger returns {ok, project_token, track_index, track_name, atomic: false, arranger}. Inspect arranger.available, truncation, metadata/count/
detail completeness and per-field reasons. Clip positions, lengths, loop
geometry and source-note spans use native quarter-note beats without tick
conversion. Source summaries do not expand loops or establish playback/audio.
For song-clip-loop, use the observed clip native_id and loop_enabled
plus a fresh project token. It rechecks project/recording context before and
inside the UI callback, request deadline, canonical track identity, exact clip
object identity and expected loop state immediately before the native undo/
observer-backed setter. Success requires read-back of the desired flag.
It does not change notes, timing, loop range, clip length or selection, and
ambiguity never authorizes replay. Re-inspect source counts/spans and flags.
Native project save
DIRECTORY_B64 is URL-safe base64 of the UTF-8 absolute directory path, not a
MIDI path or .bwproject filename. Raw clients must allocate and retain their
own fresh private empty directory. Native validation rejects symlinks/noncanonical
paths, non-directories, nonempty destinations and locations inside an existing
Bitwig project folder. The adapter's grid_song_save does this allocation under
the configured/explicit output root using a bitwig-project-* child.
The exact 6.1.1 private binding maps the controller project's native document to
its same-document application wrapper on the UI thread; it does not select the
globally focused document or invoke a Save action. Project token, native identity,
recording state and request deadline are rechecked before submission. Plugin-bridge
documents reject. The ordinary native save uses false, false flags, not Collect
and Save: external assets can remain external. The replacement-capable host writer
is not an atomic no-clobber API; the private empty destination avoids existing files.
The native callback returns {ok, status, project_file, error, verification_scope}: success is native_save_completed after the callback and
presence of DIRECTORY/PromptTrack/PromptTrack.bwproject as a regular nonsymlink
file; failure is requires_observation. This is not a project-content comparison
or audio verification. project_save_pending blocks another save until native
completion (or submission failure), not merely until a client stops waiting.
A transport timeout after submission cannot cancel the host writer.
MCP retains output_directory and retry_performed: false; a bridge/native error
becomes ok: false, status: "requires_observation", error and
native_response (null when no native reply is available). Inspect fresh song
state, pending status and the known directory; never automatically replay or
delete a possibly in-use output. Raw clients must retain the directory themselves
before sending. See checkpoint recovery.
Persistence and recovery
Shaping, incremental patch and song sessions are process-local; restarting the adapter loses their review authority. Parameter snapshots cover only observable parameters. Patch sessions retain a before-graph as evidence, not as an executable restore program. They apply one reviewed step at a time and freeze on stale state, failed verification or ambiguous outcomes; they never replay or roll back automatically.
Host undo/redo affects the latest global operation, which may belong to another writer. Re-read selection, graph and song state before recovery. A sequence of native writes is not one guaranteed undo transaction. Successful coordinate read-back also does not establish save/reload persistence: verify persistence separately when it is an acceptance requirement, without replacing the whole graph as an implicit recovery action.