lilypond-mcp
# <picture><source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/wspringer/lilypond-mcp/main/assets/logo-dark.svg"><img src="https://raw.githubusercontent.com/wspringer/lilypond-mcp/main/assets/logo-light.svg" alt="" width="52" align="top"></picture> lilypond-mcp
[](https://www.npmjs.com/package/lilypond-mcp)
MCP server that engraves [GNU LilyPond](https://lilypond.org) sources into
placeable assets: cropped PDF, EPS, SVG, and PNG, sized to the music rather
than a full page — ready to drop into page-layout software such as InDesign.
Nothing to install beyond Node: the server fetches a WebAssembly build of
LilyPond on first use and engraves with that — the same engine on every
machine.
## Quick start
Claude Code — `.mcp.json` in your project:
```json
{
"mcpServers": {
"lilypond": {
"command": "npx",
"args": ["-y", "lilypond-mcp@latest"]
}
}
}
```
Claude Desktop — `claude_desktop_config.json`, same entry under
`mcpServers`. Or skip the config entirely: grab the `.mcpb` file from
the [latest release](https://github.com/wspringer/lilypond-mcp/releases/latest)
and open it with Claude Desktop — a desktop extension with the engine
bundled in, so it works offline from the first engrave.
That's the whole setup. On the first engrave, the server downloads the
engine (~35 MB, checksum-verified, cached under
`~/.cache/lilypond-mcp` — safe to delete at any time).
## Tools
- **`engrave_file`** — engrave a `.ly` file. Defaults to cropped PDF —
the format to place in InDesign (fonts embedded and subsetted, all PDF
boxes defined). Also returns a preview PNG, both as a file and inline
as an image in the tool result, so the agent sees what it engraved.
- **`engrave_code`** — engrave LilyPond code passed inline, for iterating
on a musical idea without touching disk.
- **`lilypond_version`** — the LilyPond version the server engraves with,
for picking the right `\version` header.
Both engrave tools accept `formats` (`pdf`/`eps`/`svg`/`png`), `crop`,
`include_dirs` (for shared `\include` libraries), `output_dir`, and
`preview` (default on; turn off to skip the inline image on batch runs). Paths
are resolved against the working directory the server is launched in — for
an `.mcp.json` entry, that is the project root. On failure the result
carries LilyPond's diagnostics, line numbers included, so an agent can fix
the source and retry.
## What you get
This snippet, sent to `engrave_code`:
```lilypond
\version "2.26.0"
\header { tagline = ##f }
\score {
<<
\new ChordNames \chordmode { g2. | c | d | g }
\new Staff \new Voice = "m" \relative c'' {
\key g \major
\time 3/4
\tempo "Waltz" 4 = 132
g4 b d | e4.( d8) c4 | a4 fis d | g2 r4 \bar "|."
}
\new Lyrics \lyricsto "m" { Round and round the waltz goes, old and slow. }
>>
\layout { indent = 0 }
}
```
comes back cropped to the music, as PDF, EPS, SVG or PNG — this is the SVG:
<img src="https://raw.githubusercontent.com/wspringer/lilypond-mcp/main/docs/example.svg" alt="Four bars of a waltz in G major with chord names and lyrics, engraved by LilyPond" width="600">
(Source in [`docs/example.ly`](docs/example.ly).)
<br>
[](https://sidekick.eastpole.nl?utm_source=github&utm_medium=readme&utm_campaign=lilypond-mcp)
<br>
## Engine
Engraving runs a WebAssembly build of LilyPond from
[lilypond-wasi](https://github.com/wspringer/lilypond-wasi) releases,
pinned in `engine.json` and executed in a child Node process via
`node:wasi`. Node 22 or newer: the engine uses WebAssembly exception
handling that V8 first shipped in Node 22. (The `node:wasi` fast-call
regression in Node 22.21.1+ —
[nodejs/node#59600](https://github.com/nodejs/node/pull/59600) — is
sidestepped automatically with `--no-turbo-fast-api-calls`.)
The engine trails lilypond-wasi's stable releases: a weekly workflow
re-pins `engine.json` to the newest release, engraves with it as a real
consumer, and opens a PR.
## Development
```
npm install
npm run build # tsc → dist/
npm test # engraves real snippets with the wasm engine*
```
\* The tests need an engine dir and skip without one. Either download the
pinned release into the cache — `node scripts/assemble-engine-dir.mjs`
prints the dir — or build one from a lilypond-wasi checkout:
`./test/assemble-engine-dir.sh /tmp/engine-dir ../lilypond-wasi stable`.
Then `LILYPOND_MCP_ENGINE_DIR=<dir> npm test`.
Releases: conventional commits → Knope bot release PR → merge → npm
publish via OIDC trusted publishing (no tokens).
## Privacy Policy
Everything happens on your machine. LilyPond sources are engraved locally
by the bundled WebAssembly engine; neither your sources nor the generated
assets ever leave your computer, and the server collects no data — no
telemetry, no analytics, no accounts.
The npm package makes exactly one kind of network request: downloading
the pinned engine release from GitHub on first use (verified against
checksums, cached under `~/.cache/lilypond-mcp`). The desktop extension
(`.mcpb`) ships the engine inside the bundle and makes no network
requests at all.
Generated assets are written to the `output_dir` you choose and stay
under your control; the server retains nothing else. Questions:
[wilfred@eastpole.nl](mailto:wilfred@eastpole.nl) or the
[issue tracker](https://github.com/wspringer/lilypond-mcp/issues).
## Licence
MIT. The server runs a GPL-licensed engraver (the lilypond-wasi
WebAssembly build of GNU LilyPond) as a separate program, and the npm
package contains none of its bytes — see
`LICENSING.md` for the analysis and the design rules that keep that
boundary clean.
TDQS
Scored across 3 tools
The three tools have clearly distinct purposes: engrave_file operates on a .ly path, engrave_code takes inline source, and lilypond_version reports the engine version. The descriptions explicitly contrast the two engrave tools, reducing any misselection risk.
engrave_file and engrave_code share a consistent verb_noun pattern, but lilypond_version breaks it with a noun_noun form. The deviation is minor and still readable as a cohesive set.
Three tools is a tight, focused scope appropriate for a single-purpose LilyPond engraver wrapper. It's on the lean side but each tool earns its place without redundancy.
Core workflow (engrave file, engrave inline, check version) is fully covered with no dead ends for typical use. A preview/validate-only or batch operation is absent, but agents can work around this via engrave_code plus engrave_file.