# TwelveTake-Studios/reaper-mcp [Health: Active]

**Category:** 🎨 Art & Culture  
**Repository:** https://github.com/TwelveTake-Studios/reaper-mcp  
**GitHub Stars:** 54  
**Views:** 1  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/twelvetake-studios-reaper-mcp

## Description
MCP server enabling AI assistants to control REAPER DAW for mixing, mastering, MIDI composition, and full music production with 129 tools

## Tools
Capabilities this server exposes over MCP:

- **get_track_count** — Get the total number of tracks in the current REAPER project (excluding master track).
- **get_track** — Get information about a track.

Args:
    track_index: Track index (0-based). Use -1 for the master track.

Returns:
    Object with 'info': guid, name, volume, volume_db, pan, muted, soloed,
    has_midi, has_audio, fx_names, role.
- **get_all_tracks** — Get information about all tracks in the project.
- **get_master_track** — Get information about the master track.
- **insert_track** — Insert a new track at the specified index.

Args:
    index: Position to insert track (0-based). If not specified, adds at end.
    name: Optional name for the new track.

Returns:
    Info about the created track.
- **delete_track** — Delete a track.

Args:
    track_index: Track index to delete (0-based). Cannot delete master track (-1).
- **set_track_name** — Set the name of a track.

Args:
    track_index: Track index (0-based) or -1 for master track.
    name: New name for the track.
- **set_track_volume** — Set the volume of a track in decibels.

Args:
    track_index: Track index (0-based) or -1 for master track.
    volume_db: Volume in dB (0 = unity gain, -inf to +12 typical range).
- **set_track_pan** — Set the pan position of a track.

Args:
    track_index: Track index (0-based) or -1 for master track.
    pan: Pan position from -1.0 (full left) to 1.0 (full right). 0 = center.
- **set_track_mute** — Set the mute state of a track.

Args:
    track_index: Track index (0-based) or -1 for master track.
    mute: True to mute, False to unmute.
- **set_track_solo** — Set the solo state of a track.

Args:
    track_index: Track index (0-based) or -1 for master track.
    solo: True to solo, False to unsolo.
- **track_fx_get_count** — Get the number of FX plugins on a track.

Args:
    track_index: Track index (0-based) or -1 for master track.

Returns:
    Object with 'ret' field containing count.
- **track_fx_get_list** — Get list of all FX plugins on a track.

Args:
    track_index: Track index (0-based) or -1 for master track.

Returns:
    Object with 'fx' array, one entry per FX with index, name, enabled and offline,
    plus fx_count. 'enabled' is False when the FX is bypassed. Normal FX chain only,
    not input/record FX or master monitoring FX.
- **track_fx_add_by_name** — Add an FX plugin to a track by name.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_name: Name of the FX plugin to add (e.g., "ReaEQ", "ReaComp", "ReaLimit").
             Use the exact plugin name as it appears in REAPER's FX browser.
    position: Optional insertion position (0-based) in the FX chain.
              Default -1 adds at the end; 0 inserts at the beginning.

Returns:
    Info about the added FX including its index.
- **track_fx_move** — Move an FX plugin to a new position within the same track's FX chain.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: Current FX index (0-based) in the FX chain.
    new_position: Target position (0-based). 0 = beginning of the chain.
- **track_fx_delete** — Remove an FX plugin from a track.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
- **track_fx_get_name** — Get the name of an FX plugin.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
- **track_fx_get_enabled** — Get the enabled state of an FX plugin.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.

Returns:
    Object with 'ret' field (boolean).
- **track_fx_set_enabled** — Enable or disable an FX plugin.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    enabled: True to enable, False to bypass.
- **track_fx_get_num_params** — Get the number of parameters for an FX plugin.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
- **track_fx_get_param_name** — Get the name of an FX parameter.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).
- **track_fx_get_param** — Get a specific parameter value of an FX plugin.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).

Returns:
    Object with value, min, max for the parameter.
- **track_fx_set_param** — Set a parameter value on an FX plugin.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).
    value: New value for the parameter (typically normalized 0-1, check min/max).
- **take_fx_get_count** — Get the number of FX plugins on a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).

Returns:
    Object with 'ret' field containing the FX count.
- **take_fx_get_list** — Get a list of all FX plugins on a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).

Returns:
    Object with 'fx' array, each entry having index, name, and enabled state.
- **take_fx_add_by_name** — Add an FX plugin to a take by name.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_name: Name of the FX plugin to add (e.g., "ReaEQ", "ReaComp"). Use the exact
             plugin name as it appears in REAPER's FX browser.

Returns:
    Object with 'ret' field containing the new FX index (or -1 if it could not be added).
- **take_fx_delete** — Remove an FX plugin from a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
- **take_fx_get_name** — Get the name of an FX plugin on a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
- **take_fx_get_enabled** — Get the enabled (not bypassed) state of an FX plugin on a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.

Returns:
    Object with 'ret' field (boolean).
- **take_fx_set_enabled** — Enable or bypass an FX plugin on a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
    enabled: True to enable, False to bypass.
- **take_fx_get_num_params** — Get the number of parameters for an FX plugin on a take.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
- **take_fx_get_param_name** — Get the name of a parameter on a take's FX plugin.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
    param_index: Parameter index (0-based).
- **take_fx_get_param** — Get a parameter value on a take's FX plugin.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
    param_index: Parameter index (0-based).

Returns:
    Object with 'value', 'min', and 'max' for the parameter.
- **take_fx_set_param** — Set a parameter value on a take's FX plugin.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index within the item (0-based).
    fx_index: FX index (0-based) in the take's FX chain.
    param_index: Parameter index (0-based).
    value: New value (typically normalized 0-1; check min/max via take_fx_get_param).
- **get_takes** — List all takes of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).

Returns:
    Object with 'takes' array (each entry: index, name, is_active) and 'ret' = take count.
- **get_active_take** — Get the index of the active take of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).

Returns:
    Object with 'ret' = active take index (-1 if the item has no active take).
- **set_active_take** — Set the active take of a media item (which take plays).

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index to activate (0-based).
- **explode_takes** — Explode all takes of a media item in place (each take becomes its own overlapping item).

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
- **crop_to_active_take** — Crop a media item to its active take, discarding all other takes.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
- **delete_take** — Delete a specific take from a media item.

Args:
    track_index: Track index (0-based).
    item_index: Media item index on that track (0-based).
    take_index: Take index to delete (0-based). The take is activated first, then removed.
- **select_comp_lane** — Make a fixed lane play exclusively on a track (REAPER 7 lane-based comping).

The track must be in fixed-lane mode (right-click track -> Track lanes). Returns a clear
error if it is not, or if the lane index is out of range.

Args:
    track_index: Track index (0-based).
    lane_index: Fixed lane index to play exclusively (0-based).
- **create_send** — Create a send from one track to another.

Args:
    src_track: Source track index (0-based).
    dest_track: Destination track index (0-based).

Returns:
    Object with send_index.
- **delete_send** — Delete a send from a track.

Args:
    track_index: Source track index (0-based).
    send_index: Send index (0-based) to delete.
- **set_send_volume** — Set the volume of a track send.

Args:
    track_index: Source track index (0-based).
    send_index: Send index (0-based).
    volume_db: Send volume in dB.
- **get_track_num_sends** — Get the number of sends from a track.

Args:
    track_index: Track index (0-based).
- **set_send_dest_channels** — Set the destination channels for a send (used for sidechain routing).

Args:
    track_index: Source track index (0-based).
    send_index: Send index (0-based).
    dest_chan: Destination channel pair (0=1-2 main, 2=3-4 sidechain, 4=5-6, etc.).
               For sidechain compression, use 2 to route to channels 3-4.

Returns:
    Object with success status.
- **set_send_source_channels** — Set the source channels for a send.

Args:
    track_index: Source track index (0-based).
    send_index: Send index (0-based).
    src_chan: Source channel (-1=none, 0=stereo 1-2, 1024+n=mono from channel n).

Returns:
    Object with success status.
- **setup_sidechain_send** — Create a sidechain send from one track to another track's FX sidechain input.

This creates a send routed to channels 3-4 of the destination track,
which is the standard sidechain input for compressors like ReaComp.

Args:
    src_track: Source/trigger track index (e.g., kick drum).
    dest_track: Destination track index (e.g., bass with compressor).
    volume_db: Send volume in dB (default 0dB = unity).

Returns:
    Object with send_index and routing info.
- **configure_reacomp_sidechain** — Configure ReaComp to use sidechain input for detection.

