API & MCP

Soundbox is a local server with a browser front end. Everything the app does goes through the HTTP API documented below, and an agent can drive the same pipeline over MCP. Every example here points at this instance, so a copied command works as-is.

http://127.0.0.1:17494

Quickstart

Generate a sound over HTTP in three calls. The pattern is submit, poll, fetch.

1 · Check the server and see what is loaded

curl http://127.0.0.1:17494/api/health

2 · Submit a generation

curl -X POST http://127.0.0.1:17494/api/generate \
  -H 'Content-Type: application/json' \
  -d '{"model":"audiocpp-stable-audio-3-sfx","prompt":"heavy wooden door slam, dry room","duration_s":3}'

Returns { "job_id": "…" } immediately with 202. The audio does not exist yet. If the model isn't loaded, the job loads it first. That is the loading phase, and it can take a while the first time.

3 · Follow the job, then fetch the sound

# poll — done, error or cancelled ends it
curl "http://127.0.0.1:17494/api/jobs/$JOB_ID"

# …or stream every change as server-sent events
curl -N -H 'Accept: text/event-stream' "http://127.0.0.1:17494/api/jobs/$JOB_ID/events"

# on "status":"done", result.sounds holds the new ids
curl "http://127.0.0.1:17494/api/sounds/$SOUND_ID"

The Sound you get back has a file field like /files/<id>.wav. Fetch that from this same origin to get the audio, or add ?format=mp3 to have it transcoded.

Describe the physical sound. “Scary sound” produces noise; “low metallic groan of a ship's hull under pressure, distant, reverberant” produces something usable. Short clips layered in an editor work better than one long generation.

Connect an MCP client

The MCP server runs inside the main Soundbox process at http://127.0.0.1:17494/mcp. There is no second daemon to start, but this app must be running for any of it to work.

Before connecting a client, install at least one model on the Models page. An agent calling soundbox.generate with nothing installed gets an error telling the user to go there.

  1. Leave Soundbox running. The MCP endpoint is served by this process, so a client can only reach it while the app is up.
  2. In a terminal, in any directory, run:
    claude mcp add --transport http soundbox http://127.0.0.1:17494/mcp
    Add --scope user to make it available in every project instead of only this one.
  3. Start or restart claude, then type /mcp. Soundbox should be listed as connected, with its 21 tools.
  4. Ask for a sound in plain language, e.g. “make me a heavy wooden door slam for my game”. Claude Code will ask to run soundbox.generate; approve it, and the reply comes back with a file path.

claude mcp list shows what is registered and whether it connects. claude mcp remove soundbox undoes step 2.

The tools

What an agent sees once connected: sounds first, then speech, then the script and timeline.

Sounds

Make a sound effect and find it again. Every sound-shaped result carries an absolute path to a WAV on this machine, so the agent can use its ordinary file tools from there.

soundbox.generateblocks

Renders one or more sounds from a text description and saves them to the library. Blocks until the audio is on disk.

Arguments

prompt
string
required
Material, action and space, e.g. “glass bottle shattering on tile, small reverberant room”. For a lyrics-conditioned model this sets genre and instrumentation instead.
duration_s
number
0.5–300, default 5. Clamped to the model's own maximum.
model
string
Defaults to whichever model is already loaded.
negative_prompt
string
Ignored by models that lack it.
seed
integer
For reproducibility.
variations
integer
1–4 takes of the same idea, default 1.
tags
string[]
Library tags.
lyrics
string
Words for a lyrics-conditioned music model to sing. Ignored elsewhere; check supports.lyrics first.
source_sound_id
string
Edit an existing sound instead of rendering from scratch. Pair with edit.
edit
"extend" | "cover" | "repaint"
What to do with source_sound_id. The model must declare supports.<edit>.
soundbox.enhance_prompt

Turns a vague prompt (“scary sound”) into a concrete one, optionally several. Requires the local prompt assistant to be enabled; if it is off, the tool says so.

Arguments

prompt
string
required
The prompt to rewrite.
n
integer
1–5 distinct variations, default 1.
category
"sfx" | "music"
Which kind of model this is for. Defaults to sfx.
lyrics
boolean
True if the prompt sets genre and instrumentation for a lyrics-conditioned model.
model
string
The id you intend to generate with, so the rewrite follows that model's own prompting conventions.
soundbox.list_models

Available models with status and licence. Also lists each speech model's delivery options. A misspelled option is ignored silently, with no error.

No arguments.

soundbox.list_sounds

Search what has already been made. Call it before generating; the sound may already exist.

Arguments

q
string
Text to match in prompt or name.
tag
string
limit
integer
1–100, default 20.
soundbox.get_sound

Fetch a single sound by id, including its absolute file path.

Arguments

sound_id
string
required
soundbox.separateblocks

Splits a sound into vocals, drums, bass and other, each saved as its own sound with its own path. You can keep some and merge the rest back into one sound: the original minus the stems you kept.

Arguments

