diff --git a/README.md b/README.md new file mode 100644 index 0000000..176765c --- /dev/null +++ b/README.md @@ -0,0 +1,402 @@ +# ableton-mcp + +A full-featured [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that gives AI assistants (Claude, etc.) complete control over Ableton Live via [AbletonOSC](https://github.com/ideoforms/AbletonOSC). + +## Overview + +**ableton-mcp** bridges AI assistants and Ableton Live by exposing **124 MCP tools** covering every major area of the Live API: transport control, track mixing, clip launching and editing, MIDI note manipulation, device parameter control, scene management, view selection, and real-time property listeners. + +The server communicates with Ableton Live via [AbletonOSC](https://github.com/ideoforms/AbletonOSC) — a MIDI Remote Script that runs inside Live and exposes its internal API over OSC/UDP. + +``` +Claude (AI) ──MCP tools──▶ ableton-mcp ──OSC/UDP──▶ AbletonOSC ──Live Object Model──▶ Ableton Live +``` + +--- + +## Requirements + +- **Ableton Live 11 or later** +- **Python 3.10+** (tested with 3.10.6 via pyenv) +- **AbletonOSC** installed as a MIDI Remote Script (see below) + +--- + +## Installation + +### 1. Install AbletonOSC in Ableton Live + +1. Download or clone [AbletonOSC](https://github.com/ideoforms/AbletonOSC) +2. Copy the `AbletonOSC` folder into your Ableton User Library Remote Scripts directory: + - **Windows:** `%USERPROFILE%\Documents\Ableton\User Library\Remote Scripts` + - **macOS:** `~/Music/Ableton/User Library/Remote Scripts` +3. Restart Ableton Live +4. Open **Preferences → Link, Tempo & MIDI → MIDI** and set one of the Control Surface slots to **AbletonOSC** + +AbletonOSC will now listen on **UDP port 11000** and reply on **port 11001**. + +### 2. Install ableton-mcp + +```bash +# Clone the repo (rename the folder after cloning) +git clone https://github.com/your-user/ableton-mcp +cd ableton-mcp + +# Create and activate a virtual environment +python -m venv .venv +.venv\Scripts\activate # Windows +# source .venv/bin/activate # macOS/Linux + +# Install +pip install -e . +``` + +### 3. Configure Claude Desktop + +Add the server to your Claude Desktop configuration file: + +**Windows:** `%APPDATA%\Claude\claude_desktop_config.json` +**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json` + +```json +{ + "mcpServers": { + "ableton": { + "command": "C:\\path\\to\\ableton-mcp\\.venv\\Scripts\\ableton-mcp.exe" + } + } +} +``` + +Restart Claude Desktop. The Ableton Live tools will appear automatically when a Live session is open. + +--- + +## Configuration + +All settings can be overridden via environment variables: + +| Variable | Default | Description | +|---|---|---| +| `ABLETON_HOST` | `127.0.0.1` | AbletonOSC host address | +| `ABLETON_SEND_PORT` | `11000` | Port to send OSC commands to Live | +| `ABLETON_RECEIVE_PORT` | `11001` | Port to receive OSC replies from Live | +| `ABLETON_TIMEOUT` | `5.0` | Query timeout in seconds | + +Example (Claude Desktop config with custom port): +```json +{ + "mcpServers": { + "ableton": { + "command": "C:\\path\\to\\.venv\\Scripts\\ableton-mcp.exe", + "env": { + "ABLETON_TIMEOUT": "10.0" + } + } + } +} +``` + +--- + +## Tool Reference + +### System (7 tools) + +| Tool | Description | +|---|---| +| `system_test_connection` | Ping AbletonOSC; returns `{connected, latency_ms}` | +| `system_get_version` | Get Ableton Live version `{major, minor}` | +| `system_get_cpu_usage` | Get average CPU process usage (0.0–1.0) | +| `system_show_message` | Display a message in the Ableton status bar | +| `system_get_log_level` | Get current AbletonOSC log level | +| `system_set_log_level` | Set log level: `debug` / `info` / `warning` / `error` / `critical` | +| `system_reload_api` | Hot-reload all AbletonOSC modules | + +--- + +### Song / Transport (48 tools) + +#### Playback +| Tool | Description | +|---|---| +| `song_get_state` | Full snapshot: tempo, is_playing, loop, time sig, track/scene counts, groove, link, scale… | +| `song_start_playing` | Start playback from current position | +| `song_stop_playing` | Stop playback | +| `song_continue_playing` | Resume without resetting to start | +| `song_stop_all_clips` | Stop all active clips | +| `song_tap_tempo` | Send a tap-tempo pulse | + +#### Tempo & Time +| Tool | Description | +|---|---| +| `song_get_tempo` / `song_set_tempo` | Get/set BPM (20–300) | +| `song_get_time` / `song_set_time` | Get/jump playhead position in beats | +| `song_get_time_signature` / `song_set_time_signature` | Get/set numerator + denominator | + +#### Loop +| Tool | Description | +|---|---| +| `song_get_loop` | Get `{loop, loop_start, loop_length}` | +| `song_set_loop` | Set on/off, start, and length (beats) | + +#### Metronome, Quantization & Groove +| Tool | Description | +|---|---| +| `song_get_metronome` / `song_set_metronome` | Toggle metronome | +| `song_set_quantization` | Clip trigger quantization (0=none … 13=1/32) | +| `song_set_midi_recording_quantization` | MIDI recording quantization | +| `song_set_groove` | Global groove amount (0.0–1.0) | + +#### Recording +| Tool | Description | +|---|---| +| `song_set_arrangement_overdub` | Enable/disable arrangement overdub | +| `song_set_session_record` | Enable/disable session record | +| `song_set_punch_in` / `song_set_punch_out` | Punch in/out | +| `song_trigger_session_record` | Toggle session record | +| `song_capture_midi` | Capture incoming MIDI into a new clip | +| `song_re_enable_automation` | Re-enable overridden automation | + +#### Edit +| Tool | Description | +|---|---| +| `song_undo` / `song_redo` | Undo/redo | + +#### Tracks +| Tool | Description | +|---|---| +| `song_get_tracks` | List all tracks with index and name | +| `song_create_audio_track` | Create audio track at index | +| `song_create_midi_track` | Create MIDI track at index | +| `song_create_return_track` | Create return/aux track | +| `song_delete_track` / `song_delete_return_track` | Delete track | +| `song_duplicate_track` | Duplicate a track | + +#### Scenes +| Tool | Description | +|---|---| +| `song_get_scenes` | List all scenes with index and name | +| `song_create_scene` / `song_delete_scene` / `song_duplicate_scene` | Scene CRUD | +| `song_capture_and_insert_scene` | Capture session state as a new scene | + +#### Cue Points +| Tool | Description | +|---|---| +| `song_get_cue_points` | List all cue points | +| `song_jump_to_cue` | Jump to cue by index or name | +| `song_add_or_delete_cue` | Toggle cue at current playhead | +| `song_set_cue_name` | Rename a cue point | + +#### Scale & Link +| Tool | Description | +|---|---| +| `song_set_root_note` | Set root note (0=C … 11=B) | +| `song_set_scale_name` | Set scale name e.g. `"Major"`, `"Dorian"` | +| `song_set_ableton_link` | Enable/disable Ableton Link | +| `song_export_structure` | Export full session structure to JSON file | + +--- + +### Tracks (19 tools) + +| Tool | Description | +|---|---| +| `track_get_info` | All track properties: name, volume, pan, mute, solo, arm, type, color, monitoring, meter slots, device count | +| `track_set_volume` | Volume (0.0=−∞, 0.85≈0 dB, 1.0=+6 dB) | +| `track_set_pan` | Panning (−1.0=left, 0.0=center, +1.0=right) | +| `track_set_mute` | Mute / unmute | +| `track_set_solo` | Solo / unsolo | +| `track_set_arm` | Arm / disarm for recording | +| `track_get_send` / `track_set_send` | Get/set send level to a return track | +| `track_set_name` | Rename a track | +| `track_set_color` | Set track color by palette index (0–69) | +| `track_stop_clips` | Stop all clips on a track | +| `track_set_monitoring` | Monitoring state: 0=Auto, 1=In, 2=Off | +| `track_set_fold` | Fold/unfold a group track | +| `track_get_clips` | List session clips: index, name, length, color | +| `track_get_arrangement_clips` | List arrangement clips: index, name, length, start_time | +| `track_get_devices` | List devices: index, name, type, class_name | +| `track_get_meter` | Output meter levels: left, right, level | +| `track_delete_device` | Remove a device from the chain | +| `track_get_available_input_routing_types` | List available input routing options | +| `track_set_input_routing_type` / `track_set_output_routing_type` | Set I/O routing | + +--- + +### Clips (19 tools) + +| Tool | Description | +|---|---| +| `clip_fire` | Launch a clip | +| `clip_stop` | Stop a playing clip | +| `clip_get_info` | All clip properties: name, length, playing state, loop, markers, pitch, gain, launch mode, warp, color | +| `clip_set_name` | Rename a clip | +| `clip_set_color` | Set clip color by palette index (0–69) | +| `clip_set_gain` | Set clip gain in dB | +| `clip_set_muted` | Mute/unmute a clip | +| `clip_set_looping` | Enable/disable loop | +| `clip_set_loop_points` | Set loop start + end in beats | +| `clip_set_markers` | Set start + end markers in beats | +| `clip_set_pitch` | Transpose: semitones (−48 to +48) and cents (−50 to +50) | +| `clip_set_warp_mode` | Warp mode: 0=Beats, 1=Tones, 2=Texture, 3=Re-Pitch, 4=Complex, 5=Complex Pro | +| `clip_set_warping` | Enable/disable warping | +| `clip_set_launch_mode` | Launch mode: 0=Trigger, 1=Gate, 2=Toggle, 3=Repeat | +| `clip_set_launch_quantization` | Per-clip launch quantization (0–14) | +| `clip_set_legato` | Enable/disable legato mode | +| `clip_duplicate_loop` | Double loop length by duplicating content | +| `clip_get_notes` | Get MIDI notes (filterable by pitch range + time range) | +| `clip_add_notes` | Add MIDI notes: `[{pitch, start, duration, velocity, mute}, …]` | +| `clip_remove_notes` | Remove MIDI notes by pitch/time range | + +--- + +### Clip Slots (7 tools) + +| Tool | Description | +|---|---| +| `clip_slot_get_info` | Slot state: has_clip, is_playing, is_triggered, will_record, is_group_slot | +| `clip_slot_fire` | Trigger a slot (launches clip or starts recording if empty) | +| `clip_slot_stop` | Stop a slot | +| `clip_slot_create_clip` | Create a new empty MIDI clip with a given length in beats | +| `clip_slot_delete_clip` | Delete the clip in a slot | +| `clip_slot_duplicate_to` | Copy clip from one slot to another | +| `clip_slot_set_stop_button` | Enable/disable the stop button for a slot | + +--- + +### Scenes (7 tools) + +| Tool | Description | +|---|---| +| `scene_fire` | Launch a scene by index | +| `scene_fire_selected` | Launch the currently selected scene | +| `scene_get_info` | All scene properties: name, color, tempo, tempo_enabled, time sig, is_empty, is_triggered | +| `scene_set_name` | Rename a scene | +| `scene_set_color` | Set scene color by palette index (0–69) | +| `scene_set_tempo` | Override tempo for a scene (with enable toggle) | +| `scene_set_time_signature` | Override time signature for a scene | + +--- + +### Devices (6 tools) + +| Tool | Description | +|---|---| +| `device_get_info` | Device info: name, type (audio_effect / instrument / midi_effect), class_name, num_parameters | +| `device_get_parameters` | All parameters: index, name, value, min, max, is_quantized | +| `device_get_parameter` | Single parameter: name, value, value_string (display value) | +| `device_set_parameter` | Set a parameter value (normalized 0.0–1.0) | +| `device_set_parameters_bulk` | Set multiple parameters in one call: `[{index, value}, …]` | +| `device_map_midi_cc` | Map a MIDI CC to a device parameter | + +--- + +### View / Selection (5 tools) + +| Tool | Description | +|---|---| +| `view_get_selection` | Current selection: selected_track, selected_scene, selected_clip, selected_device | +| `view_set_selected_track` | Select a track in the Ableton UI | +| `view_set_selected_scene` | Select a scene in the Ableton UI | +| `view_set_selected_clip` | Select a clip in the Ableton UI | +| `view_set_selected_device` | Select a device in the Ableton UI | + +--- + +### Real-time Listeners (3 tools) + +Subscribe to property changes from Ableton Live and poll them at any time. + +| Tool | Description | +|---|---| +| `listener_start` | Register a listener for a property; returns the OSC response address | +| `listener_stop` | Unregister a listener | +| `listener_get_events` | Drain queued events (up to `max_events`); each event is a list of OSC args | + +**Supported objects and properties:** + +| object_type | property_name | Notes | +|---|---|---| +| `song` | `beat` | `[beat_number, beat_index, bar, song_time]` per beat | +| `song` | `is_playing` | `[0/1]` | +| `song` | `tempo` | `[bpm]` | +| `song` | `current_song_time` | `[beats]` | +| `view` | `selected_track` | `[track_index]` | +| `view` | `selected_scene` | `[scene_index]` | +| `track` | any read/write property | requires `track_index` | +| `clip` | any read/write property | requires `track_index` + `clip_index` | +| `clip_slot` | any property | requires `track_index` + `clip_index` | + +**Example usage with Claude:** +> "Watch the beat and tell me the bar number every 4 bars" +> Claude calls `listener_start("song", "beat")`, then periodically `listener_get_events("song", "beat")` + +--- + +## Example Prompts + +``` +Play the song from bar 3 at 128 BPM with the metronome on. + +Create a 4-bar MIDI clip on track 0, slot 1, and add a C minor chord +(C3, Eb3, G3) on beats 1 and 3 with velocity 90. + +Set every device on track 2 to a random-ish sound: +randomize all parameters whose name contains "Filter". + +Loop scene 4 forever: set scene 4's clips to looping and fire the scene. + +Solo track 3, mute tracks 1 and 2, and set track 3's reverb send to 0.4. + +Name all empty scenes "—" and delete any track with no clips. +``` + +--- + +## Architecture + +``` +src/ableton_mcp/ +├── server.py # FastMCP app; registers all tool modules +├── osc_client.py # Thread-safe OSCClient singleton +│ # cmd() – fire-and-forget +│ # query() – synchronous request/response +│ # start_listener() / drain_listener() – real-time events +├── config.py # Port/host/timeout from env vars +└── tools/ + ├── song.py # Transport, tempo, loop, scenes, tracks, cues, scale, Link + ├── track.py # Mixing, routing, devices list, meter + ├── clip.py # Playback, MIDI notes, pitch, warp, loop, launch + ├── clip_slot.py # Slot-level create/fire/delete/duplicate + ├── scene.py # Scene fire, tempo/time-sig overrides + ├── device.py # Parameter get/set (individual + bulk), MIDI CC mapping + ├── view.py # Selected track/scene/clip/device + ├── system.py # Connectivity, version, CPU, status bar, logging + └── listener.py # Real-time property change subscriptions +``` + +**OSC communication** uses `python-osc` directly against AbletonOSC's full 200+ endpoint API. The `OSCClient` singleton runs a `ThreadingOSCUDPServer` on the receive port in a daemon thread. Queries use `threading.Event` for synchronous blocking with configurable timeout. Listeners store events in per-address `collections.deque` instances for thread-safe polling. + +--- + +## Troubleshooting + +**`system_test_connection` returns `connected: false`** +- Confirm AbletonOSC is enabled in Ableton → Preferences → Control Surfaces +- Make sure a Live project is open (AbletonOSC only activates with an open set) +- Check that no firewall is blocking UDP 11000/11001 on localhost + +**Timeout errors on queries** +- Increase `ABLETON_TIMEOUT` env var (default 5 s) +- Some properties (e.g. `song_get_state`) issue many sequential OSC queries; slow machines may need a higher timeout + +**`No module named 'ableton_mcp'`** +- Make sure you installed with `pip install -e .` inside the correct `.venv` +- The Claude Desktop config `command` path must point to the `.venv` executable, not the system Python + +--- + +## License + +MIT