Skip to main content
Glama
AJBogo9

supercollider-mcp

by AJBogo9

supercollider-mcp

A Claude Code plugin that connects Claude to SuperCollider, enabling AI-driven music composition with both real-time playback and non-realtime (NRT) audio rendering at 50-150x realtime speed.

It bundles three things: the MCP server that drives SuperCollider, a music-craft skill that auto-loads composition and mixing guidance, and a guided /setup flow.

Why SuperCollider?

SuperCollider is a platform for audio synthesis and algorithmic composition. Its synthesis server (scsynth) runs independently of any frontend and accepts OSC messages, making it straightforward to drive programmatically.

Two features make it well-suited for AI-assisted composition:

  • Real-time synthesis: send code, hear results immediately, iterate

  • Non-realtime (NRT) rendering: render an audio score to a file at 50-150x realtime speed (a 5-minute piece renders in 2-4 seconds, no waiting)

Because the model can't hear, the server also exposes an analysis loop (sc_analyze, sc_compare) that turns a render into numbers, closing the quality side of render -> analyze -> adjust.

Related MCP server: sonic-mcp

Requirements

  • Linux (tested on Ubuntu 24.04)

  • supercollider-server package (scsynth 3.13+)

  • PipeWire with JACK compatibility (pw-jack)

  • Python 3.11+

/setup checks for these and guides you through anything missing. The pw-scsynth wrapper that routes scsynth through PipeWire is bundled (bin/pw-scsynth) and put on PATH automatically, so you do not create it by hand.

Install (Claude Code plugin)

  1. Install this repo as a plugin in Claude Code (via the /plugin menu or a local marketplace pointing at it).

  2. Run /setup. It detects scsynth, pw-jack, and Python; guides any missing installs (it never runs sudo silently); builds the Python venv at ~/.local/share/sc-mcp/venv; and verifies with a real render.

  3. Reload the Claude Code session so the supercollider MCP server connects.

.mcp.json and the venv location are handled for you and should not be hand-edited: the committed .mcp.json launches ${CLAUDE_PLUGIN_ROOT}/bin/sc-mcp-server, which locates the venv and runs the server from the plugin root.

The music-craft skill loads automatically whenever you compose, arrange, mix, or render, so quality guidance applies without being asked for.

Standalone (no plugin): you can also run the server directly. Create a venv with the dependencies (pip install .) and either point your MCP client's command at <venv>/bin/python -m sc_mcp.server, or run the bundled launcher with SC_MCP_VENV=<venv> ./bin/sc-mcp-server. scsynth must be able to reach pw-scsynth on PATH (the launcher adds bin/ for you).

Tools

Tool

Description

sc_boot

Boot scsynth (auto-called by sc_play)

sc_ping

Check server status

sc_quit

Shut down scsynth

sc_play(code)

Run Python/supriya code on the live server

sc_stop

Stop all sounds, signal song threads to exit

sc_log

Read scsynth output and exec errors

sc_render(code, duration, output_path, seed, genre)

NRT render to file (24-bit AIFF, even with a .wav name); returns a one-line analysis summary. seed = reproducible render; genre = analysis profile

sc_analyze(path, time_series, genre)

Analyze a render: loudness, dynamics, spectrum, stereo, key (genre picks the target profile)

sc_prepare_reference(path, output_path, start, duration)

Decode/trim/resample/normalize a source clip into a render-comparable reference WAV

sc_compare(reference, candidate)

Perceptual similarity [0-1] of a render vs a reference (mel/timbre, chroma/harmony, RMS-envelope)

save_song / load_song / list_songs

Song library

save_pattern / load_pattern / list_patterns

Pattern snippets

Library files (songs, patterns, renders, references) default to ~/.local/share/sc-mcp/. Override any location with the SC_SONGS_DIR, SC_PATTERNS_DIR, SC_RENDERS_DIR, or SC_REFERENCES_DIR environment variables.

SynthDef library

Eight built-in voice SynthDefs, available in every sc_play and sc_render call. All of them self-terminate, and none take a gate:

  • ambient_pad -- warm slow-attack pad, good for chords

  • bass_drone -- dark low-pass sawtooth sub-bass

  • pluck_tone -- Karplus-Strong plucked string

  • noise_wind -- band-pass filtered noise (wind, breath)

  • choir_wash -- formant-filtered pink noise (choir approximation)

  • sample_one_shot -- one-shot buffer player for audio files (live playback only)

  • kick_drum -- sine kick with an exponential pitch drop from punch to tone

  • hihat -- high-passed white-noise hat (open_hat=1 for an open tail)

Each also takes an out bus (default MIX_BUS). Two FX defs back the mastering chain: master_bus (auto-added; limiter to hardware) and reverb_send (global reverb return); use the master() / reverb() context helpers rather than adding them by hand.

