Sonicpi MCP
MCP server for Sonic Pi. Control live coding music sessions via LLMs using Streamable HTTP + OSC, implemented in Rust.
Ask AI about Sonicpi MCP
Powered by Claude · Grounded in docs
I know everything about Sonicpi MCP. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
Sonic Pi MCP Server
This is a Rust MCP server that sends OSC commands to a running Sonic Pi for live audible feedback.
✅ Sonic Pi is expected to be running normally (GUI open, audio on).
Tool calls modify music in real time (usually immediately or on the next live_loop iteration).
Quick demo
Setup:
- sonicpi-mcp running from this repository.
- MCP added to Codex CLI.
- Sonic Pi 4.6 running in Windows.
- Codex CLI prompted to generate a jam, it automatically generated it and sent it to be played on Sonic Pi.
- Recorded the loop, saved it as wav and converted to mp3 with Audacity.
Another one, this time prompted with Jetbrains AI.
![
] -> assets\jetbrains-acid.mp3
Prerequisites
1) Install & run Sonic Pi (Windows)
- Install Sonic Pi for Windows
- Launch Sonic Pi manually
- Verify audio works by pressing Run on a simple buffer, e.g.:
live_loop :beat do
sample :bd_haus
sleep 0.5
end
Leave Sonic Pi running while you use this MCP server.
Install / Releases
You can run from source or from GitHub Release artifacts.
From source:
cargo run
From Releases:
- Download the asset that matches your platform:
sonicpi-mcp-x86_64-pc-windows-msvc.zipsonicpi-mcp-x86_64-unknown-linux-gnu.tar.gzsonicpi-mcp-x86_64-apple-darwin.tar.gz
- Extract it and run the binary:
- Windows:
sonicpi-mcp.exe - macOS/Linux:
./sonicpi-mcp
- Windows:
Run the MCP server (Windows)
From the repo root:
cargo run
Endpoint:
http://127.0.0.1:8787/mcp
Auto-discovery (default)
By default the server reads:
%USERPROFILE%\.sonic-pi\log\gui.log
and discovers:
- Spider port from:
Setting up OSC sender to Spider on port #### - Token from: the last
daemon_stdout: #########line where the number is > 65535
Configuration overrides (PowerShell)
You can override discovery with environment variables:
# Where Sonic Pi is running
$env:SONICPI_HOST = "127.0.0.1"
# Optional: point directly at gui.log
$env:SONICPI_GUI_LOG = "C:\Users\<home>\.sonic-pi\log\gui.log"
# Optional: hard override values (debugging)
$env:SONICPI_SERVER_PORT = "37560"
$env:SONICPI_TOKEN = "814946186"
# Required to enable sonicpi_send_osc (default deny)
$env:SONICPI_OSC_ALLOWLIST = "/cue/*"
If SONICPI_SERVER_PORT and SONICPI_TOKEN are set, the server will not read gui.log.
Quick manual tests (Windows PowerShell)
$uri = "http://127.0.0.1:8787/mcp"
Initialize
$body = @{
jsonrpc = "2.0"
id = 1
method = "initialize"
params = @{}
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Method Post -Uri $uri -ContentType "application/json" -Body $body |
ConvertTo-Json -Depth 20
List tools
$body = @{
jsonrpc = "2.0"
id = 2
method = "tools/list"
params = @{}
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Method Post -Uri $uri -ContentType "application/json" -Body $body |
ConvertTo-Json -Depth 20
Run code in Sonic Pi (live)
$code = @"
live_loop :beat do
sample :bd_haus
sleep 0.5
end
"@
$body = @{
jsonrpc = "2.0"
id = 3
method = "tools/call"
params = @{
name = "sonicpi_run_code"
arguments = @{
code = $code
}
}
} | ConvertTo-Json -Depth 20
Invoke-RestMethod -Method Post -Uri $uri -ContentType "application/json" -Body $body |
ConvertTo-Json -Depth 20
Stop all jobs
$body = @{
jsonrpc = "2.0"
id = 4
method = "tools/call"
params = @{
name = "sonicpi_stop_all"
arguments = @{}
}
} | ConvertTo-Json -Depth 10
Invoke-RestMethod -Method Post -Uri $uri -ContentType "application/json" -Body $body |
ConvertTo-Json -Depth 20
Implemented OSC commands
/run-codewith args[token, code](sent to discovered Spider port)/stop-all-jobsis sent both tokened[token]and tokenless[](best-effort for compatibility)sonicpi_send_oscis default-deny and only allows OSC paths listed inSONICPI_OSC_ALLOWLISTsonicpi_cuesends/cue/<name>and works well withSONICPI_OSC_ALLOWLIST="/cue/*"sonicpi_setsends/set/<key>and works well withSONICPI_OSC_ALLOWLIST="/set/*"sonicpi_run_code_statusreports the current-program guardrail statesonicpi_metricsreports lightweight in-memory counters
Note: tool names use underscores for client compatibility (e.g. JetBrains AI). Dotted aliases like sonicpi.run_code are still accepted.
OSC allowlist (default deny)
sonicpi_send_osc is disabled unless you set SONICPI_OSC_ALLOWLIST.
Format:
- Comma-separated OSC paths.
- Wildcards are supported as a suffix
*and act as a prefix match.
Examples (PowerShell):
# Allow all cues (recommended starting point)
$env:SONICPI_OSC_ALLOWLIST = "/cue/*"
# Allow all sets
$env:SONICPI_OSC_ALLOWLIST = "/set/*"
# Allow both cues and sets
$env:SONICPI_OSC_ALLOWLIST = "/cue/*,/set/*"
# Allow specific addresses only
$env:SONICPI_OSC_ALLOWLIST = "/cue/tempo,/cue/section,/set/bpm"
Sonic Pi live-control template
A cue/set-friendly starter buffer is available at:
examples/live_control_template.rbexamples/cue_transition_template.rb(section changes viasonicpi_cue)
Suggested allowlist for the template:
$env:SONICPI_OSC_ALLOWLIST = "/cue/*,/set/*"
Example live controls:
# Change tempo without rewriting the whole buffer
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8787/mcp -ContentType "application/json" -Body (@{
jsonrpc = "2.0"
id = 100
method = "tools/call"
params = @{
name = "sonicpi_set"
arguments = @{ key = "tempo"; value = 140 }
}
} | ConvertTo-Json -Depth 10)
# Trigger a cue that loops can sync to
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8787/mcp -ContentType "application/json" -Body (@{
jsonrpc = "2.0"
id = 101
method = "tools/call"
params = @{
name = "sonicpi_cue"
arguments = @{ name = "mode"; args = @(1) }
}
} | ConvertTo-Json -Depth 10)
Recommended live workflow
- Start Sonic Pi manually (GUI open, audio on).
- Run the template once using
sonicpi_run_code(fromexamples/live_control_template.rb). - Avoid full rewrites during playback.
- Evolve the music with small controls:
- Use
sonicpi_setfor state (e.g.,tempo,intensity,mode,root) - Use
sonicpi_cuefor synced events (e.g.,tempo,mode,section)
- Use
This keeps audio stable while still allowing expressive live changes.
Session model and run_code guardrail
This server targets a single Sonic Pi instance, but guardrails can be
scoped with an optional session_id (default: "global").
To reduce accidental loop duplication, sonicpi_run_code blocks rapid
duplicate submissions of identical code per session_id for a short window.
Configure the window (default: 1000ms):
$env:SONICPI_RUN_CODE_DEDUPE_MS = "1000"
# Optional: managed program mode (stop all jobs before run_code)
$env:SONICPI_MANAGED_PROGRAM = "1"
Override the guardrail per call by setting force = true:
{
"jsonrpc": "2.0",
"id": 200,
"method": "tools/call",
"params": {
"name": "sonicpi_run_code",
"arguments": {
"code": "live_loop :beat do\n sample :bd_haus\n sleep 0.5\nend",
"force": true,
"session_id": "jam-a",
"managed": true
}
}
}
Check current-program guardrail state (optionally per session):
{
"jsonrpc": "2.0",
"id": 201,
"method": "tools/call",
"params": {
"name": "sonicpi_run_code_status",
"arguments": { "session_id": "jam-a" }
}
}
Example MCP workflow (safe live evolution)
- Set allowlists:
SONICPI_OSC_ALLOWLIST="/cue/*,/set/*"- Optional SSE filter:
SONICPI_OSC_IN_ALLOWLIST="/cue/*,/set/*"
- Run the template once with
sonicpi_run_codefromexamples/live_control_template.rb. - During playback, prefer:
sonicpi_setfor state changes (tempo, intensity, mode, root)sonicpi_cuefor synced transitions/events
- Use
sonicpi_stop_allto recover quickly from runaway loops. - Use
sonicpi_run_code_statusif you suspect duplicaterun_codecalls are being blocked.
OSC listener → SSE (feedback loop)
The server can listen for inbound OSC (e.g., Sonic Pi cues) and forward them to SSE clients.
Enable it explicitly (disabled by default to avoid port conflicts in tests):
$env:SONICPI_ENABLE_LISTENER = "1"
$env:SONICPI_LISTEN_HOST = "0.0.0.0"
$env:SONICPI_LISTEN_PORT = "4559"
# Optional: inbound OSC filter for SSE (default allow)
$env:SONICPI_OSC_IN_ALLOWLIST = "/cue/*,/set/*"
# Optional: tag inbound OSC events with a session_id (default: global)
$env:SONICPI_OSC_IN_SESSION_ID = "global"
# Optional: inbound OSC SSE spam protection
$env:SONICPI_OSC_IN_MIN_INTERVAL_MS = "25"
$env:SONICPI_OSC_IN_DEDUPE_MS = "100"
When enabled, SSE clients may receive events like:
{
"type": "osc.in",
"ts_ms": 1769530000000,
"from": "127.0.0.1:4559",
"address": "/cue/tempo",
"args": [140]
}
SSE event types currently include:
osc.in: a single OSC messageosc.bundle.in: an OSC bundle flattened intoeventstool.call: structured tool lifecycle events (startandok)
tool.call schema (example):
{
"type": "tool.call",
"tool": "sonicpi_run_code",
"stage": "start",
"request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"detail": {
"spider_port": 37560,
"code_hash": 1234567890,
"force": false
}
}
Simple curl-based SSE client:
curl -N -H "Accept: text/event-stream" http://127.0.0.1:8787/mcp
Per-session SSE filtering (matches session_id, default is global):
curl -N -H "Accept: text/event-stream" "http://127.0.0.1:8787/mcp?session_id=jam-a"
SSE contract reference:
docs/SSE-CONTRACT.mddocs/MCP-WORKFLOWS.md
Metrics
You can retrieve lightweight in-memory counters via sonicpi_metrics.
Example request:
{
"jsonrpc": "2.0",
"id": 300,
"method": "tools/call",
"params": {
"name": "sonicpi_metrics",
"arguments": {}
}
}
The response includes counters for:
- inbound OSC receive/publish/drop paths
- SSE session filtering
- tool call lifecycle counts
Troubleshooting
- If discovery fails, call
sonicpi_healthto see the error message. - If
sonicpi_run_codedoes nothing, check thatgui.logcontains the Spider port line and a token value. You can also temporarily setSONICPI_SERVER_PORTandSONICPI_TOKENas overrides.

-->