Args:
    track_index: Track index (0-based) where ReaComp is located.
    fx_index: FX index (0-based) of ReaComp in the FX chain.
    use_sidechain: True to use auxiliary input (channels 3-4), False for main input.

Returns:
    Object with configuration status.
- **setup_sidechain_compression** — Complete sidechain compression setup: creates send and configures compressor.

This is the all-in-one function for setting up sidechain compression.
It creates a send from the trigger track to the target track's sidechain input
and configures ReaComp to listen to that sidechain.

Args:
    trigger_track: Track that triggers compression (e.g., kick drum = track 0).
    target_track: Track to be compressed (e.g., bass = track 1).
    compressor_fx_index: Index of ReaComp in target track's FX chain.
    send_volume_db: Sidechain send volume in dB (default 0dB).

Returns:
    Object with complete setup info.

Example:
    For kick-triggered bass compression where:
    - Drums are on track 0
    - Bass is on track 1 with ReaComp at FX index 2
    Call: setup_sidechain_compression(0, 1, 2)
- **play** — Start playback in REAPER.
- **stop** — Stop playback in REAPER.
- **get_play_state** — Get the current playback state.

Returns:
    Object with play state info.
- **get_cursor_position** — Get the edit cursor position.

Returns:
    Object with cursor position in seconds.
- **set_cursor_position** — Set the edit cursor position.

Args:
    position: Position in seconds from project start.
- **save_project** — Save the current REAPER project.
- **get_project_path** — Get the project path.
- **get_project_name** — Get the project name.
- **get_tempo** — Get the project tempo.
- **get_time_signature** — Get the project time signature.
- **add_mastering_chain** — Add a standard mastering chain to the master track.

Adds the following plugins in order:
1. ReaEQ (corrective EQ)
2. ReaComp (glue compression)
3. ReaEQ (tonal shaping)
4. ReaLimit (brickwall limiter)

Returns:
    Object with list of added FX indices.
- **add_parallel_compression** — Set up New York style parallel compression for a track.

Creates a new bus track with heavy compression, fed by a send from the source track.

Args:
    track_index: Source track index (0-based).
    blend_db: Send level in dB for the compressed signal (default -6dB).

Returns:
    Object with bus_track_index, send_index, and compressor_fx_index.
- **create_bus** — Create a submix/stem bus and route specified tracks to it.

Args:
    name: Name for the bus track.
    source_track_indices: List of track indices to route to this bus.

Returns:
    Object with bus_track_index and routing info.
- **add_eq** — Add ReaEQ to a track.

Args:
    track_index: Track index (0-based) or -1 for master track.

Returns:
    Object with fx_index.
- **add_compressor** — Add ReaComp to a track.

Args:
    track_index: Track index (0-based) or -1 for master track.

Returns:
    Object with fx_index.
- **add_limiter** — Add ReaLimit (brickwall limiter) to a track.

Args:
    track_index: Track index (0-based) or -1 for master track.

Returns:
    Object with fx_index.
- **create_midi_item** — Create an empty MIDI item on a track.

Args:
    track_index: Track index (0-based).
    position: Start position in seconds.
    length: Length in seconds.

Returns:
    Object with item info including the new item_index.
- **get_midi_item** — Get information about a MIDI item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.

Returns:
    Object with item info including position, length, note count.
- **add_midi_note** — Add a MIDI note to an item using musical timing (beats).

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    pitch: MIDI note number (0-127, 60 = middle C).
    velocity: Note velocity (1-127).
    start_beat: Start position in beats from the item start (0 = first beat).
    length_beats: Note length in beats (0.25 = sixteenth, 0.5 = eighth, 1.0 = quarter).
    channel: MIDI channel (0-15, default 0).

Example:
    Four-on-the-floor kick: add_midi_note(0, 0, 36, 110, start_beat=0, length_beats=0.25)
    then start_beat=1, 2, 3.
- **add_midi_notes_batch** — Add multiple MIDI notes to an item in one call, using musical timing (beats).

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    notes: List of note dicts with keys: pitch, velocity, start_beat, length_beats,
           channel (optional).

Returns:
    Object with count of notes added.
- **get_midi_notes** — Get all MIDI notes from an item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).

Returns:
    Object with list of notes.
- **delete_midi_note** — Delete a MIDI note from an item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    note_index: Note index (0-based).

Returns:
    Object with success status.
- **clear_midi_item** — Delete all MIDI notes from an item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).

Returns:
    Object with count of notes deleted.
- **set_midi_note_velocity** — Set the velocity of a MIDI note.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    note_index: Note index (0-based).
    velocity: New velocity (1-127).

Returns:
    Object with success status.
- **transpose_midi_notes** — Transpose MIDI notes by a number of semitones (octave = 12).

Operates on the active take of (track_index, item_index). By default every note is
shifted; narrow the target with the optional value filter (pitch range, an onset
window in beats-from-item-start, channel). A note pushed outside 0-127 is left at its
original pitch and counted in `skipped` — never clamped, never dropped.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    semitones: Signed semitone shift (12 = up an octave, -12 = down). 0 = no-op.
    pitch_low: Only transpose notes with original pitch >= this (0-127, inclusive).
    pitch_high: Only transpose notes with original pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]} — the full note
    list after the transform (note indices re-sync after the internal sort).
- **nudge_midi_notes** — Shift MIDI notes in time by a number of beats (+ = later, - = earlier).

Each matched note's start and end move by the same amount, so note lengths are
preserved. Notes pushed before the item start or past the item end are kept (never
clamped) and reported in `out_of_bounds`. Narrow the target with the optional value
filter (pitch range, an onset window in beats-from-item-start, channel).

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    amount_beats: Signed beat shift (0.25 = a 16th later, -1.0 = a beat earlier). 0 = no-op.
    pitch_low: Only nudge notes with pitch >= this (0-127, inclusive).
    pitch_high: Only nudge notes with pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]}.
- **get_selected_midi_notes** — Read the MIDI notes currently SELECTED in REAPER's editor for the active take.

The escape hatch for "operate on what I've selected": select notes by hand in REAPER,
call this to see which they are, then translate that into an explicit value filter
(pitch range / beat window / channel) for the transform tools. Read-only, no undo.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.

Returns:
    {ok, notes:[...], ret} — the selected notes, same shape as get_midi_notes (each note
    carries item-relative start_beat/end_beat). `index` is REAPER's absolute PPQ-sorted
    note index, so a partial selection is non-contiguous. Empty selection -> notes:[].
- **set_midi_note** — Edit one existing MIDI note in place, addressed by its index.

The per-note editor: change a single note's pitch, start (beats from item start),
length, and/or channel. Only the fields you pass change; the rest are left as-is. At
least one field is required. (Velocity is set with set_midi_note_velocity, not here.)

`note_index` is REAPER's PPQ-sorted note index from get_midi_notes / get_selected_midi_notes
and is UNSTABLE — re-read after the edit, since the returned list reflects the new order.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    note_index: Note to edit (0-based, PPQ-sort order).
    pitch: New pitch (0-127). Out of range -> error, no write.
    start_beat: New start in beats from item start (moves the note; length preserved). A
        value before the item start clamps there (REAPER floors notes at the item start),
        reported in out_of_bounds.
    length_beats: New length in beats (> 0; resizes from the current start). <= 0 -> error.
    channel: New MIDI channel (0-15). Out of range -> error.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]} on success, or
    {ok:false, error} for a bad index / empty edit / invalid value (no write).
- **ramp_midi_note_velocities** — Apply a linear velocity ramp (crescendo / decrescendo) across MIDI notes.

Velocities interpolate by each note's onset: the earliest note in the filtered set gets
start_velocity, the latest gets end_velocity, everything between is linear. Notes sharing
an onset (a chord) get the same velocity. Results clamp to 1-127 (reported in `clamped`).
Narrow the target with the optional filter (pitch range, onset window, channel).

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    start_velocity: Velocity at the earliest onset (1-127; out of range clamps, not rejected).
    end_velocity: Velocity at the latest onset (1-127). May be below start (decrescendo).
    pitch_low: Only ramp notes with pitch >= this (0-127, inclusive).
    pitch_high: Only ramp notes with pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]}.
- **scale_midi_note_velocities** — Scale MIDI note velocities: multiply, set to a fixed value, or compress toward a pivot.

- multiply: new = velocity * ratio (ratio >= 0, no upper cap; results clamp to 1-127).
- set: every matched note's velocity becomes `value` (1-127).
- compress: pull velocities toward a pivot — new = pivot + (velocity - pivot) * ratio, with
  ratio in [0,1] (0 collapses to the pivot, 1 = unchanged). Use multiply to boost.
  pivot -1 (default) auto-picks the rounded mean of the filtered notes' velocities.

