Skip to main content
Glama

LemonStudioMCP

An MCP server that lets Claude (or any MCP client) make music in Lemon Studio, the open-source FL Studio-style DAW written in Rust.

How it works

Lemon Studio has a built-in control plane: started with --control <socket>, it listens on a local Unix socket and answers JSON-RPC 2.0 requests, one per line. This server is an MCP front end for that socket:

Claude ──MCP──▶ lemon-studio-mcp ──JSON-RPC over /tmp/lemon.sock──▶ Lemon Studio
  • Reading. get_project_state returns the whole project in one call: tempo, channels, mixer tracks, patterns and the playlist. Everything has a stable id that stays the same when other things are added, moved or deleted, and every other tool refers to things by those ids.

  • Editing. Each edit tool sends one apply request of one or more commands (set_notes, add_channel, add_clip, …). Lemon Studio applies the batch all or nothing and records it as one undo step named after the client ("Claude: Write notes"), so you can undo it with Cmd+Z like any edit of your own.

  • Units. Tools take positions and lengths in beats and convert them to ticks with the project's ppq. Notes are MIDI numbers (60 = middle C).

  • Playing. play, pause and stop drive the transport the same way the toolbar buttons do.

  • Changes from the app. get_changes lists what changed since a given point, including your own edits in the window, so Claude can catch up on what you did.

  • Errors. When Lemon Studio refuses a command ("no pattern 99999"), the message comes back to Claude as a tool error.

Nothing needs installing inside the app: the control plane is part of Lemon Studio.

Related MCP server: Ableton Live MCP

Requirements

  • macOS (Linux works too), Python 3.10+

  • Lemon Studio built from source, with the play / pause / stop / transport control methods. Until they are upstream, build it from the control-plane-transport branch of calvinw/Lemon-Studio:

    git clone -b control-plane-transport https://github.com/calvinw/Lemon-Studio.git
    cd Lemon-Studio && cargo build -p os-app --release

    Upstream Lemon Studio works too, without the transport and view tools (play, pause, stop, transport_status, select).

Install

cd LemonStudioMCP
./install.sh /path/to/Lemon-Studio/target/release/lemonstudio

The installer creates .venv, installs the package, and registers the server with Claude Code (claude mcp add) with LEMON_STUDIO_BIN pointing at the app so Claude can start it.

Use

Start Lemon Studio listening on the socket:

lemonstudio --control /tmp/lemon.sock            # with its window
lemonstudio --serve /tmp/lemon.sock song.flp     # no window

or just ask Claude: the lemon_launch tool starts it. Then, for example: "Make a 4-bar lo-fi beat at 85 BPM with a Lemon Trio bass line and play it."

Claude.ai (remote connector)

Run the server over HTTP and expose it through a tunnel:

.venv/bin/python -m lemon_studio_mcp --transport http --port 8000 --host 0.0.0.0
cloudflared tunnel --url http://localhost:8000

Add https://<random>.trycloudflare.com/mcp as a custom connector at claude.ai/customize/connectors. Only the MCP server is exposed; the app's socket stays local. The tunnel URL gives anyone who has it control of the app, so stop the tunnel when you are done.

Settings (environment)

Variable

Default

Meaning

LEMON_SOCKET

/tmp/lemon.sock

The app's control socket

LEMON_PORT, LEMON_TOKEN

Use a local TCP port instead (the app prints the token)

LEMON_STUDIO_BIN

lemonstudio on PATH

The app, for lemon_launch and render_project

LEMON_CLIENT_NAME

Claude

The name on undo steps

To have every change confirmed in the app, start Lemon Studio with --control-level propose and ask Claude to use propose_commands.

Tools

Area

Tools

Connection

lemon_launch, lemon_status

Reading

get_project_state, get_notes, list_plugins, get_plugin_params, get_history, get_changes

Notes

write_notes (replace or add), clear_notes

Channels

add_channel, set_channel, delete_channel, load_sample

Patterns, playlist

add_pattern, set_pattern, delete_pattern, place_pattern, set_clip, delete_clip

Mixer, plugins

add_mixer_track, set_mixer_track, route_mixer, set_eq, add_effect, set_effect, remove_effect, set_plugin_param

Project

set_tempo, set_time_signature, set_project_settings, save_project, render_project

Transport, view

play, pause, stop, transport_status, select (show a pattern and channel in the piano roll)

Undo, raw

undo, redo, apply_commands, propose_commands

Positions and lengths are in beats by default (unit="ticks" for raw ticks). Note keys are MIDI numbers, 60 = middle C.

Lemon Studio changes

The control plane had no transport control and no way to change what the window shows. Five methods were added to Lemon Studio in the control-plane-transport branch of the fork (crates/os-app/src/control/rpc.rs, with tests, documented in docs/control-plane.md). They are not yet proposed upstream.

Method

Level

transport

read

state (playing / paused / stopped), mode, current pattern, position, ppq, bpm

play

edit

mode, pattern, from optional; as the Play button

pause, stop

edit

as the Pause and Stop buttons

select

edit