sound_id
string
required
model
string
Separation model id. Omit for whichever `task: "sep"` model is installed.
stems
string[]
Which stems to keep, e.g. ["vocals"]. Omit for all.
rest
"discard" | "merge"
What happens to the stems not named: thrown away, or summed into one "rest" sound.
soundbox.delete_sounddeletes

Permanently deletes a sound and its audio file.

Arguments

sound_id
string
required

Voices

Spoken words take a separate path: soundbox.generate will not say them. speak is the one-off read. Put anything you want to keep and rework in a story.

soundbox.list_voices

The voices a script can be read in. Call it before writing one, so lines can be cast to real voice ids.

No arguments.

soundbox.speakblocks

Reads text aloud in a saved voice and saves it to the library. Pass one line, or several with different voices. Blocks until the audio is on disk.

Arguments

lines
{ text, voice_id?, speaker? }[]
required
1–200. Several lines with different voice_ids is a dialogue.
model
string
A text-to-speech model id. Defaults to a ready one.
gap_s
number
Silence between lines, 0–5. Default 0.4.
name
string
What the resulting sound is called in the library.
delivery
Record<string, string>
How to read it, by this model's own option names. Never write stage directions into the text; they get read aloud.

Stories

A story is a script that persists. Each line has a position in time, can be re-read on its own, and persists after the conversation that made it.

soundbox.create_story

Creates the empty script everything else in this group operates on.

Arguments

title
string
What this story is.
delivery
Record<string, string>
How every line is read unless it overrides it. Set delivery here in most cases.
soundbox.list_stories

Existing stories, most recently worked on first.

Arguments

limit
integer
1–200, default 20.
soundbox.get_story

Returns the whole script line by line: text, voice, timing, and whether each line has been voiced yet. Use it to inspect what the agent built.

Arguments

story_id
string
required
soundbox.add_lines

Appends spoken lines, pauses, or existing library sounds to the script. Add many at once; give a line its own delivery if it should be read differently.

Arguments

story_id
string
required
lines
{ text?, speaker?, voice_id?, kind?, duration_s?, sound_id?, track_index?, start_s?, delivery? }[]
required
1–200. kind “pause” is a beat of silence lasting duration_s; sound_id drops a library sound onto the timeline instead of writing words.
soundbox.update_line

Changes a line's words, voice, delivery, or where it sits in time. Changing words or delivery means the line has to be re-read before it is heard.

Arguments

line_id
string
required
text
string
speaker
string | null
voice_id
string | null
track_index
integer
−16 to 16.
start_s
number
Where it sits in time.
muted
boolean
gain_db
number
−40 to 24.
duration_s
number
How much of the clip plays. Shortens the clip without deleting its audio.
trim_start_s
number
How far into the source sound the clip begins.
delivery
Record<string, string>
Overrides the story's. {} puts the line back on the story's.
soundbox.speak_storyblocks

Reads every line that has no audio yet, in its assigned voice, one at a time. This is the slowest tool.

Arguments

story_id
string
required
model
string
A text-to-speech model id. Defaults to a ready one.
regenerate
boolean
Also re-read lines that already have audio. Default false.
soundbox.delete_linedeletes

Removes one line from a story. Its audio stays in the library.

Arguments

line_id
string
required
soundbox.split_line

Cuts a voiced line at a point in its own audio, giving two clips. The words stay with the first half, because Soundbox cannot tell which words fell on either side of the cut.

Arguments

line_id
string
required
at_s
number
required
Seconds into the line's own audio. Must be > 0.
soundbox.export_storyblocks

Mixes the whole arrangement to one sound, honouring where each clip sits, its trim, its gain, and whether it is muted.

Arguments

story_id
string
required

Arrangement

The timeline underneath a story: which lane a clip is on, how loud it is, and what the studio shows when you open it.

soundbox.open_in_studio

Lays a library sound out on a timeline, split into stems on separate tracks where that is possible. If the sound already has stems, it reuses them.

Arguments

sound_id
string
required
separate
boolean
Split into stems first, default true. Ignored for a sound that is already a stem or an edit.
stems
string[]
Which stems get a track, e.g. ["vocals", "drums"]. Omit for all.
rest
"discard" | "merge"
What happens to the stems not named: thrown away, or summed into one "rest" track. Default discard.
soundbox.mix_track

Controls one lane of a story. These settings belong to the lane; the clips on it keep their own.

Arguments

story_id
string
required
track_index
integer
required
Which lane, −16 to 16.
name
string | null
muted
boolean
soloed
boolean
With any track soloed, only soloed tracks are heard.
gain_db
number
−40 to 24.
soundbox.remove_trackdeletes

Deletes a lane and the clips on it; lanes above slide down. Call soundbox.get_story first if you are not certain what is on it.

Arguments

story_id
string
required
track_index
integer
required
−16 to 16.

What a sound looks like coming back