Narrow the target with the optional filter (pitch range, onset window, channel).

Args:
    track_index / item_index: 0-based; active take must be MIDI.
    mode: "multiply" | "set" | "compress".
    ratio: multiply/compress factor (see above).
    value: target velocity for `set` mode (1-127).
    pivot: compress pivot (1-127), or -1 to auto-pick the filtered mean.
    pitch_low / pitch_high: pitch filter (0-127, inclusive).
    start_beat / end_beat: onset window in beats from item start.
    channel: MIDI channel (0-15); None/-1 = all.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, pivot_used, notes:[...]}.
- **strum_midi_notes** — Strum chords: stagger the onsets of simultaneous notes so each chord rolls out.

Notes sharing an onset (a chord) are spread over `spread_beats` (total first-to-last span);
`up` strikes the lowest note first, `down` the highest. Only re-times existing notes (invents
nothing); note lengths are preserved. Narrow the target with the optional filter.

Args:
    track_index / item_index: 0-based; active take must be MIDI.
    spread_beats: Total first-to-last onset span within each chord (>= 0; 0 = no-op).
    direction: "up" (lowest first) or "down" (highest first).
    chord_window_beats: Onset tolerance for grouping notes into a chord (0 = exact same onset).
    pitch_low / pitch_high: pitch filter (0-127, inclusive).
    start_beat / end_beat: onset window in beats from item start.
    channel: MIDI channel (0-15); None/-1 = all.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]}.
- **snap_midi_notes_to_scale** — Snap off-key MIDI notes onto a scale (fix a wrong note, force a part into a key).

Notes already in the scale are left alone. Each off-scale note moves to the nearest
in-scale pitch; with `nearest`, a tie (the note sits exactly between two scale tones)
resolves toward the middle of the selection, which keeps a line from drifting. A note
with no in-scale pitch left inside 0-127 is left where it is and counted in `skipped`
— it is never dropped and never wrapped to another octave.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    root: Root pitch class, 0-11 (0=C, 1=C#, 2=D ... 11=B).
    mode: Scale name — one of major, minor, harmonic_minor, melodic_minor, dorian,
        phrygian, lydian, mixolydian, locrian, major_pentatonic, minor_pentatonic,
        blues, whole_tone, chromatic (aliases: ionian, aeolian, natural_minor) — OR a
        custom list of semitone intervals from the root, each 0-11 (e.g. [0,2,4,7,9]).
    direction: "nearest" (closest scale tone), "up" (only upward), "down" (only downward).
        `up`/`down` never fall back to the other direction; they skip instead.
    pitch_low: Only snap notes with pitch >= this (0-127, inclusive). None = no bound.
    pitch_high: Only snap notes with pitch <= this (0-127, inclusive). None = no bound.
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]} — the full note
    list after the transform (note indices re-sync after the internal sort).
- **quantize_midi_notes** — Quantize MIDI note onsets onto the grid (tighten sloppy timing, add swing).

Onsets snap to the PROJECT bar/beat grid, so notes land where the ruler says a 16th is —
not at an offset from the item's own start. Note lengths are preserved: start and end move
together. Use `strength` to tighten only part of the way and keep some human feel, and
`swing` to push the off-beats late for a shuffle. Notes pushed past the item end are kept
and reported in `out_of_bounds`; a note that would land before the item start is placed at
the item start instead (REAPER refuses anything earlier) and also reported there.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    grid: Grid spacing in beats — 0.25 = 1/16, 0.5 = 1/8, 1.0 = 1/4. Must be > 0.
    strength: 0.0-1.0. 1.0 snaps exactly onto the grid; 0.5 moves each note halfway there;
        0.0 is a no-op.
    swing: 0.0-1.0. 0.0 = straight; 1.0 = full triplet feel (off-beats at 66.7%); values
        between scale linearly. Only the off-beat (odd) grid cells are delayed.
    pitch_low: Only quantize notes with pitch >= this (0-127, inclusive).
    pitch_high: Only quantize notes with pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]}.
- **stretch_midi_notes** — Stretch or compress MIDI timing (half-time, double-time, or any ratio).

Each targeted note scales as a rigid unit about one fixed pivot: its distance from the
pivot AND its length both multiply by `factor`, so the phrase's rhythm is preserved while
its overall speed changes. 2.0 = half-time (twice as long), 0.5 = double-time. The pivot
stays put; by default it is the first targeted note, so a phrase grows or shrinks away from
its own downbeat.

Notes pushed past the item end are kept and reported in `out_of_bounds` (the item is not
auto-extended). A note pushed before the item start is placed at the item start — REAPER
refuses anything earlier — keeping its scaled end, so it comes out shorter than `factor`
alone implies; it is reported in `out_of_bounds` too.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    factor: Time scale ratio, must be > 0. 2.0 = twice as long/slow, 0.5 = half/fast.
    pivot_beat: The fixed point, in beats from item start. None = the earliest targeted
        onset. May be negative, and may sit outside the targeted notes.
    pitch_low: Only stretch notes with pitch >= this (0-127, inclusive).
    pitch_high: Only stretch notes with pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]}.
- **legato_midi_notes** — Close the gaps in a MIDI line (legato), or set every note to one length.

Only note ENDS move — starts are never touched, so the rhythm of the part is preserved.

`connect` runs each note's end forward to the next note's onset, so the line plays
seamlessly. A note that already reaches (or overlaps) the next one is left alone — this
never shortens anything. A gap wider than `max_gap_beats` is treated as a rest you meant
to be there: left alone and counted in `gaps_preserved`. The last note is left alone.

`fixed` ignores all that and simply sets every targeted note's length to `length_beats`.

Notes whose new end passes the item end are kept and reported in `out_of_bounds`.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    mode: "connect" (extend each end to the next onset) or "fixed" (set every length).
    voice: connect only. "chordal" extends to the next onset of any note, so a chord's
        notes move together; "per_pitch" extends to the next note of the SAME pitch and
        channel, which keeps interleaved voices independent.
    max_gap_beats: connect only. Gaps wider than this are left as rests (>= 0).
    length_beats: fixed only. The length every targeted note is set to (> 0).
    pitch_low: Only affect notes with pitch >= this (0-127, inclusive). None = no bound.
    pitch_high: Only affect notes with pitch <= this (0-127, inclusive). None = no bound.
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, gaps_preserved, notes:[...]}.
- **humanize_midi_notes** — Humanize MIDI: nudge timing and velocity by small random amounts, reproducibly.

Takes the machine-perfect edge off a programmed part. Each note gets its own random
timing and velocity offset drawn from a bell curve, so most notes move a little and a few
move more — the way a player does. Note lengths are preserved (start and end move
together), and pitches are never touched.

The randomness is SEEDED and computed here, not in REAPER: the same take with the same
seed and settings gives byte-identical results every time. Change `seed` for a different
feel; keep it to reproduce one.

Notes pushed past the item end are kept and reported in `out_of_bounds`; a note pushed
before the item start is placed at the item start (REAPER refuses anything earlier),
keeping its length, and reported there too. Velocities are clamped to 1-127 and counted
in `clamped` — a humanized note never drops to 0 (silent).

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    timing: Timing spread in beats (standard deviation). 0.02 is a subtle human feel;
        0.0 = leave timing alone and only touch velocity.
    velocity: Velocity spread (standard deviation, in velocity units). 0.0 = timing only.
    seed: Any integer. Same seed + same settings + same take = same result.
    pitch_low: Only humanize notes with pitch >= this (0-127, inclusive).
    pitch_high: Only humanize notes with pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.
    max_sigma: Cap on how far any single note may stray, in multiples of the spread.
        Stops one freak draw from throwing a note far out of place.

Returns:
    {ok, notes_changed, clamped, skipped, out_of_bounds, notes:[...]}.
- **remove_overlapping_midi_notes** — Clean up overlapping MIDI notes (same pitch stacked on itself).

Two notes only conflict if they share a pitch AND a channel AND actually overlap in time.
A chord is never a conflict, the same pitch on two channels is never a conflict, and notes
that merely touch (one ends exactly where the next begins) are left alone.

`trim` (default) is the safe one: it shortens the earlier note so it stops where the next
begins. Nothing is lost — the notes just stop fighting. `delete` instead drops one note of
each overlapping pair, keeping the louder (ties go to the longer note).

