Skip to main content
Glama
KindaMAD-hav

fl-studio-mcp

by KindaMAD-hav
README.md
# fl-studio-mcp-KindaMAD

Started as a fork of [karl-andres/fl-studio-mcp](https://github.com/karl-andres/fl-studio-mcp). Karl's project is the base of this one: the MCP server, the MIDI + JSON bridge, the controller script that runs inside FL Studio, and the transport, mixer, channel, plugin and piano roll tools. His project in turn credits [calvinw/fl-studio-mcp](https://github.com/calvinw/fl-studio-mcp) for the piano roll approach.

This fork adds what an agent needs to build a full arrangement in FL Studio on Windows without anyone at the keyboard: a tool that runs Python inside FL, screenshot and click tools, a Win32 layer that drives FL's UI without moving the cursor, and a pipeline that turns a song spec into patterns and playlist clips.

## What this fork adds

### Run code inside FL Studio

| Tool | Description |
|------|-------------|
| `fl_exec` | Run Python in FL Studio's interpreter with every API module in scope. Assign to `_result` to return a value. Stdout and tracebacks come back too. |
| `fl_introspect_api` | List what the running FL build actually exposes, module by module. FL ships no type stubs, and the reference is wrong in places. |

Every other tool wraps one API call, so a new capability used to mean a new controller handler, a reinstall and a reload. `fl_exec` skips that, and one call with a loop in it replaces dozens of tool calls.

### See and click (Windows)

| Tool | Description |
|------|-------------|
| `fl_screenshot` | Capture FL's window to a PNG with `PrintWindow`. Works when FL is covered and does not take focus. Optional crop and downscale. |
| `fl_click` | Click at a window-relative position. |
| `fl_press_key` | Send a real keystroke. Needed where the API has no equivalent: `ui.enter()` does nothing to a browser node, a real Enter loads it. |

### Multi-channel piano roll writes

| Tool | Description |
|------|-------------|
| `fl_compose_arrangement` | Write notes to several channels in one call. Selects each channel, focuses its piano roll, writes the notes and triggers the script. |

The piano roll trigger (Ctrl+Alt+Y) used to land in whichever window had focus, usually the terminal running the server, so FL never ran the script. It now finds FL's window, focuses it, sends the key and gives focus back. It also waits on a completion check instead of a fixed delay.

### Win32 automation layer: `utils/fl_window.py`

- Finds FL's main window by its process (`FL64.exe`), not its title, so an Explorer window or browser tab that mentions FL Studio never matches. It also reports the executable path, because FL 2025 and 2026 install side by side and both run as `FL64.exe`.
- Per-monitor DPI aware, so coordinates hold on scaled and mixed-DPI displays.
- Clicks, drags, scrolls and keys are posted straight to FL's message queue. The cursor does not move and your foreground window stays put.
- Detects FL's modal dialogs, which block its script thread and stall the control link. Dismissing them is the one path that uses the real cursor, and it puts the cursor back afterwards.

### Song pipeline: `pipeline.py`

`FLPipeline().build(song)` takes a `Song` (tempo, named patterns holding notes per channel, and an arrangement of `(pattern, playlist_track, start_bar)`) and builds it in FL:

| Part | How |
|------|-----|
| Tempo, patterns, channel selection | Scripting API |
| Notes | Streamed over MIDI and captured with `general.dumpScoreLog` |
| Playlist clips | Posted clicks on the playlist grid |
| Instruments | FL's browser and ADD menu, plus a real Enter keystroke |

Loading an instrument is the only step that needs focus. `scripts/verify_hands_free.py` samples the cursor during a build to check that nothing else moves it. `scripts/demo_pipeline.py` builds a two-section drum arrangement and is the place to start.

The pipeline reads the scripting API version from `general.getVersion()` and gates the newer calls on it (`patterns.setPatternLength`, `playlist.selectTool`, `patterns.clearPattern`, `patterns.duplicatePatternData`).

### What FL's scripting API can't do

Measured on FL Studio 2025 (API 38) and 2026 (API 45). Full notes are in [docs/PIPELINE.md](docs/PIPELINE.md).

- No function places a playlist clip, creates a channel or inserts a mixer FX slot. The pipeline does the first two through FL's UI. FX insertion is not built. `scripts/check_api_gain.py` re-tests all three after an FL update.
- Pattern length is in steps, 16 per bar, not beats as the reference says.
- `selectTool` is in `playlist`, not `ui`.
- `patterns.clearPattern` opens a modal dialog on every call, which blocks the script thread until it is answered. Ticking "Remember my choice" once stops it.
- PyFLP is not a way around this on FL 2025. Its writer produces files FL refuses to open, and its reader misparses FL 2025's 80-byte playlist clip records. `flp_splice.py` is kept as the record of that investigation.

### Status

Working end to end from a `Song`: tempo, patterns, notes, instruments through the ADD menu, and clip placement.

Not done yet:

- Composition helpers (scales, voicings, groove templates). Every note in the demo scripts is written out by hand.
- Mix pass. `build()` does not set mixer levels or plugin parameters yet, although the tools for both work.
- Revision loop. `build(reuse_slots=True)` rebuilds in place, but spec diffing is not written.
- Playlist and browser coordinates in `Layout` were measured at 2560x1380. Re-measure them from a screenshot if your layout differs, or clips land on the wrong bar without any error.

## Requirements

- **FL Studio 20.7+** (MIDI Controller Scripting API). The pipeline was built on FL Studio 2025 (25.2.3) and 2026 (26.1.4).
- **Python 3.10+**
- **Windows** for everything this fork adds. The original tools also run on **macOS**.
  - Windows: [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html)
  - macOS: IAC Driver (built in, needs to be enabled)

## Quick Installation

The setup script is written for macOS. On Windows it cannot set up MIDI or the controller script, so it prints those steps instead. They are covered under [Manual Installation](#manual-installation).

```bash
# Clone the repository
git clone https://github.com/KindaMAD-hav/fl-studio-mcp-KindaMAD.git
cd fl-studio-mcp-KindaMAD

# Run the one-command installer
./install.sh
```

This will:

1. Install [uv](https://github.com/astral-sh/uv) if not present
2. Install Python dependencies
3. Guide you through enabling virtual MIDI ports (IAC Driver on Mac)
4. Install the FL Studio MIDI controller script
5. Install the Piano Roll script (ComposeWithLLM)
6. Configure Claude Desktop or Claude Code automatically

## Manual Installation

### 1. Install Python Dependencies

```bash
# Using uv (recommended)
uv sync

# Or using pip
pip install -e .
```

### 2. Enable Virtual MIDI Ports

#### macOS (IAC Driver)

1. Open **Audio MIDI Setup** (search in Spotlight)
2. Press **Cmd+2** or go to **Window > Show MIDI Studio**
3. Double-click on **IAC Driver**
4. Check **"Device is online"**
5. Click **Apply**

#### Windows (loopMIDI)

1. Download and install [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html)
2. Create a virtual port (any name works)
3. Optional, for the pipeline: create a second port with `FLStudioNotes` in its name. Notes stream over it so a long burst does not wake the controller script for every note. Without it the pipeline uses the control port.
4. Keep loopMIDI running while using FL Studio

### 3. Install FL Studio Scripts

On Windows, with FL Studio closed:

```bash
python scripts/install_controller.py          # show what would change
python scripts/install_controller.py --apply  # install, keeping a backup
```

FL runs a copy of the controller script from its settings folder, and nothing keeps that copy in step with the repo. Re-run this after any change to `fl_controller/device_FLStudioMCP.py` and after every FL Studio update.

Or copy the controller script to FL Studio's Hardware folder by hand:

```bash
# macOS
mkdir -p ~/Documents/Image-Line/FL\ Studio/Settings/Hardware/FLStudioMCP
cp fl_controller/device_FLStudioMCP.py ~/Documents/Image-Line/FL\ Studio/Settings/Hardware/FLStudioMCP/

# Windows
mkdir "%USERPROFILE%\Documents\Image-Line\FL Studio\Settings\Hardware\FLStudioMCP"
copy fl_controller\device_FLStudioMCP.py "%USERPROFILE%\Documents\Image-Line\FL Studio\Settings\Hardware\FLStudioMCP\"
```

Copy the Piano Roll script:

```bash
# macOS
cp scripts/ComposeWithLLM.pyscript ~/Documents/Image-Line/FL\ Studio/Settings/Piano\ roll\ scripts/

# Windows
copy scripts\ComposeWithLLM.pyscript "%USERPROFILE%\Documents\Image-Line\FL Studio\Settings\Piano roll scripts\"
```

### 4. Configure FL Studio

1. **Restart FL Studio** (if it's running)
2. Go to **Options > MIDI Settings**
3. Under **Input**, find your virtual MIDI port (e.g., "IAC Driver Bus 1" or your loopMIDI port)
4. Set the **Controller type** to **FLStudioMCP**
5. Enable the port (click to highlight it)

### 5. Configure Claude

Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):

```json
{
  "mcpServers": {
    "fl-studio": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/fl-studio-mcp-KindaMAD", "fl-studio-mcp"]
    }
  }
}
```

Or for Claude Code, add the same server to your MCP settings.

## Usage

### Running the Server Manually

```bash
# Using uv
uv run fl-studio-mcp

# Or after installation
fl-studio-mcp
```

### Piano Roll Workflow

1. Open FL Studio and select a channel
2. Open the Piano Roll (F7 or double-click the channel)
3. The first time, manually run the script: **Tools > Scripting > ComposeWithLLM**
4. After that, the MCP tools will auto-trigger the script

### Building a Song with the Pipeline (Windows)

1. Open FL Studio with the playlist visible
2. Check the playlist and browser coordinates in `Layout` (`src/fl_studio_mcp/pipeline.py`) against a screenshot of your layout
3. Run the demo, optionally passing the bar to start at:

```bash
uv run python scripts/demo_pipeline.py 1
```

It prints the project state before the build, then each pattern and clip it created.

## Available Tools

The transport, mixer, channel, plugin and piano roll tools below are from the original project. `fl_compose_arrangement` and the scripting and vision tools are described [above](#what-this-fork-adds).

### Connection

| Tool | Description |
|------|-------------|
| `fl_connect` | Connect/reconnect to FL Studio |
| `fl_connection_status` | Get connection status |

### Transport

| Tool | Description |
|------|-------------|
| `fl_play` | Start/pause playback |
| `fl_stop` | Stop playback |
| `fl_record` | Toggle recording |
| `fl_get_transport_status` | Get playback/recording state |
| `fl_set_song_position` | Set playback position |
| `fl_get_song_length` | Get song duration |
| `fl_set_loop_mode` | Switch between pattern/song mode |
| `fl_set_playback_speed` | Adjust playback speed (0.25x-4x) |

### Mixer

| Tool | Description |
|------|-------------|
| `fl_get_mixer_track_count` | Get number of mixer tracks |
| `fl_get_mixer_track_info` | Get track details |
| `fl_get_all_mixer_tracks` | List all tracks |
| `fl_set_track_volume` | Set track volume |
| `fl_set_track_pan` | Set track pan |
| `fl_mute_track` | Mute/unmute track |
| `fl_solo_track` | Solo/unsolo track |
| `fl_arm_track` | Arm track for recording |
| `fl_set_track_name` | Rename track |
| `fl_set_track_color` | Set track color |
| `fl_set_stereo_separation` | Adjust stereo width |

### Channels

| Tool | Description |
|------|-------------|
| `fl_get_channel_count` | Get number of channels |
| `fl_get_channel_info` | Get channel details |
| `fl_get_all_channels` | List all channels |
| `fl_get_selected_channel` | Get selected channel |
| `fl_select_channel` | Select/deselect channel |
| `fl_select_one_channel` | Select channel exclusively |
| `fl_trigger_note` | Trigger MIDI note (real-time) |
| `fl_set_channel_volume` | Set channel volume |
| `fl_set_channel_pan` | Set channel pan |
| `fl_mute_channel` | Mute/unmute channel |
| `fl_solo_channel` | Solo/unsolo channel |
| `fl_set_channel_name` | Rename channel |
| `fl_set_channel_color` | Set channel color |
| `fl_route_channel_to_mixer` | Route to mixer track |
| `fl_get_grid_bit` | Get step sequencer step |
| `fl_set_grid_bit` | Set step sequencer step |
| `fl_get_step_sequence` | Get full pattern |
| `fl_set_step_sequence` | Set full pattern |

### Plugins

| Tool | Description |
|------|-------------|
| `fl_is_plugin_valid` | Check if plugin exists |
| `fl_get_plugin_name` | Get plugin name |
| `fl_get_plugin_param_count` | Get parameter count |
| `fl_get_plugin_params` | List all parameters |
| `fl_get_plugin_param_value` | Get parameter value |
| `fl_set_plugin_param_value` | Set parameter value |
| `fl_get_preset_count` | Get preset count |
| `fl_next_preset` | Next preset |
| `fl_prev_preset` | Previous preset |
| `fl_get_plugin_color` | Get plugin color |

### Piano Roll

| Tool | Description |
|------|-------------|
| `fl_send_notes` | Add notes to the piano roll |
| `fl_send_chord` | Add a chord (multiple notes at same time) |
| `fl_delete_notes` | Delete specific notes |
| `fl_clear_piano_roll` | Clear all notes |
| `fl_get_piano_roll_state` | Read current piano roll notes |
| `fl_trigger_script` | Manually trigger the FL Studio script |
| `fl_get_piano_roll_info` | Get piano roll system info |
| `fl_clear_request_queue` | Cancel pending queued changes |

## Limitations

- The dedicated tools only control what is already in the project. The scripting API cannot load plugins, add channels, place playlist clips or insert mixer FX slots. On Windows the pipeline adds channels and places clips through FL's UI (see [above](#what-fls-scripting-api-cant-do)).
- `fl_trigger_note` is real time only. Notes don't persist unless FL Studio is recording. Use the piano roll tools or the step sequencer for notes that stay.

## Example Workflows

### Adjusting a Mix

```text
"Set the volume of mixer track 1 to 80% and pan it slightly left"
```

### Creating a Drum Pattern

```text
"Create a basic kick pattern on channel 0 with kicks on steps 0, 4, 8, and 12"
```

### Adding a Melody to Piano Roll

```text
"Add a C major arpeggio starting at beat 0: C4, E4, G4, C5 - each note quarter duration"
```

### Adding Chords

```text
"Add a C major chord at beat 0, then F major at beat 2, then G major at beat 4"
```

### Automating Plugin Parameters

```text
"List the parameters of the plugin on channel 0 and set the filter cutoff to 50%"
```

### Querying Anything the Tools Don't Cover

```text
"Use fl_exec to list every pattern with its name and length in bars"
```

## Troubleshooting

### "Not connected to FL Studio"

1. Ensure FL Studio is running
2. Check that the FLStudioMCP controller is enabled in MIDI Settings
3. On Mac, verify IAC Driver is enabled in Audio MIDI Setup
4. On Windows, verify loopMIDI is running
5. Restart FL Studio after enabling the controller

### "Timeout waiting for FL Studio response"

1. Make sure FL Studio is in focus
2. Check the Script output window in FL Studio (View > Script output)
3. Verify the controller is receiving MIDI (look for activity in MIDI Settings)
4. Look for a modal dialog in FL. One blocks the script thread and every command times out until it is answered.

### FL behaves like the controller code is out of date

It probably is. Run `python scripts/install_controller.py` to see the difference between the repo and the copy FL loads, then `--apply` with FL closed.

### Pipeline clips land on the wrong bar or lane

The `Layout` coordinates no longer match your screen. Take an `fl_screenshot`, find bar 1 and bar 2 on the ruler and the first two track labels, and update `Layout`.

### Piano Roll script not triggering

1. First time: manually run **Tools > Scripting > ComposeWithLLM** in FL Studio
2. On macOS: grant Accessibility permissions when prompted
3. Ensure FL Studio is in focus when triggering
4. Try pressing Cmd+Opt+Y (macOS) or Ctrl+Alt+Y (Windows) manually

### No MIDI ports available

- **macOS**: Enable IAC Driver in Audio MIDI Setup
- **Windows**: Install and run loopMIDI

## Architecture

The original project uses a hybrid approach:

```text
┌─────────────────┐     ┌─────────────────────────────────────────┐
│   MCP Client    │────▶│           FastMCP Server                │
│  (Claude, etc)  │     │                                         │
└─────────────────┘     │  ┌─────────────────┐  ┌──────────────┐  │
                        │  │ MIDI + JSON     │  │ Piano Roll   │  │
                        │  │ Tools           │  │ Tools (JSON) │  │
                        │  └────────┬────────┘  └──────┬───────┘  │
                        └───────────┼──────────────────┼──────────┘
                                    │                  │
                               MIDI + JSON        JSON Files +
                                    │              Keystroke
                                    ▼                  ▼
                        ┌─────────────────────────────────────────┐
                        │              FL Studio                   │
                        │  ┌──────────────┐  ┌──────────────────┐ │
                        │  │FLStudioMCP   │  │ Piano Roll Script│ │
                        │  │(MIDI Ctrl)   │  │ (ComposeWithLLM) │ │
                        │  └──────────────┘  └──────────────────┘ │
                        └─────────────────────────────────────────┘
```

### How It Works

1. **Transport/Mixer/Channels/Plugins**:
   - MCP server writes command to JSON file
   - Sends MIDI trigger note to FL Studio
   - FL Studio controller script reads JSON, executes API, writes response
   - MCP server reads response

2. **Piano Roll**:
   - MCP server writes note requests to JSON file
   - Sends keystroke (Cmd+Opt+Y / Ctrl+Alt+Y) to trigger FL Studio script
   - Piano Roll script reads JSON and modifies notes

3. **Added in this fork**:
   - `fl_exec` and `fl_introspect_api` use the same MIDI + JSON link, with two new handlers in the controller script
   - The vision tools and the pipeline also post Win32 messages directly to FL's window
   - The pipeline streams notes over MIDI (a second port if there is one) and reads them back with `general.dumpScoreLog`

## Development

### Prerequisites

- Python 3.10+
- [uv](https://github.com/astral-sh/uv) (recommended)

### Setup

```bash
# Install all dependencies including dev extras
uv sync --dev

# Or with pip
pip install -e ".[dev]"
```

### Available Commands

| Command | Description |
|---------|-------------|
| `uv run fl-studio-mcp` | Run the MCP server |
| `uv run ruff check .` | Lint the codebase |
| `uv run ruff check --fix .` | Lint and auto-fix |
| `uv run pytest` | Run tests |

### Project Structure

```
fl-studio-mcp-KindaMAD/
├── fl_controller/
│   └── device_FLStudioMCP.py    # FL Studio MIDI controller script (runs inside FL Studio)
├── docs/
│   └── PIPELINE.md              # Pipeline design and the measured API capability map
├── scripts/
│   ├── setup.sh                 # FL Studio script installer
│   ├── install_mcp_for_claude.sh # Claude config installer
│   ├── install_controller.py    # Sync the controller script into FL (Windows)
│   ├── ComposeWithLLM.pyscript  # Piano Roll script (runs inside FL Studio)
│   ├── demo_pipeline.py         # Build a two-section arrangement from a Song
│   ├── check_api_gain.py        # Re-test the API walls after an FL update
│   ├── verify_hands_free.py     # Check a build never moves the cursor
│   ├── flp_validate.py          # Walk and compare .flp event streams
│   └── probe_*, diag_*, test_*  # Probes and diagnostics used to map the API
├── src/fl_studio_mcp/
│   ├── server.py                # FastMCP server entry point
│   ├── pipeline.py              # Song spec to FL project
│   ├── flp_splice.py            # Byte-level .flp clip editing (investigation record)
│   ├── tools/                   # MCP tool implementations
│   │   ├── channels.py
│   │   ├── mixer.py
│   │   ├── piano_roll.py
│   │   ├── plugins.py
│   │   ├── scripting.py         # fl_exec, fl_introspect_api
│   │   ├── transport.py
│   │   └── vision.py            # fl_screenshot, fl_click, fl_press_key
│   └── utils/
│       ├── connection.py        # FL Studio connection wrapper
│       ├── fl_trigger.py        # Piano roll keystroke trigger
│       ├── fl_window.py         # Win32 window lookup, focus and posted input
│       ├── midi_connection.py   # MIDI + JSON communication layer
│       └── screenshot.py        # PrintWindow capture
├── fl_api_report.json           # Live API surface from probe_fl_api.py, the baseline for check_api_gain.py
└── install.sh                   # One-command installer
```

## Credits

- [karl-andres/fl-studio-mcp](https://github.com/karl-andres/fl-studio-mcp) - the project this started from
- [calvinw/fl-studio-mcp](https://github.com/calvinw/fl-studio-mcp) - Piano Roll integration approach
- [FL Studio API Stubs](https://github.com/IL-Group/FL-Studio-API-Stubs) - API documentation
- [FastMCP](https://github.com/jlowin/fastmcp) - MCP server framework
- [mido](https://github.com/mido/mido) - MIDI library for Python
- [Image-Line](https://www.image-line.com/) - FL Studio

## License

MIT. See [LICENSE](LICENSE).