pattern, channel, piano_roll optional; makes the pattern current, selects the channel, opens the piano roll scrolled to its notes

Not yet

What Lemon Studio's control plane doesn't offer yet: automation clips, markers, presets, channel envelopes, note fields beyond key/velocity/pan, rendering the open project without saving it first, and pushed change events (the get_changes tool polls).

Found while using it:

  • No presets. Instrument sounds are picked by hand in each plugin's window.

Development

docs/DEVELOPMENT.md covers both repositories and the fork branch, the local clone setup, the Lemon Studio change in detail, how the server is built, known gaps, and next steps.

Tests

LEMON_STUDIO_BIN=/path/to/lemonstudio .venv/bin/pytest -v

They start Lemon Studio headless on a temporary socket and drive it through a real MCP client over stdio.

Available Tools

42 tools
add_channelA

Add a channel played by instrument plugin (a name from list_plugins, e.g. "Lemon Trio"), or a sampler with sample (an audio file path) loaded. mixer_track: a mixer track id to route it to. Returns its id.

Inputs: name, plugin, sample, mixer_track.

add_effectA

Add effect plugin (a name from list_plugins) to mixer track track (an id), in slot 0..9 or the first free one. Returns the slot plugin's id (for get_plugin_params / set_plugin_param).

Inputs: track (required), plugin (required), slot.

add_mixer_trackB

Add a mixer insert (at insert number at, else after the last). Returns its id.

Inputs: at.

add_patternC

Add a pattern. Returns its id.

Inputs: name, color.

apply_commandsA

Run raw control-plane commands (Lemon Studio docs/control-plane.md) as ONE undo step, all or nothing. Positions and lengths here are raw ticks. Each command is {"op": ..., fields...}, e.g. {"op": "add_channel", "name": "Bass", "plugin": "Lemon Trio"}. Returns each command's result.

Inputs: commands (required), name.

clear_notesC

Remove the notes of channel from pattern pattern (or all of its notes when channel is None).

Inputs: pattern (required), channel.

delete_channelB

Delete channel channel (an id), with its notes, clips and automation.

Inputs: channel (required).

delete_clipC

Remove playlist clip clip (an id).

Inputs: clip (required).

delete_patternB

Delete pattern pattern (an id) and its playlist clips.

Inputs: pattern (required).

get_changesA

What changed after event number since (0: all kept), including the user's own edits in the app, and last, the newest event number to pass next time.

Inputs: since.

get_historyA

The undo history: each step's name and whether it is undone.

Inputs: no named parameters.

get_notesC

The notes of pattern pattern (an id): channel id, position, length, key, velocity, pan.

Inputs: pattern (required), unit.

get_plugin_paramsC

The parameters (id, name, value 0..1, value text, ...) of the plugin playing channel id or effect slot id owner.

Inputs: owner (required).

get_project_stateA

The whole project: tempo, ppq, time signature, channels, mixer tracks (with effect slots and sends), patterns, the playlist's clips, the current pattern and song/pattern mode. Positions here are raw ticks.

Inputs: no named parameters.

lemon_launchA

Start Lemon Studio listening on the control socket (does nothing if it already is).

project: an .flp file to open. headless: run with no window (--serve) instead of the app with its window.

Inputs: project, headless.

lemon_statusB

Whether Lemon Studio is connected, the app's version, this client's permission level, and the transport.

Inputs: no named parameters.

list_pluginsB

The instrument and effect plugin names that add_channel and add_effect take.

Inputs: no named parameters.

load_sampleB

Load audio file path into sampler channel channel (an empty path clears it).

Inputs: path (required), channel (required).

pauseA

Pause playback in place (play resumes).

Inputs: no named parameters.

place_patternB

Place pattern pattern (an id) in the playlist on track track (1-based) at position. Returns the clip id.

Inputs: pattern (required), position (required), unit, track, +1 more.

playB

Start playback, as the Play button (resumes when paused).