NOTE: this tool can REMOVE notes. Even in `trim` mode, notes stacked on the exact same
onset are collapsed to one (the loudest), because there is nothing to trim between them —
and a trim left shorter than `min_length_beats` is removed rather than left as a click.
Everything removed is reported in `notes_removed`. It is undoable in REAPER as one step.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based) on the track.
    mode: "trim" (shorten the earlier note) or "delete" (drop the quieter note).
    min_length_beats: A trimmed note left shorter than this is removed instead of leaving
        an inaudible click. Default 1/128 of a beat. 0 disables it.
    pitch_low: Only consider notes with pitch >= this (0-127, inclusive). Notes outside the
        filter are invisible: never touched, and never counted as an overlap partner.
    pitch_high: Only consider notes with pitch <= this (0-127, inclusive).
    start_beat: Only notes whose onset is at/after this beat from item start.
    end_beat: Only notes whose onset is at/before this beat from item start.
    channel: Only this MIDI channel (0-15); -1 = all channels.

Returns:
    {ok, mode, notes_changed, clamped, skipped, out_of_bounds, notes_removed, trimmed,
    deduped, deleted, notes:[...]} — `notes_removed` = deduped + deleted.
- **insert_audio_file** — Insert an audio file onto a track.

Args:
    track_index: Track index (0-based).
    file_path: Full path to the audio file.
    position: Position in seconds.

Returns:
    Object with track_index, item_index of the created item, and position.
- **get_track_items** — Get all media items on a track.

Args:
    track_index: Track index (0-based).

Returns:
    Object with list of items.
- **get_item_info** — Get information about a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).

Returns:
    Object with item properties (position, length, take info, etc.).
- **set_item_position** — Set the position of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    position: New position in seconds.

Returns:
    Object with success status.
- **set_item_length** — Set the length of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    length: New length in seconds.

Returns:
    Object with success status.
- **delete_item** — Delete a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).

Returns:
    Object with success status.
- **duplicate_item** — Duplicate a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).

Returns:
    Object with new item info.
- **split_item** — Split a media item at a position.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    position: Split position in seconds (absolute project time).

Returns:
    Object with info about both resulting items.
- **set_item_mute** — Mute or unmute a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    mute: True to mute, False to unmute.

Returns:
    Object with success status.
- **set_item_volume** — Set the volume of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    volume_db: Volume in dB.

Returns:
    Object with success status.
- **set_item_fade_in** — Set the fade-in length of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    length: Fade-in length in seconds.

Returns:
    Object with success status.
- **set_item_fade_out** — Set the fade-out length of a media item.

Args:
    track_index: Track index (0-based).
    item_index: Item index (0-based).
    length: Fade-out length in seconds.

Returns:
    Object with success status.
- **set_tempo** — Set the project tempo.

Args:
    bpm: Tempo in beats per minute.

Returns:
    Object with success status.
- **set_time_signature** — Set the project time signature.

Args:
    numerator: Beats per measure (e.g., 4 for 4/4).
    denominator: Beat unit (e.g., 4 for quarter note).

Returns:
    Object with success status.
- **create_project** — Create a new REAPER project.

Note: REAPER has no API to name a project that has never been saved, so this server
has no way to name one either. The removed `name` parameter never took effect.

Returns:
    Object with success status.
- **open_project** — Open a REAPER project file.

Args:
    path: Full path to the .rpp file.

Returns:
    Object with success status.
- **render_project** — Render the project's master mix to an audio file (uses REAPER's last render settings
for format details; a .wav extension selects WAV output).

If the target file already exists, this returns an error unless overwrite=True
(which deletes the existing file first). This is explicit because REAPER's own
behavior on existing files (prompt vs auto-increment) is a user preference, and the
overwrite prompt blocks unattended rendering.

Args:
    output_path: Full path for output file (extension determines format).
    start_time: Start time in seconds (None = project start).
    end_time: End time in seconds (None = project end).
    tail_seconds: Extra seconds to render at end for reverb tails.
    overwrite: True to replace an existing file at output_path.

Returns:
    Object with render status and REAPER's computed output target(s).
- **render_region** — Render a specific region to an audio file.

NOT YET IMPLEMENTED — returns an explanatory error. A full region-render suite
(render matrix, stems) is planned for v1.9. Use render_project with explicit
start_time/end_time as a workaround.

Args:
    region_index: Region index (0-based).
    output_path: Full path for output file.
- **add_marker** — Add a marker at a position.

Args:
    position: Position in seconds.
    name: Marker name.
    color: Marker color (0 = default).

Returns:
    Object with marker index.
- **add_region** — Add a region.

Args:
    start: Start position in seconds.
    end: End position in seconds.
    name: Region name.
    color: Region color (0 = default).

Returns:
    Object with region index.
- **get_markers** — Get all markers in the project.

Returns:
    Object with list of markers (position, name, index).
- **get_regions** — Get all regions in the project.

Returns:
    Object with list of regions (start, end, name, index).
- **delete_marker** — Delete a marker by index.

Args:
    marker_index: Marker index.

Returns:
    Object with success status.
- **delete_region** — Delete a region by index.

Args:
    region_index: Region index.

Returns:
    Object with success status.
- **go_to_marker** — Move the edit cursor to a marker.

Args:
    marker_index: Marker index.

Returns:
    Object with success status.
- **go_to_region** — Move the edit cursor to a region start.

Args:
    region_index: Region index.

Returns:
    Object with success status.
- **get_track_envelope** — Get a track envelope by name.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name (e.g., "Volume", "Pan", "Mute").

Returns:
    Object with envelope info.
- **get_envelope_point_count** — Get the number of points in an envelope.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name.

Returns:
    Object with point count.
- **add_envelope_point** — Add a point to an envelope.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name.
    time: Time position in seconds.
    value: Envelope value (0.0-1.0 for most envelopes).
    shape: Point shape (0=linear, 1=square, 2=slow start/end, 3=fast start, 4=fast end, 5=bezier).

Returns:
    Object with point index.
- **get_envelope_points** — Get all points from an envelope.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name.

Returns:
    Object with list of points (time, value, shape).
- **delete_envelope_point** — Delete an envelope point.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name.
    point_index: Point index (0-based).

Returns:
    Object with success status.
- **clear_envelope** — Delete all points from an envelope.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name.

Returns:
    Object with success status.
- **set_track_automation_mode** — Set the automation mode for a track.

Args:
    track_index: Track index (0-based) or -1 for master.
    mode: 0=trim/read, 1=read, 2=touch, 3=write, 4=latch.

Returns:
    Object with success status.
- **arm_track_envelope** — Arm or disarm an envelope for recording.

Args:
    track_index: Track index (0-based) or -1 for master.
    envelope_name: Envelope name.
    arm: True to arm, False to disarm.

Returns:
    Object with success status.
- **get_fx_envelope** — Get or create an automation envelope for an FX parameter.

This enables automation of any FX parameter (e.g., a flanger knob in Guitar Rig).
The envelope is created if it doesn't exist.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based). Use track_fx_get_num_params() to find available parameters.

Returns:
    Object with envelope_name, param_name, point_count, and indices.
- **add_fx_envelope_point** — Add an automation point to an FX parameter envelope.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).
    time: Time position in seconds.
    value: Parameter value (typically 0.0-1.0, normalized).
    shape: Point shape (0=linear, 1=square, 2=slow start/end, 3=fast start, 4=fast end, 5=bezier).

Returns:
    Object with point_index and confirmation.
- **get_fx_envelope_points** — Get all automation points from an FX parameter envelope.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).

Returns:
    Object with list of points (time, value, shape, tension, selected).
- **delete_fx_envelope_point** — Delete an automation point from an FX parameter envelope.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).
    point_index: Point index (0-based) to delete.

Returns:
    Object with success status.
- **clear_fx_envelope** — Clear all automation points from an FX parameter envelope.

Args:
    track_index: Track index (0-based) or -1 for master track.
    fx_index: FX index (0-based) in the FX chain.
    param_index: Parameter index (0-based).

Returns:
    Object with deleted_count.
- **undo** — Undo the last action in REAPER.

Returns:
    Object with undo description.
- **redo** — Redo the last undone action in REAPER.

Returns:
    Object with redo description.
- **get_undo_state** — Get the current undo/redo state.

Returns:
    Object with undo and redo descriptions.
- **select_track** — Select a track.

Args:
    track_index: Track index (0-based).
    exclusive: If True, deselect other tracks first.

Returns:
    Object with success status.
- **select_all_tracks** — Select all tracks.

Returns:
    Object with success status.
- **unselect_all_tracks** — Unselect all tracks.

Returns:
    Object with success status.
- **get_selected_tracks** — Get indices of all selected tracks.

