A local file bridge for controlling REAPER from an AI agent — Claude Code,
Codex, Cursor's terminal agent, anything that can read and write files. No
network, no socket required: the agent drops JSON command files in a folder, a
Lua script inside REAPER executes them and writes JSON results back. An
optional MCP server (reaper_mcp.py, stdio, same trust model) exposes the
whole bridge as tools for Claude Desktop, Claude Code, and any other MCP
client — see MCP server below.
Works on macOS, Windows, and Linux. The bridge itself is pure Lua using
native REAPER API only (path separators are derived from package.config, so
the same script runs everywhere). The agent-facing CLI (reaperd.py) and the
MCP server (reaper_mcp.py) are plain Python 3 files with no third-party
dependencies.
The bridge is plugin-agnostic and drum-library-agnostic. It ships with no
knowledge of any specific synth, amp sim, or instrument. An agent discovers
what a project contains with scan_fx, acts on tracks/FX/parameters by name,
and can auto-discover a drum kit's note map from the library's own
.midnam.
- Project control — transport, tempo, cursor, time selection, render.
- Tracks — add, delete, rename, select, volume, pan, mute, solo, arm, color.
- FX — add, remove, bypass, reorder, set parameters, write parameter automation envelopes. Any plugin (VST2/3, CLAP, AU, JS).
- Markers, regions, media items.
- MIDI — insert MIDI files; a creative drum DSL engine with humanization (velocity model, fatigue, timing jitter).
- Discovery —
scan_fxdumps every FX and parameter;discover_drum_mapreads a drum track's note names and builds a kit map for any library.
Every mutating command runs inside a REAPER undo block (Cmd+Z / Ctrl+Z to revert).
A single runtime: the Lua bridge (bridge/reaper_agent_bridge.lua) runs
forever as a reaper.defer loop inside REAPER. It polls inbox/, executes one
command per tick, writes results to outbox/, and a heartbeat to
bridge/heartbeat.json. This is the only thing that touches the REAPER API.
All JSON writes are atomic (write .tmp, then rename). Command files move
inbox/ → processing/ → archive/ (ok) or failed/ (error).
Requires REAPER and Python 3.8+ (no pip packages needed).
git clone https://github.com/wretcher207/reaper-daemon.git
cd reaper-daemon
python3 setup/install.py # macOS/Linux (use `python` on Windows)setup/install.py detects your OS, locates REAPER's per-user resource
directory, and writes a marker-delimited block into Scripts/__startup.lua
that auto-loads the bridge on every launch, pointing at this clone. It is
idempotent — re-run it any time you move the repo. Then (re)start REAPER.
Options:
python3 setup/install.py --dry-run # preview, change nothing
python3 setup/install.py --uninstall # remove the managed auto-start block
python3 setup/install.py --bridge-root /path/to/clone
REAPER_RESOURCE_PATH=/custom/dir python3 setup/install.pyPrefer to load it manually instead of at startup? In REAPER:
Actions > Show action list > ReaScript: Load…, pick
bridge/reaper_agent_bridge.lua, run it once. It runs as a background deferred
script and regenerates bridge/bridge_config.json and its working folders on
first run.
Prefer REAPER's own package manager? Add this repo to ReaPack:
- In REAPER:
Extensions > ReaPack > Import repositories. - Paste:
https://github.com/wretcher207/reaper-daemon/raw/main/index.xml Extensions > ReaPack > Browse packages, find Reaper Daemon, install.
Two things to know, because ReaPack delivers only the two Lua files
(reaper_agent_bridge.lua + json.lua) — not the agent CLI, the command
examples, or the drum engine:
- It does not auto-start. ReaPack installs the bridge as an Action but does
not run it on launch. Run the action once per session, or add it to your
Scripts/__startup.lua(python3 setup/install.pyfrom a clone does this for you). - You still need the rest of the package.
reaperd.py(the agent CLI),commands/examples/, andskills/drum-apparatus/(the drum DSL engine) are NOT installed by ReaPack. For those, also clone the repo:git clone https://github.com/wretcher207/reaper-daemon.gitand point your agent at the clone (or runpython3 setup/install.pyfrom the clone, which handles auto-start too). ReaPack then just keeps the bridge script updated. - Point your agent at the install folder. ReaPack installs to
<REAPER resource>/Scripts/reaper-daemon/. Right-click the package in ReaPack and "Show in explorer/finder" to get the exact path, then aim your agent at that folder.
With REAPER open and a project loaded:
python3 reaperd.py status
python3 reaperd.py send commands/examples/get_context.json --waitstatus reports the bridge heartbeat; send --wait prints a JSON result
describing the open project. If it times out, check that
bridge/heartbeat.json exists and is fresh.
One Python entry point for everything an agent does (no shell helpers, no
jq/grep pipelines — works identically on macOS, Windows, Linux):
python3 reaperd.py status # liveness check (run first)
python3 reaperd.py send <cmd.json> --wait # send a command file
python3 reaperd.py cmd <type> '<payload-json>' # send by type + payload
python3 reaperd.py fxload "<plugin query>" <track|master>
python3 reaperd.py setparam <track> "<fx>" "<param>" "<display value>"
python3 reaperd.py eq <track> "<fx>" <band> <freqHz> <gaindB> [Q]
python3 reaperd.py groove <beat.dsl> --track Drums [--position SEC] [--map NAME]
python3 reaperd.py jam # DSL drum beat from stdin -> selected track
python3 reaperd.py list-maps # available drum-kit maps
python3 reaperd.py discover-map <track> [--save <name>]
python3 reaperd.py add-map <name> --file <map.json> # or --roles '{...}' / stdin
python3 reaperd.py remove-map <name>fxload and cmd add_fx resolve a fuzzy plugin query to REAPER's exact
installed name from the VST/CLAP/AU cache before loading. setparam works on
any plugin by parameter index, binary-searching the normalized value that
produces a target display value, then verifying.
reaper_mcp.py wraps the same file bridge as an
MCP stdio server, so any MCP client can
drive REAPER conversationally. Zero dependencies, no network listener — it
translates tool calls into the same inbox/outbox files, with the same safety
semantics (undo blocks, dry_run, risk gating).
Claude Code:
claude mcp add reaper -- python3 /path/to/reaper-daemon/reaper_mcp.py(Use python instead of python3 on Windows if that's what is on PATH.)
Claude Desktop (claude_desktop_config.json):
{ "mcpServers": { "reaper": {
"command": "python",
"args": ["C:/path/to/reaper-daemon/reaper_mcp.py"] } } }Then just ask: "add a ReaEQ to the bass and carve 2 dB at 300 Hz", "what plugins are on the master?", "program a d-beat groove at bar 33".
18 tools: project/FX discovery (get_context, scan_fx,
get_fx_parameters, get_track_routing), transport, tracks, FX chains,
set_fx_param (formatted values like "-16.00 dB" work), automation
envelopes, markers/regions, MIDI insertion, batch (one undo block),
post-FX stem capture, and — with
Post Mortem installed —
analyze_track / compare_tracks, which hand the calling model measured
mix data (LUFS, true peak, spectrum, stereo image, masking table) to
diagnose. Mutations are undoable with one Ctrl/Cmd+Z; destructive tools ask
the model to confirm intent; audio capture stays behind the
allow_risk_level_3 config gate (restart REAPER after changing it).
FX discovery returns REAPER's real track and FX GUIDs alongside names and
indices. Use the GUIDs as stable identity when planning a later change: names
can be duplicated, and indices move when a user edits the chain. The exact
identity fields and compatibility rules are documented in
bridge/command_schema.md.
Post Mortem Phase 1 consumes these read-only identities to validate structured recommendations. Reaper Daemon does not preview or apply those recommendations; its existing mutation commands remain explicit, independently authorized bridge operations. Reaper Daemon stays MIT-licensed and local. Planned hosted Post Mortem services or UI do not move this bridge behind a commercial boundary.
The DSL drum engine (skills/drum-apparatus/) ships a few built-in kit maps
(GM Standard, RS Monarch, Odeholm, MDL Tone, Sleep Token II) in
skills/drum-apparatus/catalog/maps.json. For any other library, auto-discover:
# 1. With the drum plugin on a track that has its .midnam loaded:
python3 reaperd.py discover-map Drums --save MyKit
# 2. Use it:
python3 reaperd.py groove beat.dsl --track Drums --map MyKit
# or in the DSL: @map MyKitdiscover-map reads the MIDI note names REAPER has for the track (the
.midnam the library installed), classifies each note into a groovekit role
(kick / snare / hat-closed / hat-open / ride / crash / china / tom1..4 / ...),
fills missing articulations by fallback so a sparse kit never breaks the
engine, and saves the result to the user overlay
(skills/drum-apparatus/maps/<name>.json, gitignored). Libraries that don't
ship a .midnam (some Kontakt kits) report no note names; for those, build the
map by hand with add-map:
python3 reaperd.py add-map MyKontactKit --roles '{"KICK_R":36,"SNARE":38,"HH_OPEN_1":46,"CRASH_R":49,"CHINA_R":52}'Read AGENTS.md for the workflow, CLAUDE.md for the fast operational
protocol, and bridge/command_schema.md for every command. Working JSON
examples are in commands/examples/.
The CI matrix covers Windows, macOS, and Linux. From the repository root:
python -m pip install pytest
python -m pytest tests skills/drum-apparatus/tests -q
python -m py_compile reaperd.py reaper_mcp.py setup/install.py
lua bridge/test_bridge.lua
lua bridge/test_json.luaThe Lua checks require Lua 5.4. Live REAPER integration remains a separate manual gate because CI does not launch the DAW.
bridge/reaper_agent_bridge.lua the bridge (runs inside REAPER, OS-neutral)
bridge/bridge_config.json machine-specific config (regenerated)
bridge/command_schema.md full command reference
reaperd.py cross-platform agent CLI (Python 3)
reaper_mcp.py MCP stdio server over the same bridge
setup/install.py wire auto-start into REAPER (cross-platform)
commands/examples/ one JSON example per command
skills/drum-apparatus/ DSL drum engine + kit-map auto-discovery
inbox/ outbox/ processing/ ... runtime folders
The bridge is a local file control channel: any process that can write to
inbox/ can drive REAPER (load FX, change tracks, render). That's fine for a
single-user dev box, which is what it's built for. Two rules keep it that way:
- Keep the bridge folder local. Don't put it on a network share, a synced drive, or anywhere another machine/user can write. That's the real boundary.
- Mutating commands run inside REAPER undo blocks, so anything an agent does is
Cmd/Ctrl+Z-reversible.
Optional shared-secret token. On a shared or less-trusted box you can require
a token: set auth_token to any string in bridge/bridge_config.json. The
bridge then rejects any command without a matching token (AUTH_FAILED), and
reaperd.py reads the same config and attaches it automatically — no other
setup. Honest limit: the token lives in a local-readable config, so it stops
accidental or other-app writes, not a local attacker who can read that
file. Keeping the folder local is still the actual protection.
MIT. See LICENSE.