I thought it would be interesting to control Cubase via MCP, so I created the Cubase MCP.
By sending commands such as “play,” “stop,” or “report the current status” from an MCP client, you can operate Cubase’s transport controls and retrieve its state.
It’s not yet possible to control every aspect of Cubase. This time, I first built a small implementation that simply connects MCP to Cubase.
What was created
The implementation is an integration daemon written in Rust.
When you think of an MCP server, you often imagine only the part that implements the MCP tools, but in this case we also need to handle communication with Cubase.
Therefore, we split the system into a daemon that receives requests from the MCP client and a bridge that forwards those requests to Cubase.
Currently, six tools are available.
| MCP Tool | Function |
|---|---|
cubase.get_status | Retrieve the connection status with Cubase and basic status information |
cubase.play | Start playback |
cubase.stop | Stop playback or recording |
cubase.record | Start recording |
cubase.get_transport | Retrieve playback state, recording state, tempo, and playback position |
cubase.get_capabilities | Retrieve the features supported by the connected bridge |
A recording tool is also provided, but it actually starts recording. You need to verify the recording destination and the Record Enable state before executing it.
Overall Architecture
The overall flow is as follows.
flowchart LR
CLIENT["MCP client"] -->|"stdio / JSON-RPC"| DAEMON["cubase_mcp<br/>Rust Integration Daemon"]
DAEMON -->|"Bridge Protocol v1"| MIDI["MIDI / SysEx Bridge"]
MIDI -->|"MIDI Remote API"| REMOTE["Cubase MIDI Remote script"]
REMOTE --> CUBASE["Cubase"]
The MCP client and cubase_mcp exchange JSON‑RPC messages delimited by newlines via standard I/O.
Inside cubase_mcp, the MCP tool name is translated into Cubase‑side methods such as transport.play and transport.get.
The subsequent Bridge component handles communication with Cubase. The logical protocol for the Bridge is defined separately from MCP, allowing the same requests and responses to be handled regardless of whether TCP, MIDI, or a mock implementation is used.
In the actual Cubase environment, the MIDI Bridge is employed.
Communicating with Cubase via MIDI Remote
Cubase includes a MIDI Remote API.
Instead of searching for an API that lets external programs control Cubase directly, we created a MIDI Remote script that runs inside Cubase and made it the Control Bridge.
The MIDI Remote script can access Cubase’s transport functions.
var transport = page.mHostAccess.mTransport
This script translates incoming external requests into play, stop, and record commands for Cubase.
On macOS, the daemon creates the following virtual MIDI ports.
Cubase MCP To Cubase
Cubase MCP From Cubase
Cubase’s MIDI Remote detects this port. This setup works without needing to install a separate external MIDI loopback driver.
The installed MIDI Remote script appears in Cubase’s MIDI Remote zone as CubaseMCP - CubaseMCP.
Why Use SysEx
MIDI includes several message types for music production, such as note events and Control Change messages.
In this case, because we need to transport MCP requests and responses as JSON, we use SysEx, which can carry arbitrary data.
However, MIDI SysEx requires that each data byte be limited to 7 bits. Simply converting JSON to a UTF‑8 byte sequence may not be directly usable.
Therefore, we split each JSON byte into its upper 4 bits and lower 4 bits and store them as two bytes. This ensures that each data byte stays within the 7‑bit limit.
The frame has the following structure.
F0 7D 43 4D 43 50 01 <payload> F7
The contents of payload are JSON for Bridge Protocol v1.
For example, a playback request would logically look like this.
{
"version": 1,
"id": "req-123",
"type": "request",
"method": "transport.play",
"params": {}
}
MIDI is used not merely for sending commands, but as a communication channel that links requests with their corresponding responses.
Don’t consider it a success just because the command was sent
Just because a playback request can be sent via MIDI does not necessarily mean that Cubase has actually started playing.
Therefore, on the MIDI Remote side, we monitor Cubase’s transport state using Value Binding.
play, stop, and record do not return a success response as soon as they receive a request; they respond only after confirming that Cubase’s state has changed.
At first I thought that once the MIDI message was sent, the job was done. In reality, however, the fact that the message was sent and that Cubase actually changed its state are separate matters.
From the Bridge, if a state change fails or a response is not received, errors such as NOT_CONNECTED, TIMEOUT, and BUSY are returned.
We also avoid arbitrarily filling in values that cannot be obtained. For example, if the Bridge cannot determine whether a project exists at that moment, we return null instead of assuming false.
Handling multiple Cubase instances
Even if a response is received from the MIDI port, it is necessary to differentiate which Cubase instance it originated from.
The MIDI Bridge first sends system.discover to obtain a temporary instance ID from the MIDI Remote. Subsequent requests include that target instance ID.
If multiple Cubase instances are detected, we do not arbitrarily choose one. Because a state‑changing request could be sent to the wrong project, we halt processing by returning BUSY.
I hadn’t thought much about this until implementing it, but when dealing with playback and recording, ensuring that the request is sent to the correct target is more important than anything else.
Installation and usage
Build a release version of the Rust code and install the MIDI Remote script.
cargo build --release
./target/release/cubase_mcp --install-midi-remote
Next, register cubase_mcp in the MCP client settings.
{
"mcpServers": {
"cubase": {
"command": "/absolute/path/to/cubase_mcp/target/release/cubase_mcp",
"args": ["--bridge", "midi", "--timeout-ms", "3000"]
}
}
}
For the first run, launch Cubase while the Integration Daemon is already running.
If you install or update the MIDI Remote script while Cubase remains open, either run Reload Scripts from the MIDI Remote Scripting Tools or restart Cubase.
When using actual hardware, you can use virtual MIDI ports on macOS/Linux. When using Windows or an existing loopback MIDI port, you can specify the ports with --midi-input and --midi-output.
Not yet supported
Currently, only the transport functions are supported.
The following features are still unimplemented:
- Retrieving and selecting track lists
- Muting and soloing tracks, as well as adjusting volume and pan
- Marker manipulation
- Executing Cubase commands
- Controlling VST plugin parameters
- Audio analysis
Ultimately, it would be convenient to be able to perform actions such as “select this track and lower its volume slightly” or “analyze this region.”
However, trying to implement everything from the start makes it difficult to determine where problems arise—whether in MCP, Cubase, MIDI Remote, or the asynchronous state‑retrieval process.
Therefore, we first built a small implementation that handles play, stop, record, and state retrieval, sending a request and receiving a response.