Returns:
    Object with list of selected track indices.
- **select_all_items** — Select all media items.

Returns:
    Object with success status.
- **unselect_all_items** — Unselect all media items.

Returns:
    Object with success status.
- **get_selected_items** — Get all selected media items.

Returns:
    Object with list of selected items.
- **copy_selected_items** — Copy selected items to clipboard.

Returns:
    Object with success status.
- **cut_selected_items** — Cut selected items to clipboard.

Returns:
    Object with success status.
- **paste_items** — Paste items from clipboard at edit cursor.

Returns:
    Object with success status.
- **delete_selected_items** — Delete all selected items.

Returns:
    Object with success status.
- **set_time_selection** — Set the time selection.

Args:
    start: Start time in seconds.
    end: End time in seconds.

Returns:
    Object with success status.
- **get_time_selection** — Get the current time selection.

Returns:
    Object with start and end times.
- **clear_time_selection** — Clear the time selection.

Returns:
    Object with success status.
- **set_track_phase** — Set the phase inversion of a track.

Args:
    track_index: Track index (0-based) or -1 for master.
    invert: True to invert phase, False for normal.

Returns:
    Object with success status.
- **set_track_width** — Set the stereo width of a track.

Args:
    track_index: Track index (0-based) or -1 for master.
    width: Width value (0=mono, 1=stereo, 2=200% width).

Returns:
    Object with success status.
- **set_track_as_folder** — Set a track as a folder parent or child.

Args:
    track_index: Track index (0-based).
    folder_depth: 0=normal, 1=folder parent, -1=end of folder.

Returns:
    Object with success status.
- **arm_track** — Arm or disarm a track for recording.

Args:
    track_index: Track index (0-based).
    arm: True to arm, False to disarm.

Returns:
    Object with success status.
- **set_track_input** — Set the record input for a track.

Args:
    track_index: Track index (0-based).
    input_index: Input index (-1=no input, 0+=hardware inputs, 4096+=virtual MIDI).

Returns:
    Object with success status.
- **set_track_monitor** — Set the monitor mode for a track.

Args:
    track_index: Track index (0-based).
    monitor: 0=off, 1=normal, 2=not when playing.

Returns:
    Object with success status.
- **set_track_color** — Set the color of a track.

Args:
    track_index: Track index (0-based).
    r: Red component (0-255).
    g: Green component (0-255).
    b: Blue component (0-255).

Returns:
    Object with success status.
- **get_track_peak** — Get the current peak level of a track.

Args:
    track_index: Track index (0-based) or -1 for master.
    channel: Channel (0=left, 1=right).

Returns:
    Object with peak value in dB.
- **get_track_peak_hold** — Get the peak hold level of a track (highest peak since meters were last reset).

Returns the max peak from a previous playback without needing to be actively
playing — play the project, stop, then call this for gain staging.

Args:
    track_index: Track index (0-based) or -1 for master.
    channel: Channel (0=left, 1=right).

Returns:
    Object with peak hold value in dB.
- **clear_all_peak_indicators** — Clear the peak hold indicators on all tracks (including master).

Resets the held peak values that accumulate during playback. Use before a
fresh playback pass when you want clean readings for gain staging.
- **get_track_master_send** — Get the master/parent send state of a track.

Args:
    track_index: Track index (0-based) or -1 for master.

Returns:
    Object with 'ret' field (1 = enabled, 0 = disabled).
- **set_track_master_send** — Enable or disable the master/parent send on a track.

When enabled, the track's audio routes to its parent folder track (or the
master if it has no parent). Disable it when a track should only output
through its sends (e.g., routed exclusively to a bus).

Args:
    track_index: Track index (0-based) or -1 for master.
    enabled: True to enable, False to disable.
- **run_action** — Run a REAPER action by command ID.

Args:
    action_id: REAPER action/command ID number.

Returns:
    Object with success status.
- **run_action_by_name** — Run a REAPER action by its named command id.

Named commands (e.g. ReaScripts "_RS12345" or SWS "_SWS_...") are resolved
to a numeric command id via NamedCommandLookup before firing. A purely numeric
string (e.g. "40297") is treated as a built-in command id and run directly;
prefer run_action(action_id) for built-ins.

Args:
    action_name: Named command id (e.g. "_RS12345") or a numeric command id string.

Returns:
    Object with success status. If the named command cannot be resolved,
    returns {"ok": False, "error": ...} without firing any action.
- **get_fx_presets** — Get list of presets available for an FX.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based).

Returns:
    Object with list of preset names.
- **get_fx_preset** — Get the current preset name of an FX.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based).

Returns:
    Object with current preset name.
- **set_fx_preset** — Set the preset of an FX.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based).
    preset_name: Preset name.

Returns:
    Object with success status.
- **save_fx_preset** — Save the current FX settings as a preset.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based).
    preset_name: Name for the new preset.

Returns:
    Object with success status.
- **get_eq_bands** — Get all ReaEQ band settings in one structured call.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based) of ReaEQ in the FX chain.

Returns:
    Object with a 'bands' list. Each band has band_index, bandtype,
    bandtype_name, bandidx, paramtype, paramtype_name, normval, the
    REAPER-formatted value, and (for gain params) the computed gain_db.
- **set_eq_band** — Set a ReaEQ band parameter.

Pass real values by default: frequency in Hz (paramtype 0), gain in dB
(paramtype 1), or Q (paramtype 2). Gain is converted to ReaEQ's normalized
curve internally; freq and Q are sent raw.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based) of ReaEQ.
    bandtype: -1=master gain, 0=hipass, 1=loshelf, 2=band, 3=notch,
              4=hishelf, 5=lopass, 6=bandpass, 7=parallel bandpass.
    bandidx: Band index within that type (0=first). Ignored for master gain.
    paramtype: 0=frequency (Hz), 1=gain (dB), 2=Q. Ignored for master gain.
    value: The value, in real units unless is_normalized=True.
    is_normalized: If True, value is a raw 0-1 normalized value written directly.

Returns:
    Object with success status (ok).
- **get_eq_band_enabled** — Check whether a ReaEQ band is enabled.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based) of ReaEQ.
    bandtype: Band type (0=hipass, 1=loshelf, 2=band, 3=notch, 4=hishelf, 5=lopass).
    bandidx: Band index within that type (0=first).

Returns:
    Object with 'ret' boolean (true=enabled).
- **set_eq_band_enabled** — Enable or disable a ReaEQ band.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based) of ReaEQ.
    bandtype: Band type (0=hipass, 1=loshelf, 2=band, 3=notch, 4=hishelf, 5=lopass).
    bandidx: Band index within that type (0=first).
    enabled: True to enable, False to disable.

Returns:
    Object with success status (ok).
- **find_eq** — Find ReaEQ on a track, optionally adding it if absent.

Args:
    track_index: Track index (0-based) or -1 for master.
    instantiate: If True and ReaEQ is not present, add it.

Returns:
    Object with 'ret' = the FX index of ReaEQ, or -1 if not found.
- **get_track_fx_chunk** — Get the raw state chunk from an FX plugin (includes preset/state data).

Useful for reading VSTi state data like Toontrack EZkeys chord progressions.
The chunk contains the full serialized state of the plugin.

Args:
    track_index: Track index (0-based) or -1 for master.
    fx_index: FX index (0-based) in the FX chain.

Returns:
    Object with 'chunk' containing the FX state data string.
- **get_project_length** — Get the length of the project (end of last item).

Returns:
    Object with project length in seconds.
- **get_project_summary** — Get a comprehensive summary of the current REAPER project.

Returns everything needed to understand the project state and give
useful mixing/production advice in a single call.

Returns:
    Object with:
    - project_name: Name of the project file
    - project_path: Full path to the project
    - tempo: Project tempo in BPM
    - time_signature: {numerator, denominator}
    - project_length: Length in seconds
    - track_count: Total number of tracks
    - tracks: List of track info objects, each containing:
        - index: Track index (0-based)
        - name: Track name
        - volume_db: Volume in decibels
        - pan: Pan position (-1 to 1)
        - mute: Boolean mute state
        - solo: Boolean solo state
        - fx_count: Number of FX plugins
        - fx_names: List of FX plugin names
    - master: Master track info {volume_db, fx_count, fx_names}
    - markers: List of {index, position, name}
    - regions: List of {index, start, end, name}
- **get_play_position** — Get the current playback position.

Returns:
    Object with play position in seconds.
- **record** — Start recording in REAPER.

Returns:
    Object with success status.
- **pause** — Pause playback in REAPER.