{
  "id": "987a0eae-6c0e-45e8-b777-81cfe4d15af3",
  "name": "Door slam 01",
  "path": "C:\\Users\\you\\.soundbox\\sounds\\987a0eae-….wav",
  "url": "/files/987a0eae-6c0e-45e8-b777-81cfe4d15af3.wav",
  "model": "stable-audio-open-small",
  "prompt": "heavy wooden door slam, dry room",
  "duration_s": 3.0,
  "sample_rate": 44100,
  "channels": 2,
  "seed": 752071476,
  "tags": ["door", "impact"]
}

Every sound-shaped result carries path: an absolute filesystem path to the WAV on this machine. The agent can hand it straight to ffmpeg, copy it into a game project's assets folder, or attach it, without knowing anything about the Soundbox server or its HTTP port.

Generation is serialised (one GPU, one job at a time), so five parallel soundbox.generate calls queue and run one after another. Use variations for several takes of one idea. If no model is installed, generate returns an actionable error naming the Models screen: surface it, do not retry.

A worked example

One full turn, from a sentence to a file sitting in a game project.

You

“Make me a door slam for my game: heavy wooden door, stone corridor. Put it in assets/audio/ as a 48k mono wav.”

Agent

Checks the library first, finds nothing close, and calls the generate tool. It writes a physical description of material, action and space instead of repeating your words back.

Tool call

soundbox.generate({
  "prompt": "heavy wooden door slamming shut in a stone corridor, hard impact, long tail",
  "duration_s": 3,
  "variations": 2,
  "tags": ["door", "impact", "game"]
})

Tool result

{
  "sounds": [
    {
      "id": "987a0eae-6c0e-45e8-b777-81cfe4d15af3",
      "name": "Door slam 01",
      "path": "C:\\Users\\you\\.soundbox\\sounds\\987a0eae-….wav",
      "url": "/files/987a0eae-6c0e-45e8-b777-81cfe4d15af3.wav",
      "duration_s": 3.0,
      "sample_rate": 44100,
      "channels": 2,
      "seed": 752071476
    },
    { "id": "b1f4…", "name": "Door slam 02", "path": "C:\\Users\\you\\.soundbox\\sounds\\b1f4….wav" }
  ],
  "model": "stable-audio-open-small",
  "elapsed_s": 4.71
}

Agent

The path is absolute, so the next step is ordinary file work, with no HTTP and no knowledge of the Soundbox port. It converts to the format the project wants and drops it in:

ffmpeg -i "C:/Users/you/.soundbox/sounds/987a0eae-….wav" \
  -ac 1 -ar 48000 assets/audio/door_slam.wav

Then it plays both takes back to you by path, or keeps the one whose seed it can reuse later for a matching variant. The sounds are also in the Library here, so you can audition them without leaving the conversation.

The agent needed no Soundbox-specific code: it called a tool, got JSON with a path, and used its normal file tools. A URL relative to the Soundbox server would have made this turn three steps longer.

Debugging your MCP connection

Roughly in order of how often each one is the problem.
  1. Check the server is up. The MCP endpoint lives inside this app's process, so if Soundbox is closed there is nothing to connect to.
    curl http://127.0.0.1:17494/api/health
  2. Check /mcp is reachable. A bare GET is not a valid MCP request, but the status code tells you what you need: 405 or 400 means the endpoint is there and speaking MCP, 404 means it is not mounted, and connection-refused means the port is wrong.
    curl -i http://127.0.0.1:17494/mcp
  3. Inspect it directly. MCP Inspector connects to a server without any client in the way, lists its tools and lets you invoke one with arguments you choose. That tells “the server is broken” apart from “my client's config is wrong” in about a minute.
    npx @modelcontextprotocol/inspector

    In the Inspector, pick transport Streamable HTTP and URL http://127.0.0.1:17494/mcp, then Connect → List Tools. You should see all 21 tools; run soundbox.list_models as the cheapest end-to-end check, since it needs no GPU.

  4. 421 Misdirected Request. The MCP SDK arms DNS-rebinding protection with a localhost-only host allowlist. That is right for a local app, but it rejects requests arriving under any other hostname. That happens if you bound the server to 0.0.0.0 and connect by LAN address or container name. Set SOUNDBOX_ALLOWED_HOSTS to a comma-separated list of the hosts you use.
  5. Check the URL has no trailing slash and that it ends in /mcp, not /api/mcp. Clients that do not follow 307 redirects on POST fail in a confusing way otherwise.
  6. stdio bridge. The bridge proxies to a running server, so both must be up. If the server is not on the default port, set SOUNDBOX_URL in the client's env block (http://127.0.0.1:17494 for this instance).
  7. Read the server log. It records every connection and job. The Activity page in this app shows the tail of it, and GET /api/logs?lines=200 returns the same thing as JSON.

Every endpoint

The quickstart above is the core loop. The API reference indexes the rest of the HTTP surface route by route: sounds, voices, stories, sessions, models, settings and the MCP log.

Read the full API reference for every route with its method, purpose and key fields. It is kept in the repository as docs/API.md beside the code it describes. Mutating requests need a JSON body or an X-Soundbox header; see the reference for why.