This repository provides three MCP servers that compile, render, or play Faust DSP code:
faust_server.py: C++ compile pipeline (Faust CLI + g++).faust_server_daw.py: DawDreamer offline render pipeline.faust_realtime_server.py: real-time playback via node-web-audio-api + Faust WASM.
faust_server.py: MCP server entrypoint (FastMCP) and tool implementation.faust_server_daw.py: DawDreamer-based MCP server (no C++ compile step).faust_realtime_server.py: Real-time MCP server using node-web-audio-api + Faust WASM.faust_realtime_worker.mjs: Node worker that hosts the real-time DSP graph.analysis_arch.cpp: Faust C++ architecture used to generate analysis data.t1.dsp,t2.dsp,noise.dsp: Example Faust DSP programs.sse_client_example.py: SSE client example.stdio_client_example.py: stdio client example.smoke_test.py: Basic stdio smoke test for both offline servers.Makefile: Common run/test targets.requirements.txt: Client-side Python dependencies.
The project has three MCP server variants that share a common client interface, but differ in how they compile/render Faust DSP code.
flowchart LR
LLM[LLM / MCP Client] -->|SSE or stdio| MCP[MCP Server]
subgraph Server3["S3:faust_realtime_server.py"]
S3["MCP tool calls"] --> PY[Python MCP server]
PY -->|stdin/stdout JSON| NODE[faust_realtime_worker.mjs]
NODE --> NWA[node-web-audio-api + faustwasm]
NODE --> UI["Optional UI server: faust UI or fallback"]
end
subgraph Server2["S2:faust_server_daw.py"]
S2["MCP tool call"] --> DD["DawDreamer + Faust DSP"]
DD --> JSON2[Analysis JSON + features]
end
subgraph Server1["S1:faust_server.py"]
S1["MCP tool call"] --> CLI["faust CLI + analysis_arch.cpp"]
CLI --> BIN[Native C++ binary]
BIN --> JSON1[Analysis JSON]
end
Notes:
- SSE is the recommended transport for web clients; stdio is useful for local CLI tools.
- The real-time server returns parameter metadata and current values, not offline analysis.
- Real-time tools:
compile_and_start,check_syntax,get_params,set_param,get_param,get_param_values,stop. - Offline tools:
compile_and_analyze. - DawDreamer and real-time servers accept optional
input_source(none,sine,noise,file),input_freq(Hz), andinput_file(path) to inject test inputs.
make setup
make smoke-test DSP=t1.dspReal-time setup:
make setup-rtFaust UI setup (optional):
make setup-uiCleanup:
make cleanMCP_HOST(default:127.0.0.1)MCP_PORT(default:8000)MCP_TRANSPORT(default:sse)- Supported values:
sse,streamable-http,stdio
- Supported values:
MCP_MOUNT_PATH(optional, SSE only)TMPDIR(recommended) temp folder used by the compiler toolchain
- Accept a Faust DSP string via the
compile_and_analyzetool. - Write it to a temporary
process.dspfile. - Compile Faust DSP to C++ using
analysis_arch.cpp. - Compile the generated C++ into a native binary (C++11+).
- Run the binary to produce JSON analysis output.
- Return the JSON result to the MCP client.
- Python 3.10+
- Faust CLI available in PATH (
faust) - C++ compiler (
g++) with C++11+ support - Python package
mcp
MCP_TRANSPORT=sse MCP_HOST=127.0.0.1 MCP_PORT=8000 \
TMPDIR=/path/to/tmp \
python3 faust_server.pyDefault SSE endpoint:
http://127.0.0.1:8000/sse
MCP_TRANSPORT=stdio python3 faust_server.pyFor faust_server.py, set TMPDIR to a writable path if compilation fails:
MCP_TRANSPORT=stdio TMPDIR=/tmp/faust-mcp-test python3 faust_server.pyInput:
faust_code(string) - the DSP source code
Output:
JSON string with:
statusmax_amplitudermsis_silentwaveform_asciinum_outputschannels(array of per-output metrics)
The analysis is performed by analysis_arch.cpp and returns a JSON payload with
these fields:
status: hard-coded to"success"when the binary completes.max_amplitude: maximum absolute value of the mono mix over the full render. The mono mix is the average of all output channels per sample.rms: root-mean-square of the mono mix over the full render.is_silent:truewhenmax_amplitude < 0.0001, otherwisefalse.waveform_ascii: a 60-character ASCII summary of the mono mix. Each character represents a chunk of the rendered buffer and is chosen by peak magnitude:_for near-silence (< 0.01),#for > 0.5,=for > 0.2, and-otherwise.num_outputs: number of output channels produced by the DSP.channels: array of per-output objects with:index(0-based output index)max_amplitudermsis_silentwaveform_ascii
Render details:
- Sample rate: 44100 Hz
- Duration: 2 seconds (88200 samples)
- Processing block size: 256 frames
This variant uses DawDreamer to compile
and render Faust DSP directly in Python, so you do not need to generate and compile C++ code.
It renders offline audio and returns the same analysis metrics plus a dawdreamer info block and
DawDreamer-only features.
- Python 3.10+
dawDreamer(import name can bedawDreamerordawdreamer)numpyfor spectral features (otherwisespectral_availableisfalse)
Install:
python3 -m pip install dawDreamerDD_SAMPLE_RATE,DD_BLOCK_SIZE,DD_RENDER_SECONDSfor renderingDD_FFT_SIZE,DD_FFT_HOP,DD_ROLLOFFfor spectral analysis
compile_and_analyze accepts optional input_source (none, sine, noise, file),
input_freq (Hz for sine, default 1000), and input_file (path for file) to inject
test inputs for effects. For DawDreamer, input_file must be a local WAV path
and requires numpy for decoding.
MCP_TRANSPORT=sse MCP_HOST=127.0.0.1 MCP_PORT=8000 \
DD_SAMPLE_RATE=44100 DD_BLOCK_SIZE=256 DD_RENDER_SECONDS=2.0 \
python3 faust_server_daw.pyMakefile targets:
make run-daw
make client-daw DSP=t1.dspmake client-daw DSP=... runs the SSE client against the DawDreamer server using
that DSP file. You can also use:
make client-sse DSP=t1.dspcompile_and_analyze with a test input source (DawDreamer):
python3 sse_client_example.py --url http://127.0.0.1:8000/sse \
--tool compile_and_analyze --dsp t1.dsp --input-source noisefeatures(global time + spectral features)- Per-channel
features dawdreamerobject with render settings and version info
Example output (truncated):
{
"status": "success",
"max_amplitude": 0.990577,
"rms": 0.49998,
"is_silent": false,
"waveform_ascii": "############################################################",
"num_outputs": 2,
"features": {
"dc_offset": 0.0001,
"zero_crossing_rate": 0.022,
"crest_factor": 1.98,
"clipping_ratio": 0.0,
"spectral_centroid": 1000.0,
"spectral_bandwidth": 120.0,
"spectral_rolloff": 1500.0,
"spectral_flatness": 0.12,
"spectral_flux": 0.04,
"spectral_frame_size": 2048,
"spectral_hop_size": 1024,
"spectral_rolloff_ratio": 0.85,
"spectral_available": true
},
"channels": [
{
"index": 0,
"max_amplitude": 1.0,
"rms": 0.707111,
"is_silent": false,
"waveform_ascii": "############################################################",
"features": {
"dc_offset": 0.0001,
"zero_crossing_rate": 0.022,
"crest_factor": 1.98,
"clipping_ratio": 0.0,
"spectral_centroid": 1000.0,
"spectral_bandwidth": 120.0,
"spectral_rolloff": 1500.0,
"spectral_flatness": 0.12,
"spectral_flux": 0.04,
"spectral_frame_size": 2048,
"spectral_hop_size": 1024,
"spectral_rolloff_ratio": 0.85,
"spectral_available": true
}
}
],
"dawdreamer": {
"version": "0.7.0",
"sample_rate": 44100,
"block_size": 256,
"render_seconds": 2.0,
"num_channels": 2
}
}This variant compiles Faust DSP code to WebAudio on the fly and plays it in real time
using the node-web-audio-api runtime. It returns parameter metadata extracted from
the Faust JSON so an LLM can control the running DSP (no offline analysis metrics).
node-web-audio-api is an open-source Node.js implementation of the Web Audio API that provides AudioContext/AudioWorklet support outside the browser, backed by native audio I/O.
- Node.js
node-web-audio-apicheckout atWEBAUDIO_ROOT(submodule:external/node-web-audio-api)@grame/faustwasminstalled in that checkout- Optional:
@shren/faust-uiinstalled inui/for the UI bridge
Environment variables:
WEBAUDIO_ROOT: node-web-audio-api path (defaultexternal/node-web-audio-api)FAUST_UI_PORT: enable UI server on this port (optional)FAUST_UI_ROOT: path to a builtfaust-uibundle (optional, overrides auto-detect)
Submodule setup (one-time):
git submodule update --init --recursive
cd external/node-web-audio-api
npm install
npm run build- The real-time server runs one DSP at a time;
compile_and_startreplaces it. - Parameter paths come from Faust JSON, not
RT_NAME. Usemake rt-get-params. npm run buildgeneratesnode-web-audio-api.build-release.nodeand should be re-run if you update the submodule or switch branches.- If you get no sound, check OS audio permissions and the default output device.
WEBAUDIO_ROOT=external/node-web-audio-api \
MCP_TRANSPORT=sse MCP_HOST=127.0.0.1 MCP_PORT=8000 \
python3 faust_realtime_server.pySet FAUST_UI_PORT to start a small HTTP UI server (fallback sliders). If you
have the @shren/faust-ui package installed (in ui/), the server will auto-load
it. You can also point FAUST_UI_ROOT to a custom bundle directory so the page
can load /faust-ui/index.js and use it instead of the fallback UI.
WEBAUDIO_ROOT=external/node-web-audio-api \
FAUST_UI_PORT=8787 FAUST_UI_ROOT=/path/to/faust-ui/dist/esm \
MCP_TRANSPORT=sse MCP_HOST=127.0.0.1 MCP_PORT=8000 \
python3 faust_realtime_server.pyThen open:
http://127.0.0.1:8787/
compile_and_start(faust_code, name?, latency_hint?, input_source?, input_freq?, input_file?)check_syntax(faust_code, name?)get_params()get_param(path)get_param_values()set_param(path, value)stop()
latency_hint accepts interactive (default) or playback.
input_source accepts none (default), sine, noise, or file. input_freq
sets the sine frequency in Hz (default 1000). input_file sets the path for a
soundfile input when input_source=file.
For the real-time server (faustwasm), soundfiles must be served over HTTP/HTTPS. To test local files, start a simple server in the repo root:
python3 -m http.server 9000The real-time server runs a Node worker process and talks to it over stdin/stdout:
faust_realtime_server.pystarts the worker withnode faust_realtime_worker.mjsand passesWEBAUDIO_ROOTin the environment.- The worker reads JSON lines like:
{ "id": 1, "method": "compile_and_start", "params": {...} } - It responds with:
{ "id": 1, "result": {...} }or{ "id": 1, "error": "..." } - The Python server forwards MCP tool calls to the worker and returns the result.
t1.dsp:
import("stdfaust.lib");
cutoff = hslider("cutoff[Hz]", 1200, 50, 8000, 1);
drive = hslider("drive[dB]", 0, -24, 24, 0.1) : ba.db2linear;
process = _ * drive : fi.lowpass(2, cutoff);t2.dsp:
import("stdfaust.lib");
freq1 = hslider("freq1[Hz]", 500, 50, 2000, 1);
freq2 = hslider("freq2[Hz]", 600, 50, 2000, 1);
gain = hslider("gain[dB]", -6, -60, 6, 0.1) : ba.db2linear;
process = os.osc(freq1) * gain, os.osc(freq2) * gain;noise.dsp:
import("stdfaust.lib");
gain = hslider("gain[dB]", -6, -60, 6, 0.1) : ba.db2linear;
process = no.noise * gain;Example flow for Claude Code or another MCP-capable LLM:
- Start the desired server (
faust_server.py,faust_server_daw.py, orfaust_realtime_server.py). - The LLM connects over SSE or stdio and lists available tools.
- The LLM sends DSP code to
compile_and_analyze(offline servers) orcompile_and_start(real-time). - The server returns analysis metrics (offline) or parameter metadata (real-time).
- The LLM uses
set_paramto adjust controls, andget_param_valuesto read back state.
Minimal real-time loop (conceptual):
compile_and_start(faust_code="...", name="osc1")
get_param_values()
set_param(path="/freq", value=440)
Local file server for soundfile inputs:
python3 -m http.server 9000Offline analysis with a sine test input (DawDreamer):
make run-daw
make client-daw DSP=t1.dsp INPUT_SOURCE=sine INPUT_FREQ=1000Offline analysis with a soundfile test input (DawDreamer, local path):
make run-daw
make client-daw DSP=t1.dsp INPUT_SOURCE=file INPUT_FILE=tests/assets/sine.wavIf you see addSoundfile : soundfile for sound cannot be created, make sure the
path points to a local WAV file (HTTP URLs are for the real-time server).
Real-time compile with noise test input:
make run-rt
make rt-compile DSP=t1.dsp RT_NAME=fx INPUT_SOURCE=noiseReal-time compile with a soundfile test input (HTTP URL):
make run-rt-ui
make rt-compile DSP=t1.dsp RT_NAME=fx INPUT_SOURCE=file INPUT_FILE=http://127.0.0.1:9000/tests/assets/sine.wavpython3 sse_client_example.py --url http://127.0.0.1:8000/sse --dsp t1.dspcompile_and_start example:
python3 sse_client_example.py --url http://127.0.0.1:8000/sse \
--tool compile_and_start --dsp t1.dsp --name osc1 --latency interactiveWith a test input source:
python3 sse_client_example.py --url http://127.0.0.1:8000/sse \
--tool compile_and_start --dsp t1.dsp --name osc1 --latency interactive \
--input-source sine --input-freq 1000With a file test input:
python3 sse_client_example.py --url http://127.0.0.1:8000/sse \
--tool compile_and_start --dsp t1.dsp --name osc1 --latency interactive \
--input-source file --input-file http://127.0.0.1:9000/tests/assets/sine.wavtests/assets/sine.wav is a mono 1 kHz test file included in this repo.
python3 stdio_client_example.py --dsp t1.dspReal-time over stdio:
MCP_TRANSPORT=stdio python3 faust_realtime_server.pypython3 stdio_client_example.py --server faust_realtime_server.py \
--tool compile_and_start --dsp t1.dsp --name fx --latency interactive \
--input-source noiseThese commands exercise the common server/client combinations on a local machine:
# C++ server (stdio)
python3 stdio_client_example.py --server faust_server.py --dsp t1.dsp --tmpdir /tmp/faust-mcp-test
# DawDreamer server (stdio, file input)
python3 stdio_client_example.py --server faust_server_daw.py --dsp t1.dsp \
--input-source file --input-file tests/assets/sine.wav
# Real-time server (SSE)
WEBAUDIO_ROOT=external/node-web-audio-api MCP_PORT=8000 \
python3 faust_realtime_server.py
python3 sse_client_example.py --url http://127.0.0.1:8000/sse \
--tool compile_and_start --dsp t1.dsp --name fx --latency interactive \
--input-source noisemake run-rt
make run-rt-ui
make rt-compile DSP=t1.dsp RT_NAME=osc1
make rt-get-params
make rt-get-param RT_PARAM_PATH=/freq
make rt-set-param RT_PARAM_PATH=/freq RT_PARAM_VALUE=440
make rt-stop| Server | Transport | Client | Works |
|---|---|---|---|
faust_server.py |
SSE | sse_client_example.py / make client-sse |
Yes |
faust_server.py |
stdio | stdio_client_example.py / make client-stdio |
Yes |
faust_server_daw.py |
SSE | sse_client_example.py / make client-daw |
Yes |
faust_server_daw.py |
stdio | stdio_client_example.py |
Yes |
faust_realtime_server.py |
SSE | sse_client_example.py |
Yes |
faust_realtime_server.py |
stdio | stdio_client_example.py |
Yes |
Edit ~/.config/Claude/claude_desktop_config.json and add:
{
"mcpServers": {
"faust": {
"type": "sse",
"url": "http://127.0.0.1:8000/sse"
}
}
}If your MCP client reads a servers.json file, add a stdio server entry:
{
"servers": {
"faust": {
"command": "python3",
"args": ["/path/to/faust-mcp/faust_server.py"],
"env": {
"MCP_TRANSPORT": "stdio",
"TMPDIR": "/path/to/faust-mcp/tmp"
}
}
}
}- If the compiler cannot create temp files, set
TMPDIRto a writable location. - Ensure the
tmp/directory exists if you useTMPDIR=./tmp(create it once withmkdir -p tmp). - If the server cannot bind to
127.0.0.1:8000, either stop the process using that port or changeMCP_PORTto another value.