Dedicated to John Philip Sousa (1854–1932) — "The March King"
Fmr. Director of "The President's Own" United States Marine Band
Sousa led "The President's Own" from 1880–1892 and wrote the American march canon — "The Stars and Stripes Forever" (the National March of the United States), "Semper Fidelis" (the official march of the U.S. Marine Corps), and "The Washington Post." He invented the sousaphone and, before recording was common, put a marching band in every American's ear. He built music that a whole ensemble reads off one score — which is exactly what this project generates: an editable multitrack FL project, not a flat mixdown.
Sibling to the Dix (VibeComposing) Analyzer, which is dedicated to Maj. Brian Dix of "The Commandant's Own" — the two Marine-music namesakes of the As30p toolchain.
An MCP server that lets Claude Code (and any MCP client) interact with FL Studio — drive the running DAW and generate/edit FL projects programmatically.
Sister project: the Dix (VibeComposing) Analyzer — a native VLC visualizer that sees a track's spectrum, key, and loudness. Where the Analyzer reads a finished mix, this server writes the FL project behind it. Both grew out of the As30p music toolchain, and both are dedicated to a Marine bandmaster.
FL Studio exposes no external REST / OSC / AppleScript API. Its only programmatic
surface is a built-in Python API (14 modules, 427+ functions: transport, mixer,
channels, patterns, playlist, plugins, …) intended for MIDI-controller scripts
that run inside FL. This project wraps that surface — plus offline project-file
tooling — as clean MCP tools.
| Route | Module | What it does | Runs | Status |
|---|---|---|---|---|
| A — PyFLP | routes/pyflp_route.py |
Read/write .flp project files directly (tempo, title, metadata, channel names). |
Offline, no FL running | ✅ implemented |
| B — Flapi | routes/flapi_route.py |
External client → a server script inside FL → the 427-function API (transport, mixer, channels, hint, eval). | Live, FL open | ✅ implemented (needs FL-side setup) |
| C — Piano Roll | routes/script_route.py |
A library of flpianoroll scripts, installed into FL's Piano Roll tools menu. |
One-off in FL | ✅ implemented |
Full breakdown, links, and trade-offs: docs/ARCHITECTURE.md.
Route A is the deterministic, offline route: no FL Studio needs to be running. New
projects are minted from a bundled copy of FL's own Empty template
(src/fl_studio_mcp/templates/empty.flp), so every generated .flp opens cleanly in FL.
Tools registered by the server:
| Tool | What it does |
|---|---|
flp_info(path) |
Inspect an .flp — FL version, PPQ, tempo, title/artists/genre/comments, channel & pattern names. |
flp_create(out_path, title?, tempo?, artists?, genre?, comments?) |
Create a new .flp (from the Empty template) with the given metadata. |
flp_load_samples(out_path, samples, title?, tempo?, arrange?, stagger_bars?, …) |
Create a new .flp with one Sampler channel per audio file — every clip loaded (and named) in the Channel Rack. Clones the template's Sampler at the event level and injects a SamplePath per channel. With arrange=true, also drops each stem as a full-length Audio Clip on the Playlist (one per track, staggered stagger_bars apart) — opens already arranged. |
flp_set_tempo(path, bpm, out_path?) |
Set tempo (BPM). Edits in place, or writes a copy to out_path. |
flp_set_metadata(path, title?, artists?, genre?, comments?, out_path?) |
Set metadata; only the fields you pass change. |
flp_rename_channel(path, index, name, out_path?) |
Rename a channel by 0-based index. |
Plus ping (health check) and routes (route status).
Scope, honestly: Route A v1 covers create-from-template, inspect, and metadata/tempo/ channel-name edits — the surface PyFLP actually supports for round-trip writes. Composing new channels, patterns, and notes from scratch is not exposed by PyFLP's public model API; that's the job of Route B (Flapi), where FL itself does the note-making. See BACKLOG.md.
Compatibility note: PyFLP 2.2.1 can't parse a single event on modern CPython — its memberless
EventEnumtrips aTypeErrorinenum.Enum.__new__before the_missing_hook runs.fl_studio_mcp/_compat.pyinstalls a tiny, targeted shim that routes the lookup back through PyFLP's own resolver, without patching PyFLP's source.
FL 2025 arrangement note: with
arrange=true,flp_load_samplesdrops each stem as a native FL Audio Clip on the Playlist, so the project opens already arranged. Two FL Studio 2025 (25.x) details make this non-trivial, both reverse-engineered byte-for-byte from FL-native saves: (1) a Playlist audio clip can only reference an Audio Clip channel (ChannelID.Type == 4) — a plain Sampler (Type 0) is silently dropped — so the writer promotes each channel; and (2) FL 2025 stores each freshly-placed playlist item as an 80-byte record (32-byte core + 48-byte trailer) that PyFLP 2.2.1's 32/60/68 struct can't round-trip, so the writer emits it directly. All three FL playlist clip types (audio, automation, pattern) share this 80-byte record; theitem_indexfield selects among them (< 20480→ a channel clip by iid;≥ 20480→pattern_base + pattern#).
Route B drives a running FL Studio through Flapi: an external client talks to a server script inside FL over a virtual MIDI port (macOS IAC Driver, auto-created), so calls to FL's scripting API hit the live session.
One-time FL-side setup:
pip install "fl-studio-mcp-server[live]" # flapi + FL API stubs
# then, via the MCP tool `fl_install_server` (or `python -m flapi install`), and restart FL| Tool | What it does |
|---|---|
fl_status |
Is the live extra installed? Are we connected? (safe — never touches MIDI) |
fl_connect |
Connect to a running FL via Flapi (returns FL's API version). |
fl_install_server |
Install the Flapi server into FL (flapi install); restart FL after. |
fl_hint(message) |
Show a hint in FL's panel — the quickest connectivity smoke test. |
fl_transport(action) |
play / stop / record / toggle the transport. |
fl_get_tempo |
Current project tempo (BPM). |
fl_mixer(index?, volume?) |
Track count / read a track volume / set a track volume. |
fl_channels |
List channel-rack channel names. |
fl_eval_expr(expr) |
Escape hatch: evaluate any expression in the live FL (e.g. patterns.patternCount()). |
Every Route B tool degrades gracefully — with no live extra or no running FL, it
returns a structured {"ok": false, "error": …} instead of crashing the server.
Route C ships ready-made Piano Roll scripts
(flpianoroll) and installs them into FL's Piano roll scripts folder, where they show
up under the Piano Roll's Tools (wrench) dropdown:
| Script | What it does |
|---|---|
transpose |
Shift notes by N semitones. |
humanize |
Subtle random timing/velocity variation for a played-by-hand feel. |
strum |
Roll each chord out over time, like a guitar strum. |
note_repeats |
Echo notes with time / pitch / velocity-decay offsets. |
scale_fill |
Generate a run of notes drawn from a scale (major/minor/modes/pentatonic). |
| Tool | What it does |
|---|---|
piano_scripts_list |
List the bundled scripts + summaries. |
piano_scripts_describe(name) |
Show a script's full source. |
piano_scripts_install(dest?) |
Copy them into FL's Piano roll scripts folder. |
All three routes implemented and tested. Route A and the Route B error-paths / Route C scripts+installer are covered by the pytest suite (no FL needed); the live Route B calls and in-FL script execution require FL Studio with the one-time setup above. See BACKLOG.md / the GitHub Issues tab.
src/fl_studio_mcp/server.py— the MCP server (FastMCP); registers each route's tools.src/fl_studio_mcp/routes/— one module per API route:pyflp_route(A),flapi_route(B),script_route(C).src/fl_studio_mcp/templates/empty.flp— bundled FL Empty template (Route A base).src/fl_studio_mcp/piano_scripts/*.pyscript— the bundled Route C Piano Roll scripts.src/fl_studio_mcp/_compat.py— the PyFLP-on-modern-CPython shim.tests/— pytest suite (Route A round-trips, Route B error-paths, Route C scripts+installer; no FL needed).docs/— architecture & research notes.BACKLOG.md— task list, mirrored to Issues.
- Python 3.11 or 3.12 (PyFLP 2.2.1 does not yet support 3.13+).
- FL Studio 2025 (25.x) — to open Route A projects, and (with the
liveextra) for Routes B/C.
pip install -e . # or pip install -e ".[test]" for the test deps
fl-studio-mcp # starts the MCP server (stdio)Add to your MCP config (e.g. .mcp.json):
{
"mcpServers": {
"fl-studio": { "command": "fl-studio-mcp" }
}
}Then, in Claude Code:
Create an FL project "The Stars and Stripes Forever" at ~/Music/sousa.flp, 120 BPM, artist As30p.
pip install -e ".[test]"
pytest -qThe suite covers all three routes without needing FL Studio: Route A end-to-end
(create, inspect, set-tempo in place and to a new path, metadata, channel rename,
round-trip re-parse against the bundled template); Route B at its error boundary
(missing extra / FL unreachable return clean errors, not crashes); and Route C
(every bundled .pyscript is syntax-checked and follows FL's createDialog/apply
contract, plus the installer). CI runs it on Python 3.11 and 3.12 for every push/PR
(.github/workflows/ci.yml).
Apache-2.0 — see LICENSE.
Built live with Dix. As30p / Ronin 48.
Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/