mode: "pattern" loops the current pattern, "song" plays the playlist (default: the app's PAT/SONG switch). pattern: make this pattern (an id) the current one. from_position: where to start.

Inputs: mode, unit, pattern, from_position.

propose_commandsB

Like apply_commands, but Lemon Studio asks the user to Apply or Discard. Returns the proposal number and what it would change.

Inputs: commands (required), name.

redoB

Redo the last undone step.

Inputs: no named parameters.

remove_effectB

Remove the effect in slot slot (0..9) of mixer track track (an id).

Inputs: slot (required), track (required).

render_projectA

Render a saved .flp project to an audio file (.wav), with no window and no audio device. Save the open project first (save_project) to render what is in the app.

Inputs: out (required), project (required), rate, seconds, +1 more.

route_mixerA

Send mixer track from_track to to_track (ids) at level 0..1; level None removes the route.

Inputs: to_track (required), from_track (required), level.

save_projectB

Save the project (to path, else its own file). With the app's window open, the user is asked first.

Inputs: path.

selectA

Change what the user sees, not the music: make pattern (an id) the current pattern, select channel (an id) in the channel rack, and with piano_roll=True open the piano roll on them (False closes it). While the piano roll is shown it scrolls to the channel's notes. No undo step. Use it after writing notes so the user sees them.

Inputs: channel, pattern, piano_roll.

set_channelA

Change channel channel (an id): volume 0..1, pan -1..1, pitch in cents, color "#rrggbb", enabled (False mutes it), mixer_track (a mixer track id). Only the fields given change.

Inputs: channel (required), pan, name, color, +4 more.

set_clipC

Move, resize or mute playlist clip clip (an id).

Inputs: clip (required), unit, muted, track, +2 more.

set_effectA

Turn effect slot slot (0..9) of mixer track track (an id) on or off, or set its mix 0..1.

Inputs: slot (required), track (required), mix, enabled.

set_eqA

Set band 0..2 of mixer track track's built-in 3-band EQ: gain in dB (-18..18), frequency and width as stored (0..65536).

Inputs: band (required), track (required), width, gain_db, +1 more.

set_mixer_trackC

Change mixer track track (an id): volume 0..1 (0.8 = unity), pan and stereo -1..1, enabled (False mutes).

Inputs: track (required), pan, name, color, +4 more.

set_patternC

Rename, recolor, or set the length of pattern pattern (an id). length 0 = automatic.

Inputs: pattern (required), name, unit, color, +1 more.

set_plugin_paramA

Set parameter param (an id from get_plugin_params) of the plugin of channel or slot owner to value 0..1.

Inputs: owner (required), param (required), value (required).

set_project_settingsC

Swing 0..1, main volume 0..1 (0.8 = unity), main pitch in cents, and the song info.

Inputs: genre, swing, title, author, +3 more.

set_tempoB

Set the tempo (10..522 BPM).

Inputs: bpm (required).

set_time_signatureB

Set the time signature (denominator 2, 4, 8 or 16).

Inputs: numerator (required), denominator (required).

stopA

Stop playback and rewind to where it started.

Inputs: no named parameters.

transport_statusC

Playing, paused or stopped; pattern or song mode; the current pattern; the playhead position; the tempo.

Inputs: unit.

undoA

Undo the last step (anyone's: this client's or the user's).

Inputs: no named parameters.

write_notesA

Write notes into pattern pattern (an id).

notes: [{"channel": id, "position": 0, "length": 0.5, "key": 60, "velocity": 100, "pan": 64}, ...]; position/length in unit (beats by default). channel fills in notes that leave theirs out. replace=True: these become the notes of channel in the pattern (or all of its notes when channel is None); replace=False: they are added to what is there.

Inputs: notes (required), pattern (required), name, unit, +2 more.

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3/5.0

    Scored across 42 tools

    Disambiguation4/5

    Nearly every tool pairs a distinct resource with a distinct action (channel vs pattern vs clip vs mixer track vs effect slot vs plugin param), and descriptions explicitly disambiguate near-pairs like set_clip vs place_pattern and set_effect vs set_plugin_param. The main overlap is apply_commands, which is a raw escape hatch that can do anything the individual tools do, and there is mild redundancy between transport_status, lemon_status and get_project_state on transport/mode info.

    Naming Consistency4/5

    The dominant convention is verb_noun (get_notes, write_notes, add_channel, set_mixer_track, delete_pattern, route_mixer, remove_effect, save_project), applied consistently across resources. Deviations are minor and idiomatic: bare transport verbs (play, pause, stop, undo, redo), the select verb, a noun-first transport_status, and the lemon_ prefixed lifecycle pair (lemon_launch, lemon_status).

    Tool Count2/5

    42 tools is heavy even for a full DAW control surface; the domain (channels, patterns, notes, playlist, mixer, effects, plugins, transport, project, render) justifies breadth, but many setter tools are one-field wrappers that apply_commands already covers generically. The surface could be consolidated (e.g. generic update_* tools or dropping per-field setters) without losing capability.

    Completeness4/5

    Coverage is broad and lifecycle-complete for most objects: channels, patterns, notes, clips, mixer tracks, effects and plugin params all have create/read/update/delete, plus transport, undo/redo, save and render. Gaps are moderate rather than blocking: no delete_mixer_track, no tools for automation clips/points despite delete_channel mentioning automation, and no way to open/close a project beyond the lemon_launch argument.

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      B
      maintenance
      MCP server for headless, agent-native control of openDAW, enabling programmatic music production with tracks, instruments, effects, MIDI, automation, and rendering via 543 tools.
      100
      Apache 2.0
    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables MCP clients to programmatically control an Ableton Live 12 session, including tracks, clips, MIDI notes, devices, parameters, mixer, scenes, warping, and rendering, over streamable HTTP.
      1
      MIT
    • A
      license
      C
      quality
      C
      maintenance
      Enables end-to-end control of FL Studio on macOS through MCP clients, including transport, patterns, mixer, piano roll, playlist, live metering, browser navigation, and rendering.
      160
      MIT
    • A
      license
      C
      quality
      B
      maintenance
      Enables podcast producers and agents to manage multitrack projects, run per-track transcription, perform transcript-driven edits, process audio via FFmpeg, and inspect a read-only DAW timeline over MCP.
      183
      MIT