Skip to main content
Glama

rubin

tests

A minimal, dependency-free MCP server that turns Logic Pro into an AI-playable instrument: compose MIDI, load any factory patch (Alchemy included), and drive the transport.

A rubin-composed 40-bar arrangement playing in Logic Pro — five tracks
composed as MIDI, Alchemy patches loaded via the Library, piano roll showing
the lead hook

Everything in this screenshot was placed by rubin: the five tracks and their regions (composed as a Standard MIDI File), the Alchemy patches on each channel strip (loaded through the Library), tempo, and key.

Why it works

UI-automation-first tools (clicking Logic's interface for every note) fail silently and read garbage state. rubin flips the approach:

  • Composition is a file, not clicks. Notes, CC automation, pitch bends, tempo, and time signature are written into a real Standard MIDI File — deterministic, testable, no UI involved. Logic maps each track's GM program to a software instrument on import.

  • Patch discovery is a disk read, not a guess. Logic's ~6,000 factory patches live on disk with the synth engine recorded inside each bundle; find_patches filters them by name, category, or engine (Alchemy, Retro Synth, ES2, Sculpture...).

  • UI scripting is a last resort, and verified. Only patch loading, import, and transport touch the UI — anchored to stable accessibility landmarks (the Library's "Search Sounds" field, track-header fields), with list_tracks reading real state back so nothing fails silently.

Related MCP server: garageband-llm-bridge

Tools

Tool

What it does

compose_midi

Write a .mid from notes / CC / bends / volume / pan per track

open_midi_as_project

Open a .mid in Logic as a new project (no dialogs, most reliable)

import_midi

File > Import > MIDI File into the open project at bar 1

compose_and_import

Both steps in one call

transcribe_audio

Audio → MIDI via basic-pitch, content-hash cached

list_transcriptions

Browse the transcription cache

analyze_midi

Full analysis: key/swing/chord detection, density contour, warnings, per-track stats

describe_midi

The same, as one readable paragraph

suggest_accompaniment

Analysis → ready compose_midi args that fit a source file (registers, key, feel)

cut_samples

Cut/arrange WAV sample slices on a tempo grid into a rendered song (no DAW)

render_midi_audio

Bounce a .mid to WAV with the built-in synth (mix MIDI arrangements with sample cuts)

analyze_audio

RMS energy contour of a WAV — 'see' a render's dynamics without hearing it

catalog_samples

Report each sample's pitch, key, and kind (pitched/sub/noise)

reveal_in_finder

Reveal a file/folder in Finder, ready to drag onto a track

import_audio

Reveal + drag guidance (Logic audio import isn't scriptable)

find_patches

Search the on-disk factory patch index (name / category / engine)

find_surge_presets

Discover installed Surge XT presets (load via Surge's browser)

find_channel_strips

Discover factory FX-chain settings (.cst) — names for the Setting menu; not Library-loadable

select_track

Select track N (1-based)

load_patch

Load a Library patch onto the selected track; returns the loaded name

list_tracks

Read back [{name, patch}] from the open project

save_project

Save the project (Cmd+S); reports the sheet if the project is unnamed

transport

play / stop / record / go_to_beginning

answer_dialog

Click through a Logic sheet/dialog (e.g. tempo-import prompt)

logic_status

Is Logic running + front window title

Note format: {start, dur, pitch, vel} — start/dur in beats, pitch 0–127 (60 = middle C), velocity 1–127. Channel 9 is GM drums (36 kick, 38 snare, 39 clap, 42 closed hat, 46 open hat). Tracks also accept cc ({beat, controller, value}), bends ({beat, value} −8192…8191), program, volume, pan. Compositions accept time_sig ([3, 4]), key ("Am", "Eb", "F#m"), tempo_changes ([{beat, bpm}]), and swing (50 = straight, ~62 = MPC feel; swing_unit 0.5/0.25 for 8th/16th swing; per-track override supported). Tracks can declare a progression ({chords: ["Am", "F", "Cmaj7", "E7"]}) instead of raw notes — voicings are generated (styles: pad / bass / arp / melody — the latter writes a hook: chord tones on strong beats, one peak, resolves to the root), with repeat and start_bar for staged entrances; drums: {pattern: "half_time"} generates a full groove. analyze_midi reads the same chord names back.

Example: a full backing track from declarations

compose_midi {tempo: 85, key: "Am", name: "sketch", tracks: [
  {name: "Pad",   channel: 1, progression: {chords: ["Am","F","C","E"], repeat: 2}},
  {name: "Drums", channel: 9, drums: {pattern: "half_time", bars: 12, start_bar: 4}},
  {name: "Bass",  channel: 0, progression: {chords: ["Am","F","C","E"], style: "bass", start_bar: 4}},
  {name: "Arp",   channel: 2, progression: {chords: ["Am","F","C","E"], style: "arp", start_bar: 8}},
]}
describe_midi {path: "~/Desktop/sketch.mid"}
  → 4 track(s), 283 notes, ~16 bars at 85 BPM. key Am (82% confident).
    progression Am-F-C-E... dynamic arrangement (density 0.4-6.7 notes/beat). ...

Staged entrances (start_bar) give the arrangement a real build; the analyzer verifies its own output — key, chords, contour, and warnings for register clashes or robotic velocities.

Example: an Alchemy pad in three calls

find_patches {category: "Synthesizer/Pad", plugin: "Alchemy"}
  → [{"name": "Drifting Away", ...}, ...]
select_track {index: 3}
load_patch {query: "Drifting Away"}
  → Loaded patch 'Drifting Away' on the selected track

Genre starting points (stock Logic, index-verified)

Role

Patches (exact load_patch names)

Future-bass chord stabs

Massive Stack, Chicago Stack, Sparkle Chord (category EDM Chord)

Vocal-chop feel

Breathy Vox, Starlight Vox, Rhythmic Vox

Sub bass

Deep Sub Bass, Full Sub, Super Sub 808 Bass

Wobble bass (CC1 = wobble)

Mega Wobble Bass, FM Wobble Bass, Sharp Wobble Bass

Dark pads

Drifting Away (Alchemy)

Trap/808 kits

Modern 808; dub: Dub Machine

Punk/rock

SoCal+ kit, Hard Rock guitar, Punchy Bottom bass

rubin's CC automation maps onto these: cc 1 drives the wobble patches' filter, cc 11 fakes sidechain pump on stabs, bends do scoops and slides.

Ears: audio in, MIDI out

transcribe_audio turns any recording — a hummed melody, a guitar take, a sample — into MIDI with Spotify's basic-pitch, cached by content hash under ~/.cache/rubin/midi so repeat calls are free. analyze_midi then reads the result back (rubin has its own SMF parser) and reports what the instrument plays: pitch range, note density, polyphony. That loop — hear it, read it, compose against it — is how rubin learns what source material sounds like.

Cut samples into a song

rubin edits audio the way it composes MIDI — as a deterministic file operation, never through a DAW's region UI. cut_samples loads WAV files (16/24/32-bit), slices/pitches/reverses/fades them, and arranges the chops on a tempo grid into a rendered mix:

catalog_samples {folder: "~/Music/Samples/dubstep"}
  → Evil_1: sub, key E · Down_Growl: noise · ...
cut_samples {tempo: 140, out_path: "~/Desktop/drop.wav", events: [
  {file: ".../Evil_1.wav", at_beat: 0, repeat: {times: 8, every: 4}},        # E sub every bar
  {file: ".../Down_Growl.wav", at_beat: 0, end: 0.2, pitch: 3,
   repeat: {times: 32, every: 0.5}},                                          # growl chops
]}

catalog_samples first tells you each sample's pitch and whether it's tonal or texture, so you can tune and place them. Output is a standard WAV any DAW imports (or drag it in via reveal_in_finder). limit: true soft-clips dense stacks so one loud hit doesn't thin the mix.

Install

# from a clone (development)
claude mcp add --scope user rubin -- /usr/bin/python3 ~/dev/rubin/server.py

# or installed as a package
pip install git+https://github.com/mrcolo/rubin
claude mcp add --scope user rubin -- rubin-mcp
python3 server.py --check   # environment health: Logic, indexes, AUs, transcription
python3 server.py --verify song.mid   # pre-flight: analysis + warnings, exit 1 if dirty
# optional, for transcribe_audio:
cd ~/dev/rubin && python3 -m venv .venv-bp && .venv-bp/bin/pip install basic-pitch

Requires macOS Accessibility + Automation permissions for the host app (Claude / terminal) — only for the UI-touching tools. compose_midi, find_patches, and open_midi_as_project need no permissions at all.

Test & demo

python3 -m unittest discover -s tests   # 76 tests, no Logic needed for most
python3 server.py --demo                # compose + open a demo beat in Logic
python3 server.py --demo --write-only   # just write the .mid
python3 server.py --cut-demo demo.wav   # render a from-scratch sample cut (no external files)

Files

  • server.py — MCP stdio server (hand-rolled JSON-RPC, no SDK)

  • midi.py — Standard MIDI File writer

  • midi_read.py — SMF parser + per-track analyzer

  • transcribe.py — basic-pitch wrapper + content-addressed cache

  • wave_edit.py — zero-dependency WAV slicer/arranger (the audio counterpart to midi.py)

  • patches.py — factory patch index reader

  • logic_ctl.py — the minimal AppleScript/AX layer

  • demo_beat.py — 8-bar dark R&B demo (85 BPM, Am–F–C–E)

  • tests/ — unit + protocol tests

See CHANGELOG.md for the full arc.

Known limits

  • import_audio is best-effort and often aborts: Logic imports audio through a Browser pane, not a file dialog. It is hardened to NEVER type a path into the arrange window (a path's letters fire as key commands — r starts a recording — which once damaged a live session). Drag audio from Finder for reliability.

  • import_midi and import_audio both confirm a file panel is frontmost before typing a path, and abort with Escape otherwise — they never leak a path into the arrange window. import_midi's happy path is live-verified; import_audio usually aborts (audio import is a Browser pane).

  • Patch loading, track selection, and import drive Logic's UI; they're anchored to stable AX landmarks (bounded subtree scans, never the whole window — real projects have thousands of AX elements) but a future Logic redesign could move them. list_tracks exists so failures are visible, not silent.

  • Logic auto-renames a track to its patch name after load_patch unless the track was named manually first.

  • Track-header patch labels can sit in an off-screen column and read stale — header text is NOT a reliable patch readback (this once produced a false "loading is broken" diagnosis). The selected track's channel-strip name in the inspector is ground truth; load_patch verifies against it and returns VERIFIED or FAILED accordingly.

  • Tested on Logic Pro X 10.x/11 ("Logic Pro X.app" and "Logic Pro.app" are both handled), macOS 14+.

Related MCP Connectors

Related MCP Servers