Lovely Composer MCP
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Lovely Composer MCP Write me a 16-bar 8-bit loop in A minor with melody, bass and drums."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Lovely Composer MCP
An MCP server that lets an AI agent compose chiptune music in Lovely Composer on your own machine.
It is a zero-dependency Node.js server speaking the stdio transport. It works by
reading and writing Lovely Composer's .jsonl project files, so an agent can
create songs, write melodies / bass / drums / chords, change tempo and loop
points — and you just open the folder in Lovely Composer to listen to it, edit
it, or export it.
Why this approach
Driving a GUI is brittle: window focus, timing, and whatever else you are doing on the computer all get in the way. Lovely Composer stores a song as a plain two-line JSON file, so this server edits the data directly. That means:
no GUI automation, no window focus, no interference with your desktop;
the agent gets a precise, inspectable data model instead of pixels;
the files it writes are the exact files Lovely Composer itself writes — see Verification for how that is proven.
Related MCP server: OpenUtau-MCP
Requirements
Node.js 18 or newer (for
TextDecoderwith CJK codecs).Lovely Composer installed. It is auto-detected in the usual Steam locations and in
Documents\LovelyComposer; override withLC_MUSIC_ROOT.
Install
Clone it anywhere and point your MCP client at src/server.js.
git clone https://github.com/baichuan4167-lang/lovely-composer-mcp.git
cd lovely-composer-mcp
node test/smoke.js # optional: prove it works on your machineThere is nothing to npm install — the server has no dependencies.
Generic MCP client configuration
Most clients take the same stdio server block:
{
"mcpServers": {
"lovelycomposer": {
"command": "node",
"args": ["/absolute/path/to/lovely-composer-mcp/src/server.js"]
}
}
}For Claude Desktop that goes in claude_desktop_config.json. Some hosts add a
cwd and a failOnStartupError flag; both are harmless here.
Environment variables
Variable | Used by | Purpose |
| server | Force the music library root instead of auto-detecting it. |
| server | Pin the negotiated MCP protocol revision (default |
|
| Path to a real |
|
| Path to |
Then just talk to your agent:
Use Lovely Composer to write me a 16-bar 8-bit loop in A minor with a melody, a bass line and drums.
Read song 00 in the DSH folder and raise page 2's melody by an octave.
Tools
Tool | What it does |
| Reports the install path, music root, writability and the data model. Call this first. |
| Lists the song folders in the music library. |
| Lists songs in a folder (title / speed / pages / notes per channel). |
| Reads a song and prints each channel as compact per-page patterns. |
| Creates an empty song (and the |
| The main composing tool. Writes one page of one channel from a pattern string. |
| Places notes at exact |
| Erases the given channels and pages. |
| Title, author, speed, page count, ticks per page, loop points, scale. |
| Every instrument preset, effect letter and scale. |
| Copies a song (handy to start from an existing tune). |
| Deletes a song file; requires |
Pattern string syntax
lc_write_page takes one whitespace-separated token per tick, starting at tick
0, up to 32 ticks per page:
C5@25 . . E5 . G5@25 . . - . . . C5 with preset 25; "." empty; "-" holdToken | Meaning |
| empty tick |
| hold: extend the previous note |
| hold with fade-in / fade-out |
| note using the page's default instrument |
| note with instrument preset 25 |
| instrument by name |
| plus volume |
| a raw Lovely Composer voice string, used verbatim |
Common presets (full list via lc_list_instruments):
0 pulse · 1 triangle · 2 square · 3 noise · 4 piano · 7 drum ·
16 sawtooth · 25 flute · 30 stomp (kick) · 33 punch (snare) ·
35 short-freq noise (hat) · 47 low-reso triangle (bass) · 56 bell ·
68 fast arpeggio.
Effect letters: N none · S slur/slide · V vibrato · F fade out ·
I fade in · D drop · H hop · A fast arpeggio · P phaser, and more.
Data model
A song is one folder entry, <music>/<FOLDER>/<NN>.jsonl (NN = 00..99):
5 channels: 0–3 melodic, 4 = the chord track.
Each song has
pagespages, each page up to 32 ticks.Every page carries its own length (
ticksPerPage) and speed.Default
barsPerPage=4: 4 bars per page, 8 ticks per bar.Scientific pitch notation:
C4is middle C, range A0–C8.Tempo:
bpm = 900 × barsPerPage / speed, so lowerspeedmeans a faster song.speed=30, bars=4gives 120 BPM.
Chord-track notes use Lovely Composer's chord encoding:
id = ((type + velocity<<4 + seventh<<6 + ninth<<8) << 16) + 65536, where type
is 1=major, 2=minor, 3=sus4, 4=aug, 5=dim. With lc_set_notes you just write
chord: "minor".
⚠️ Lovely Composer rewrites the whole folder on save
This matters more than anything else in this README:
Lovely Composer reads the entire folder when it opens it and rewrites all 100 song files when it saves. Reload the folder (or restart LC) to see what the agent wrote.
If LC currently has the folder open, do not press save in LC before reloading, or it will overwrite what the agent just wrote.
Songs LC marks as write-protected (
write_protected_flag; all the bundled sample songs are) are refused by default. Passforce=trueto override.
The file format
The serialization is a straight obj.__dict__ dump tagged with the class name:
{"__LCVoice__": true, "n": 60, "t": 1, "v": 4, "f": 0, "id": 2, "x": 12, "p": 0, "e": 0}n pitch (null empty, -1 rest) · t oscillator · v volume 0-7 ·
f effect · id instrument preset · x expression · p pan · e envelope.
The __LCVoice__ tag has to be the first key. LC's json_loader_hook walks
the dict and rebuilds a real object from the first key that names a class it
knows; without the tag the voice degrades to a plain dict and LC then breaks on
attribute access.
A song file is exactly two lines (CRLF-separated, no trailing newline after the second — matching what LC writes):
{"__LCMusicDataHeader__": true, ...}
{"__LCMusic__": true, "speed":…, "channels":{…}, "rhythms":{…}, …}LC builds a default instance and merges the loaded dict over it, so omitted keys keep LC's own defaults. This implementation writes only the keys it actually needs and lets LC supply wave memory, sampling modulators and the rest.
Container hierarchy: LCMusic → LCChannelList.channels[5] →
LCSoundList.sl[pages] → LCSound.vl[32] → LCVoice.
⚠️ Encoding: LC reads files with the system ANSI code page, not UTF-8
LC reads and writes project files with Python's open(path) and no explicit
encoding, so it uses the locale default — GBK/cp936 on a Chinese Windows,
cp932 on a Japanese one.
A UTF-8 file with a non-ASCII title makes LC raise UnicodeDecodeError, so
load_music() fails and the song simply will not open. The trap is subtle
because LC's own sample songs have pure-ASCII titles.
How this project handles it:
Writing — every non-ASCII character is escaped to
\uXXXX, so the file is pure ASCII on disk. Pure ASCII decodes identically under GBK and UTF-8, so it works on any locale.Reading — try strict UTF-8 first, then fall back through GB18030 / GBK / Big5 / Shift_JIS, taking the first result that
JSON.parseaccepts (LC stores non-ASCII as raw GBK bytes when it saves).
Tempo and grid: barsPerPage drives both BPM and resolution
bpm = 900 × barsPerPage / speed, and a page always plays ticksPerPage ticks,
so changing barsPerPage changes both the tempo and the editable resolution:
barsPerPage | ticksPerPage | ticks per bar | finest note | for 180 BPM use |
4 (default) | 32 | 8 | eighth note | speed 30 → 120 BPM |
2 | 32 | 16 | sixteenth note | speed 10 |
1 | 32 | 32 | thirty-second note | speed 5 |
For J-core / denpa sixteenth-note runs, use barsPerPage=1, ticksPerPage=32:
one page is then one bar, and 1 tick = a 32nd note.
Instrument preset parameters come from the game's app/lcl/common.py
(VOICE_STR_LIST, after LC's own expression/pan post-processing) and were
checked entry by entry against real project files.
Verification
The claim "the files it writes are real Lovely Composer files" is tested with the game's own code, not just with this project's parser:
$py = "<LovelyComposer>\app\python\python.exe"
# Full validation: model types, containers, and an LCJSONEncoder round trip
& $py -u tools/validate_with_lc.py "<music>\DSH\01.jsonl"
# Replay lcl.load_music() stage by stage, next to an LC sample song
& $py -u tools/diagnose_load.py "<music>\DSH\01.jsonl"With LC_APP_DIR unset these search the usual Steam locations; set it to
<LovelyComposer>\app if your install lives somewhere unusual.
diagnose_load.py prints every stage of LC opening a song. A generated song
reaches exactly the same stages as an LC sample:
[1] read+parse OK header=LCMusicDataHeader music=LCMusic title='…'
[2] trim channels OK 5 remain
[3] version check OK 16 <= 16
[4] _old_lcmusic_data_updater OK
[5] model usable OK pages=48 speed=5 bpm=181 notes=2158
[6] set_wave_memory_from_lcmusic_settings ... (doxel audio layer, needs a writable game dir)Stage 6 aborts the process: doxel insists on writing its log inside the game install directory and calls
os._exit()when that is denied, e.g. under a restricted sandbox. That is unrelated to the data format — LC's own sample songs stop at the same point in the same environment.
Included demos
Two songs were written with this server and live in the DSH folder of the
author's library:
# | Title | Notes |
| Neon Loop | 8-bar beginner loop, A minor, melody / bass / drums / chords. |
| 配信中毒 - STREAM OVERDOSE | 48-bar piece, 180 BPM, F# minor, denpa / J-core, 2158 notes. |
compositions/stream-overdose.js regenerates the second one.
Development
node test/smoke.js # 36 self-checks: round trips, encoding fallback, DSL
node test/demo-song.js # compose a demo song into .tmp-test/DEMO
node test/demo-song.js DSH 1 # write straight into a real library folder
node src/server.js < test/protocol-probe.jsonl # exercise the JSON-RPC layerThe self-test covers note-name conversion, the instrument table, chord encoding, the pattern DSL, full song round trips, write protection and the GBK fallback. When a real Lovely Composer install is present it also round-trips a song the game wrote — and silently skips that part when there is none, so CI stays green.
Files
File | Purpose |
| Format engine: constants, note/instrument/chord conversion, pattern parsing, project file I/O and encoding. |
| The 12 MCP tool definitions and their implementations. |
| stdio JSON-RPC MCP server (zero dependencies, hand-written protocol layer). |
| Full composition generator (denpa / J-core, 48 bars, 2158 notes). |
| Self-test suite. |
| Demo composer that goes through the tool handlers. |
| JSON-RPC probe requests. |
| Validates a file with the game's own |
| Replays LC's |
| Finds the game install so the validators can import |
| Flags song files containing non-ASCII bytes. |
| Structural analyzer used during reverse engineering. |
Known limitations
It can only write project files; it cannot trigger an export. Export to WAV/MIDI from Lovely Composer itself, or set up an LC addon to do it.
It does not drive LC's GUI, so it never depends on window focus.
Wave memory and sampling modulator parameters are left to LC's defaults and are not editable here.
The rhythm track (
rhythms) uses LC's new-song defaults; there is no editing interface for it yet.
License
Available Tools
12 toolslc_clear_pagesC
Erase every note in the given channels and pages (defaults to all of them).
Inputs: song (required), folder (required), force, pages, +1 more.
lc_copy_songA
Copy a song to another folder/slot (useful to start from an existing tune). Clears write protection.
Inputs: song (required), folder (required), toSong (required), toFolder (required), +2 more.
lc_create_songA
Create a new empty song file at //.jsonl. Fails if the song already exists unless force=true. Returns the usable tick range and an approximate BPM.
Inputs: song (required), folder (required), force, pages, +10 more.
lc_delete_songA
Delete a song file. Requires force=true; there is no undo.
Inputs: song (required), folder (required), force.
lc_list_foldersA
List the song folders inside the Lovely Composer music root, with song counts and a few titles.
Inputs: no named parameters.
lc_list_instrumentsA
List every instrument preset (id + name), the note modifier syntax, the effect letters, and the available scales. Call this before composing so you pick real presets.
Inputs: no named parameters.
lc_list_songsB
List the songs (00..99) in one folder with title, speed, page count and note counts per channel.
Inputs: folder (required).
lc_read_songA
Read a song and print its settings plus each channel as compact per-page patterns ("C5@2 . . E5@2 ..."), so you can inspect and edit existing music.
Inputs: song (required), folder (required), pages, channels, +1 more.
lc_set_notesA
Place individual notes at exact (channel, page, tick) positions — use when a pattern string is awkward. Each event needs channel/page/tick plus note and either instrument (preset id or name) or chord (major/minor/sus4/aug/dim, chord track only). Optional volume/effect/expression/pan/envelope, or clear=true to empty that tick. Events that fail are reported and skipped.
Inputs: song (required), notes (required), folder (required), force.
lc_set_song_optionsC
Change song-level settings: title, editor, speed, page count, ticks per page, loop points, scale.
Inputs: song (required), folder (required), force, pages, +10 more.
lc_statusA
Report the detected Lovely Composer installation, music folder root, whether it is writable, and the song data model (channels, pages, ticks, note range). Call this first.
Inputs: no named parameters.
lc_write_pageA
Write one page of one channel from a compact pattern string — the main composing tool. One whitespace-separated token per tick starting at tick 0: "." or "R" = empty, "-" = hold the previous note, "<"/">" = hold with fade-in/fade-out, "C5" = note, "C5@25" = note with instrument preset 25. Optional modifiers: *volume(0-7) +effect(letter, e.g. +S slur) ^expression(0-F) ~pan(0-F) %envelope(0-F). Example: "C5@25 . . E5 . G5@25 . . - . . ." The page is replaced entirely.
Inputs: page (required), song (required), folder (required), channel (required), +4 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 12 tools
Each tool has a clear, distinct purpose: status, listings, read/create/write/set/clear/copy/delete operations are all separated by resource and action granularity. The closest pair, lc_write_page and lc_set_notes, is distinguished by bulk page replacement versus individual note placement, eliminating confusion.
All tools share the lc_ prefix and follow a verb_noun snake_case pattern (list_folders, read_song, create_song, etc.). The only deviation is lc_status, which uses a noun instead of a verb such as get_status, but the overall pattern remains highly predictable.
With 12 tools, the set is well-scoped for the domain: it covers environment inspection, navigation, song lifecycle, page/note editing, options, and instrument reference without redundancy or bloat. Each tool earns its place in a typical composition workflow.
The tool surface covers the full song lifecycle (create, read, edit, copy, delete), plus detailed editing (page patterns, individual notes, clearing) and reference data (instruments, scales). Minor gaps like a dedicated rename tool or an undo operation are non-critical and workaroundable via copy/delete.
Maintenance
Related MCP Connectors
Read and edit DAW projects as an agent, save native Logic projects, convert projects between DAWs
Autonomous music production for AI agents with MIDI generation, QC and provenance.
Make and edit instrumental music from your agent: songs you can keep changing, MP3/WAV export
Read and edit DB Planner database schemas, diagrams and board layouts as an AI agent.
Related MCP Servers
- AlicenseAqualityDmaintenanceA game-agnostic MCP toolkit for composing chiptune/retro MIDI and rendering game audio, enabling AI agents to generate multi-track MIDI files and render them to OGG/WAV.7MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI agents to generate, inspect, and micro-tune OpenUtau .ustx project files and DiffSinger neural expression curves.5MIT
- AlicenseBqualityDmaintenanceEnables AI agents to create, modify, and save LMMS music projects programmatically, including tracks, notes, mixer control, effects, and song settings.4523MIT
- AlicenseBqualityBmaintenanceismail is a DAW your agent drives through MCP, CLI or Python. The agent writes notes, instruments, effects and automation as data. ismail's own engine renders the audio, and every render comes back as text the agent can read. No AI model generates the audio. It runs on your own machine. It is free and open source. https://github.com/newsbubbles/ismail373329 PyPI18MIT