33 SynthDefs total (a sc_mcp/stdlib/ package, one module per family). Beyond the originals: bass (sub_bass, reese_bass, tuned_808), percussion (snare_drum, clap, tom, open_hihat, crash, ride, and the cinematic timpani/taiko), strings/brass (string_ensemble, brass_braam), leads (fm_lead, fm_bell, saw_lead, acid_lead, stab), and transition FX (riser, downlifter, noise_impact, boom). A sequence()/euclid()/beats() clock and a tempo-synced sidechain() pump round out the rhythm tools. This makes hybrid cinematic / trailer music buildable end-to-end (analyze with genre="cinematic").

See the music-craft skill for how to shape each one and a full cinematic recipe.

Sampling

load_sample(path) now works in both live and NRT, so the fast render→analyze loop can use real recorded audio. Helpers: sample(path, at, note=…) (load + pitch + play), multisample(mapping, note) (keyboard-mapped instrument with round-robin + velocity layers), chop(path, slices, order=…) (break chopping), and slicer (play a buffer region). A tiny synthesized CC0 starter kit is bundled under samples/starter/ (drum one-shots + an fm_bell mallet multisample) and resolvable with sample_path("starter/drums/kick.wav"); regenerate it with scripts/build_starter_kit.py. For realistic orchestra/piano, fetch real CC0 libraries with scripts/fetch_samples.sh and canonicalize clips with prepare_sample().

NRT example

sc_render("""
# Ambient chord progression. Each pad self-terminates after attack+sustain+release.
chords = [[293, 369, 440], [247, 311, 392], [261, 329, 415]]
for i, freqs in enumerate(chords):
    for freq in freqs:
        with score.at(i * 6.0):
            score.add_synth(ambient_pad, freq=freq, amp=0.25,
                            attack=2.0, sustain=3.0, release=2.0)
with score.at(duration):
    score.do_nothing()
""", duration=20.0, output_path="/tmp/ambient.wav")

Renders 20 seconds of audio in under 0.1 seconds.

File format: sc_render always writes 24-bit AIFF, even when output_path ends in .wav. The plugin's own tools (sc_analyze, sc_compare) and libsndfile/soundfile read it transparently by content, but a strict WAV-only player will reject it and decoding it as 16-bit PCM turns the audio into noise. Decode as AIFF, or name outputs .aiff to avoid surprise.

Real-time example

sc_play("""
import threading, random, time

BEAT = 60.0 / 90.0

def bass_loop():
    while not stop.is_set():
        with server.at():
            server.add_synth(bass_drone, freq=55, amp=0.4, attack=1.0, sustain=2.0, release=1.0)
        stop.wait(4 * BEAT)

def melody_loop():
    scale = [440, 494, 554, 587, 659]
    while not stop.is_set():
        with server.at():
            server.add_synth(pluck_tone, freq=random.choice(scale), amp=0.3, decay=2.0)
        stop.wait(random.uniform(0.5, 2.0))

for fn in [bass_loop, melody_loop]:
    threading.Thread(target=fn, daemon=True).start()
""")
# Stop with: sc_stop()

Audio analysis

The model can't hear the audio, so sc_analyze turns a render into measurable proxies it can reason about, closing the quality side of the loop (render -> analyze -> adjust). It reads the rendered file directly (no real-time playback or recording) and reports:

  • Loudness: integrated LUFS, loudness range, true peak (dBTP), clipping

  • Dynamics: dynamic range (peak - RMS)

  • Spectral balance: 6-band energy, spectral centroid, mud/harshness flags

  • Stereo: phase correlation, width, and a mono-bass check

  • Key: detected key/scale, to compare against what was intended

sc_render("...", duration=180.0, output_path="/tmp/track.wav")
# -> Rendered 180.0s ... Analysis: -11.9 LUFS, peak -3.0 dBTP, DR 11.3 dB, ...

sc_analyze("/tmp/track.wav")
# -> full report with per-band levels, stereo metrics, detected key, and notes

sc_render folds a one-line summary into its output automatically; call sc_analyze for the full report.

To match a target track, prepare it once with sc_prepare_reference, then call sc_compare(reference, candidate) after each render to score how close you are, extending the loop to render -> analyze/compare -> adjust.

Pass genre= (e.g. sc_analyze(path, genre="cinematic")) to judge against a genre profile instead of the ambient defaults.

Mastering, reverb & reproducibility

Every voice routes through a private mix bus into an auto-added master (In -> HPF -> tilt EQ -> brickwall limiter at -1 dBTP -> hardware), so every render is peak-safe with no extra work and existing songs are unchanged apart from the safety limiter. For shared space, call reverb(room=0.85) once and route voices with out=REVERB_BUS; the reverb return is limited too. Pass seed= to sc_render for reproducible takes (pre-generated random values render identically; UGen noise needs a RandSeed.ir UGen).

Security note

sc_play and sc_render exec() the code you pass them by design --- this is an audio scripting tool, and arbitrary Python (and thus filesystem/network access) runs with the server's privileges. Only send code you trust, and run the server where you would run any other local dev tool. Uncaught exceptions inside a live song-loop thread are captured into sc_log rather than crashing silently.

Related MCP Connectors

Related MCP Servers