Returns:
    Object with success status.
- **toggle_repeat** — Toggle repeat/loop mode.

Returns:
    Object with new repeat state.
- **get_repeat_state** — Get the current repeat state.

Returns:
    Object with repeat state (true/false).
- **zoom_to_selection** — Zoom the arrange view to the time selection.

Returns:
    Object with success status.
- **zoom_to_project** — Zoom the arrange view to show the entire project.

Returns:
    Object with success status.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `uvx` (confidence: high):

```json
"mcpServers": {
  "reaper-mcp": {
    "command": "uvx",
    "args": ["twelvetake-reaper-mcp"]
  }
}
```

## Documentation & README

# TwelveTake REAPER MCP

A [TwelveTake Studios](https://twelvetake.com) project.

[![Tools](https://img.shields.io/badge/tools-176-blue)](https://github.com/TwelveTake-Studios/reaper-mcp)
[![Buy Me a Coffee](https://img.shields.io/badge/Buy%20Me%20a%20Coffee-support-yellow)](https://buymeacoffee.com/twelvetake)
[![Ko-fi](https://img.shields.io/badge/Ko--fi-support-ff5e5b)](https://ko-fi.com/twelvetake)

**[Setup guide, examples and FAQ -> twelvetake.com/tools/reaper-mcp](https://twelvetake.com/tools/reaper-mcp/)**

A comprehensive Model Context Protocol (MCP) server that enables AI assistants to control REAPER DAW for mixing, mastering, MIDI composition, and full music production workflows.

**Version:** 1.7.3

## Why This Server

### Workflow Automation, Not Just API Wrappers

Most MCP servers just wrap REAPER's API and call it a day. This one includes **production workflow helpers** that handle multi-step operations in a single call:

| Tool | What it does for you |
|------|---------------------|
| `setup_sidechain_compression()` | Creates send, routes to channels 3-4, configures ReaComp's detector input — complete sidechain setup in one call |
| `add_mastering_chain()` | Adds ReaEQ → ReaComp → ReaEQ → ReaLimit to master track with proper signal flow |
| `add_parallel_compression()` | Creates a bus track, sets up the send, adds compressor — NY-style compression ready to blend |
| `create_bus()` | Creates a submix track and routes your specified tracks to it |
| `get_project_summary()` | Returns track count, all track names/volumes/pans/FX, markers, regions, tempo, time signature — everything your AI needs in one call |

### Zero Configuration

- **File-based communication** works immediately — no network setup, no ports to configure
- **Stock REAPER Lua only** — the bridge script has no dependencies, nothing extra to install in REAPER
- Copy the script, run it, connect your AI assistant

### 176 Tools Covering Real Production Needs

- **Full FX control** — add/remove plugins, get/set any parameter by index, manage presets, bypass
- **FX parameter automation** — automate any plugin knob (flanger depth, filter cutoff, etc.)
- **Complete routing** — sends, receives, sidechain routing to specific channel pairs
- **Automation** — create envelopes, add/edit points, set automation modes
- **MIDI** — create items, add notes individually or in batches, edit velocities
- **MIDI editing** — transpose, quantize with swing, humanize, snap to a scale, stretch, legato,
  strum, velocity ramps — each targetable by pitch range, beat window, or channel
- **Audio items** — import, split, duplicate, fade, position, mute
- **Markers & regions** — create, edit, navigate, render by region

## Requirements

- REAPER (any recent version; full live suite green through **REAPER 7.79**)
- Python 3.10+ (for the MCP server)
- An MCP-compatible AI assistant

## Installation

### 1. Install the Bridge Script in REAPER

The bridge script runs inside REAPER and handles communication with the MCP server.

The package can deploy it for you:

```bash
uvx twelvetake-reaper-mcp --install-bridge
```

That copies `reaper_mcp_bridge.lua` into REAPER's Scripts folder for your platform, backing
up any existing copy first. It writes nothing else, and the server never touches your REAPER
installation on its own. Pass a path if REAPER is portable or installed somewhere unusual:
`--install-bridge "/path/to/REAPER/Scripts"`.

To do it by hand instead:

1. Copy `reaper_mcp_bridge.lua` to your REAPER Scripts folder:
   - Windows: `%APPDATA%\REAPER\Scripts\`
   - macOS: `~/Library/Application Support/REAPER/Scripts/`
   - Linux: `~/.config/REAPER/Scripts/`
2. In REAPER: **Actions → Show action list → Load ReaScript**
3. Select `reaper_mcp_bridge.lua` and click **Run**

You should see "REAPER MCP Bridge (File-based, Full API) started" in REAPER's console.

### 2. Install and Configure the MCP Server

The server is published on PyPI as **`twelvetake-reaper-mcp`**. The simplest path is to let your
MCP client launch it with [`uvx`](https://docs.astral.sh/uv/) (or `pipx`) — nothing to install by hand.

Add it to your MCP client's configuration (e.g. `.mcp.json`, or your client's MCP settings):

```json
{
  "mcpServers": {
    "reaper": {
      "command": "uvx",
      "args": ["twelvetake-reaper-mcp"]
    }
  }
}
```

> VS Code uses a top-level `servers` key with `"type": "stdio"` instead of `mcpServers`.

To confirm the server starts on its own:

```bash
uvx twelvetake-reaper-mcp
# or: pipx run twelvetake-reaper-mcp
```

It waits quietly for a client to connect — press Ctrl+C to stop.

### 3. Verify

With REAPER open and the bridge running, ask your assistant **"how many tracks are in my project?"** —
a number back means the server, bridge, and REAPER are all talking.

---

### Run from source (for development)

To work on the server itself, run it from a clone instead of from PyPI. Install the dependencies:

```bash
pip install -r requirements.txt   # or: pip install mcp
```

Point your MCP client at the local script:

```json
{
  "mcpServers": {
    "reaper": {
      "command": "python",
      "args": ["path/to/reaper_mcp_server.py"]
    }
  }
}
```

Then check the connection with:

```bash
python test_connection.py
```

#### Nix flake (optional)

If you use [Nix](https://nixos.org/), the repo ships a flake-based dev shell that provides
Python 3.12 and creates/activates a virtualenv for you:

```bash
# Enter the dev shell manually
nix develop

# Or, with direnv, auto-activate on cd:
direnv allow
```

Then install the dependencies as usual:

```bash
pip install -r requirements.txt
```

This pins the Python version and keeps dependencies isolated from your system.

> **Note:** the `x86_64-linux` dev shell is tested and working. The macOS (Darwin) shells are
> provided but have **not** been tested — confirmation from a macOS user is welcome.

## How It Communicates

The server and the bridge exchange JSON files in a mailbox directory that the bridge script
polls from inside REAPER. There is no network configuration and no port to open.

```
MCP Server                    REAPER Bridge
    │                              │
    ├── writes request_N.json ────►│
    │                              ├── processes request
    │◄── reads response_N.json ────┤
```

**Bridge directory:** REAPER's own `Scripts/mcp_bridge_data`, resolved per platform:

| Platform | Path |
|----------|------|
| Windows | `%APPDATA%\REAPER\Scripts\mcp_bridge_data` |
| macOS | `~/Library/Application Support/REAPER/Scripts/mcp_bridge_data` |
| Linux | `~/.config/REAPER/Scripts/mcp_bridge_data` (or `~/.reaper/...` for older installs) |

Override with `REAPER_BRIDGE_DIR` for portable installs. The server prints the directory it
resolved to stderr on startup, and includes it in any timeout error.

The HTTP transport that shipped alongside this was removed in v1.7.2. It had been deprecated
since v1.2.1, and its request parser could never read a POST body, so no call it was handed
ever reached REAPER. `REAPER_COMM_MODE` no longer does anything.

## Quick Start Examples

### Basic Track Operations
```
"How many tracks are in my project?"
"Create a new track called 'Vocals'"
"Set track 0 volume to -6dB"
"Mute track 2"
"Solo the drums track"
```

### Mixing
```
"Add ReaComp to the bass track"
"Set up sidechain compression from the kick to the bass"
"Create a drum bus and route tracks 0-3 to it"
"Add a mastering chain to the master track"
```

### FX and Parameters
```
"What plugins are on track 0?"
"Get the parameters for the compressor on track 1"
"Set the threshold to -20dB"
"Bypass the EQ on the vocal track"
```

### MIDI Composition
```
"Create a 4-bar MIDI item on track 0"
"Add a C major chord at the start"
"Get all the notes in the MIDI item"
"Set the velocity of note 0 to 100"
```

### Transport and Navigation
```
"Play the project"
"Stop playback"
"Set the cursor to 30 seconds"
"Add a marker called 'Chorus' at the current position"
```

### Project Management
```
"What's the project tempo?"
"Set the tempo to 120 BPM"
"Save the project"
"Render to D:/Output/mix.wav"
```

## Tool Reference

### Track Operations (23 tools)

| Tool | Description |
|------|-------------|
| `get_track_count()` | Get total number of tracks (excluding master) |
| `get_track(index)` | Get track info (name, volume, pan, mute, solo) |
| `get_all_tracks()` | Get info for all tracks |
| `get_master_track()` | Get master track info |
| `insert_track(index, name)` | Create a new track |
| `delete_track(index)` | Delete a track |
| `set_track_name(index, name)` | Rename a track |
| `set_track_volume(index, db)` | Set volume in dB |
| `set_track_pan(index, pan)` | Set pan (-1 to 1) |
| `set_track_mute(index, mute)` | Mute/unmute track |
| `set_track_solo(index, solo)` | Solo/unsolo track |
| `set_track_phase(index, invert)` | Invert phase |
| `set_track_width(index, width)` | Set stereo width (0-2) |
| `set_track_color(index, r, g, b)` | Set track color |
| `get_track_peak(index, channel)` | Get current peak level (dB) |
| `get_track_peak_hold(index, channel)` | Get held peak since last reset (dB) |
| `clear_all_peak_indicators()` | Reset peak hold on all tracks |
| `get_track_master_send(index)` | Get master/parent send state |
| `set_track_master_send(index, enabled)` | Enable/disable master send |
| `set_track_as_folder(index, depth)` | Set as folder parent/child |
| `arm_track(index, arm)` | Arm for recording |
| `set_track_input(index, input)` | Set record input |
| `set_track_monitor(index, mode)` | Set monitor mode |

### FX Operations (16 tools)

| Tool | Description |
|------|-------------|
| `track_fx_get_count(index)` | Count FX on track |
| `track_fx_get_list(index)` | List all FX with details |
| `track_fx_add_by_name(index, name, position?)` | Add FX plugin (optionally at position) |
| `track_fx_move(index, fx_index, new_position)` | Reorder FX in the chain |
| `track_fx_delete(index, fx_index)` | Remove FX |
| `track_fx_get_name(index, fx_index)` | Get FX name |
| `track_fx_get_enabled(index, fx_index)` | Check if enabled |
| `track_fx_set_enabled(index, fx_index, enabled)` | Enable/bypass FX |
| `track_fx_get_num_params(index, fx_index)` | Count parameters |
| `track_fx_get_param_name(index, fx_index, param)` | Get parameter name |
| `track_fx_get_param(index, fx_index, param)` | Get parameter value |
| `track_fx_set_param(index, fx_index, param, value)` | Set parameter value |
| `get_fx_presets(index, fx_index)` | List available presets |
| `get_fx_preset(index, fx_index)` | Get current preset |
| `set_fx_preset(index, fx_index, name)` | Load preset |
| `save_fx_preset(index, fx_index, name)` | Save current settings as preset |

### ReaEQ Operations (5 tools)

Dedicated ReaEQ band control using REAPER's EQ-specific API, which handles ReaEQ's
non-linear parameter curves (dB gain, log frequency, log Q) correctly.

| Tool | Description |
|------|-------------|
| `find_eq(track, instantiate?)` | Find ReaEQ on a track (optionally add it) |
| `get_eq_bands(track, fx)` | Read all ReaEQ bands with human-readable values |
| `set_eq_band(track, fx, bandtype, bandidx, paramtype, value, is_normalized?)` | Set a band parameter (Hz, dB, or Q) |
| `get_eq_band_enabled(track, fx, bandtype, bandidx?)` | Check whether a band is enabled |
| `set_eq_band_enabled(track, fx, bandtype, bandidx?, enabled?)` | Enable/disable a band |

### Take FX Operations (11 tools)

Per-take (per-item) FX, mirroring the track FX tools. Every take is addressed by
`(track_index, item_index, take_index)`.

| Tool | Description |
|------|-------------|
| `take_fx_get_count(track, item, take)` | Count FX on a take |
| `take_fx_get_list(track, item, take)` | List all take FX with details |
| `take_fx_add_by_name(track, item, take, name)` | Add FX plugin to a take |
| `take_fx_delete(track, item, take, fx)` | Remove FX from a take |
| `take_fx_get_name(track, item, take, fx)` | Get take FX name |
| `take_fx_get_enabled(track, item, take, fx)` | Check if enabled |
| `take_fx_set_enabled(track, item, take, fx, enabled)` | Enable/bypass take FX |
| `take_fx_get_num_params(track, item, take, fx)` | Count parameters |
| `take_fx_get_param_name(track, item, take, fx, param)` | Get parameter name |
| `take_fx_get_param(track, item, take, fx, param)` | Get parameter value |
| `take_fx_set_param(track, item, take, fx, param, value)` | Set parameter value |

### Take Management & Comping (7 tools)

Multi-take workflows: list/switch/delete takes, explode/crop, REAPER 7 fixed-lane comping.

| Tool | Description |
|------|-------------|
| `get_takes(track, item)` | List all takes (name + active flag) |
| `get_active_take(track, item)` | Get the active take index |
| `set_active_take(track, item, take)` | Switch which take plays |
| `explode_takes(track, item)` | Explode takes to overlapping items (in place) |
| `crop_to_active_take(track, item)` | Keep only the active take |
| `delete_take(track, item, take)` | Delete a specific take |
| `select_comp_lane(track, lane)` | Play one fixed lane exclusively (lane comping) |

### Routing (9 tools)

| Tool | Description |
|------|-------------|
| `create_send(src, dest)` | Create send between tracks |
| `delete_send(index, send_index)` | Remove a send |
| `set_send_volume(index, send_index, db)` | Set send level |
| `get_track_num_sends(index)` | Count sends from track |
| `set_send_dest_channels(index, send_index, chan)` | Route to specific channels |
| `set_send_source_channels(index, send_index, chan)` | Set source channels |
| `setup_sidechain_send(src, dest, db)` | Create sidechain send |
| `configure_reacomp_sidechain(index, fx_index, use)` | Configure ReaComp sidechain |
| `setup_sidechain_compression(trigger, target, fx, db)` | Complete sidechain setup |

### Transport (10 tools)

| Tool | Description |
|------|-------------|
| `play()` | Start playback |
| `stop()` | Stop playback |
| `pause()` | Pause playback |
| `record()` | Start recording |
| `get_play_state()` | Get current state (playing/paused/recording) |
| `get_cursor_position()` | Get edit cursor position (seconds) |
| `set_cursor_position(seconds)` | Move edit cursor |
| `get_play_position()` | Get playback position (seconds) |
| `toggle_repeat()` | Toggle loop mode |
| `get_repeat_state()` | Check if looping |

### Project (15 tools)

| Tool | Description |
|------|-------------|
| `get_project_summary()` | Get comprehensive project state in one call |
| `save_project()` | Save current project |
| `create_project()` | Create new project (REAPER cannot name an unsaved project) |
| `open_project(path)` | Open project file |
| `get_project_path()` | Get project directory |
| `get_project_name()` | Get project filename |
| `get_project_length()` | Get project length (seconds) |
| `get_tempo()` | Get project tempo (BPM) |
| `set_tempo(bpm)` | Set project tempo |
| `get_time_signature()` | Get time signature |
| `set_time_signature(num, denom)` | Set time signature |
| `render_project(path, start, end, tail)` | Render to audio file |
| `render_region(index, path)` | Render specific region |
| `zoom_to_selection()` | Zoom to time selection |
| `zoom_to_project()` | Zoom to show entire project |

### MIDI Operations (8 tools)

| Tool | Description |
|------|-------------|
| `create_midi_item(track, pos, length)` | Create empty MIDI item |
| `get_midi_item(track, item)` | Get MIDI item info |
| `add_midi_note(track, item, pitch, vel, start_beat, length_beats, chan)` | Add single note (beats) |
| `add_midi_notes_batch(track, item, notes)` | Add multiple notes |
| `get_midi_notes(track, item)` | Get all notes |
| `delete_midi_note(track, item, note)` | Delete a note |
| `clear_midi_item(track, item)` | Delete all notes |
| `set_midi_note_velocity(track, item, note, vel)` | Change note velocity |

### MIDI Utilities (13 tools)

Editing tools for notes that already exist. Every one takes the same optional filter — a pitch
range, an onset window in beats from the item start, and a channel — so you can target a phrase
without selecting anything by hand. Timing is in beats, pitch in semitones. Each is one undo step.

| Tool | Description |
|------|-------------|
| `transpose_midi_notes(track, item, semitones, ...)` | Shift pitch; notes pushed outside 0-127 are left alone, never wrapped |
| `snap_midi_notes_to_scale(track, item, root, mode, ...)` | Snap off-key notes onto a scale (named or a custom interval list) |
| `quantize_midi_notes(track, item, grid, strength, swing, ...)` | Snap onsets to the project grid, with strength and swing |
| `nudge_midi_notes(track, item, amount_beats, ...)` | Shift notes in time; lengths preserved |
| `stretch_midi_notes(track, item, factor, pivot_beat, ...)` | Scale timing about a pivot (half-time / double-time) |
| `legato_midi_notes(track, item, mode, voice, ...)` | Close the gaps in a line, or set every note to one length |
| `humanize_midi_notes(track, item, timing, velocity, seed, ...)` | Seeded, reproducible timing + velocity jitter |
| `strum_midi_notes(track, item, spread_beats, direction, ...)` | Roll a chord out into a strum |
| `ramp_midi_note_velocities(track, item, start_vel, end_vel, ...)` | Linear velocity ramp across a phrase (crescendo) |
| `scale_midi_note_velocities(track, item, mode, ratio, ...)` | Multiply / set / compress velocities |
| `set_midi_note(track, item, note, ...)` | Edit one note's pitch, velocity, timing, channel |
| `get_selected_midi_notes(track, item)` | Read the notes selected in REAPER's editor |
| `remove_overlapping_midi_notes(track, item, mode, ...)` | Trim or delete overlapping same-pitch notes |

`remove_overlapping_midi_notes` is the only one here that can remove notes; the rest only move
what is already there. It is flagged `destructive` so a client can prompt first.

### Audio Items (17 tools)

| Tool | Description |
|------|-------------|
| `insert_audio_file(track, path, pos)` | Import audio file |
| `get_track_items(track)` | List all items on track |
| `get_item_info(track, item)` | Get item details |
| `set_item_position(track, item, pos)` | Move item |
| `set_item_length(track, item, length)` | Change item length |
| `delete_item(track, item)` | Delete item |
| `duplicate_item(track, item)` | Duplicate item |
| `split_item(track, item, pos)` | Split item at position |
| `set_item_mute(track, item, mute)` | Mute/unmute item |
| `set_item_volume(track, item, db)` | Set item volume |
| `set_item_fade_in(track, item, length)` | Set fade-in |
| `set_item_fade_out(track, item, length)` | Set fade-out |
| `select_all_items()` | Select all items |
| `unselect_all_items()` | Deselect all items |
| `get_selected_items()` | Get selected items |
| `copy_selected_items()` | Copy to clipboard |
| `paste_items()` | Paste from clipboard |

### Markers & Regions (8 tools)

| Tool | Description |
|------|-------------|
| `add_marker(pos, name, color)` | Add marker |
| `add_region(start, end, name, color)` | Add region |
| `get_markers()` | Get all markers |
| `get_regions()` | Get all regions |
| `delete_marker(index)` | Delete marker |
| `delete_region(index)` | Delete region |
| `go_to_marker(index)` | Jump to marker |
| `go_to_region(index)` | Jump to region start |

### Automation (8 tools)

| Tool | Description |
|------|-------------|
| `get_track_envelope(track, name)` | Get envelope by name |
| `get_envelope_point_count(track, name)` | Count envelope points |
| `add_envelope_point(track, name, time, value, shape)` | Add automation point |
| `get_envelope_points(track, name)` | Get all points |
| `delete_envelope_point(track, name, index)` | Delete point |
| `clear_envelope(track, name)` | Clear all points |
| `set_track_automation_mode(track, mode)` | Set automation mode |
| `arm_track_envelope(track, name, arm)` | Arm envelope for recording |

### FX Parameter Automation (5 tools)

| Tool | Description |
|------|-------------|
| `get_fx_envelope(track, fx, param)` | Get/create envelope for any FX parameter |
| `add_fx_envelope_point(track, fx, param, time, value, shape)` | Add automation point to FX parameter |
| `get_fx_envelope_points(track, fx, param)` | Get all points from FX envelope |
| `delete_fx_envelope_point(track, fx, param, point)` | Delete point from FX envelope |
| `clear_fx_envelope(track, fx, param)` | Clear all points from FX envelope |

### Selection & Editing (11 tools)

| Tool | Description |
|------|-------------|
| `undo()` | Undo last action |
| `redo()` | Redo last undone action |
| `get_undo_state()` | Get undo/redo state |
| `select_track(index, exclusive)` | Select a track |
| `select_all_tracks()` | Select all tracks |
| `unselect_all_tracks()` | Deselect all tracks |
| `get_selected_tracks()` | Get selected track indices |
| `set_time_selection(start, end)` | Set time selection |
| `get_time_selection()` | Get time selection |
| `clear_time_selection()` | Clear time selection |
| `delete_selected_items()` | Delete selected items |

### Mixing Helpers (6 tools)

| Tool | Description |
|------|-------------|
| `add_mastering_chain()` | Add EQ→Comp→EQ→Limiter to master |
| `add_parallel_compression(track, db)` | Set up NY compression |
| `create_bus(name, tracks)` | Create submix bus |
| `add_eq(track)` | Add ReaEQ |
| `add_compressor(track)` | Add ReaComp |
| `add_limiter(track)` | Add ReaLimit |

### Advanced (4 tools)

| Tool | Description |
|------|-------------|
| `run_action(action_id)` | Run REAPER action by ID |
| `run_action_by_name(name)` | Run action by name |
| `get_track_fx_chunk(track, fx)` | Get raw FX state data |
| `cut_selected_items()` | Cut items to clipboard |

## Track Indexing

- **Regular tracks:** 0-based index (first track = 0)
- **Master track:** Use index `-1`

```
"Set the master track volume to -3dB"  → track_index = -1
"Mute track 1"                          → track_index = 1 (second track)
```

## Common Plugin Names

Use these names with `track_fx_add_by_name()`:

| Plugin | Name |
|--------|------|
| EQ | `ReaEQ` |
| Compressor | `ReaComp` |
| Limiter | `ReaLimit` |
| Gate | `ReaGate` |
| Delay | `ReaDelay` |
| Reverb | `ReaVerbate` or `ReaVerb` |

Third-party plugins use their full name as shown in REAPER's FX browser.

## Troubleshooting

### "Cannot connect to REAPER"
1. Ensure REAPER is running
2. Ensure the bridge script is running (check REAPER's console)
3. Verify the bridge directory exists

### "Track not found"
- Track indices are 0-based
- Use `-1` for master track
- Check track count with `get_track_count()`

### Bridge script won't load
- Deploy it with `twelvetake-reaper-mcp --install-bridge`, then load and run it from
  REAPER's action list. REAPER runs the deployed copy, not the one in a clone.

### Slow response
- File-based mode has ~50ms latency per call
- Batch operations when possible (e.g., `add_midi_notes_batch`)

## Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `REAPER_BRIDGE_DIR` | REAPER's `Scripts/mcp_bridge_data`, resolved per platform | File bridge directory |
| `REAPER_FILE_TIMEOUT` | `5.0` | Seconds to wait for the bridge to answer. The bridge answers only once the work finishes, and renders run at roughly realtime, so a render longer than this reports a timeout while REAPER completes it normally. Raise it when rendering; note that a genuinely unreachable bridge then also takes this long to report. |
| `REAPER_MCP_DEBUG` | unset | Set to `1` before launching REAPER for per-call bridge console logging |

## Contributing

See [CONTRIBUTING.md](https://github.com/TwelveTake-Studios/reaper-mcp/blob/HEAD/CONTRIBUTING.md). Read the first section before writing a patch: this
repo is published from a private working repo through an explicit allowlist, so PRs are ported
by hand rather than merged, and it is better to know that up front. A well-diagnosed issue is
worth as much here as a patch and costs you far less.

## Contributors

People outside the project whose work is in this software are listed in
[CONTRIBUTORS.md](https://github.com/TwelveTake-Studios/reaper-mcp/blob/HEAD/CONTRIBUTORS.md), including several whose diagnoses shipped before anyone
here thought to look for them.

## License

MIT License - see [LICENSE](https://github.com/TwelveTake-Studios/reaper-mcp/blob/HEAD/LICENSE)

---

**TwelveTake Studios LLC**
Website: [twelvetake.com](https://twelvetake.com)
Contact: contact@twelvetake.com

