Connect Claude Code, Claude Desktop, Codex or any other Model Context Protocol client to Volta. It gets the compiler, the renderer and this reference as tools. With a Studio open, it can also get the song you are working on: read the buffer, play it, and make changes you can undo. If you cannot run the Studio binary, a hosted server offers the compiler and the reference without files or audio.
What it is#
The Studio binary has two MCP modes:
volta-studio --mcp— a server your agent launches. It speaks MCP over stdin/stdout and opens no window. It works on.voltafiles on disk.- A Studio with agent control on — the normal Studio, plus a local socket that agents may connect to. Turn it on in View ▸ Assist ▸ Allow Agent Control, or launch with
--agent-control. When one is running,volta-studio --mcpforwards your agent to it, so the same client configuration also gets the live tools.
The server never calls a model and needs no API key. Your agent brings the model. Volta brings the checks: every change compiles before it is written, and every claim about sound comes from a measurement.
Connect a client#
The binary is target/fast/volta-studio in a checkout (make build-studio), or /Applications/Volta Studio.app/Contents/MacOS/volta-studio in an installed app.
In this repository there is nothing to set up for Claude Code: the checked-in .mcp.json runs scripts/volta-mcp.sh, which builds volta-studio from the tree it sits in (--profile fast, a second or two when nothing changed) and then serves --mcp. A session in a worktree gets that worktree's tools. Claude Code asks once before it trusts a project's server. VOLTA_MCP_NO_BUILD=1 skips the build. If the build fails, the previous binary serves and a warning goes to stderr. A running server keeps the binary it launched with, so after a merge or rebuild reconnect it before trusting its measurements. Codex can point command at the same script.
Claude Code, from any other project folder:
claude mcp add volta -- /path/to/volta-studio --mcp
Codex — in ~/.codex/config.toml:
[mcp_servers.volta]
command = "/path/to/volta-studio"
args = ["--mcp", "--root", "/Users/you/Music/volta-songs"]
Claude Desktop — in claude_desktop_config.json:
{ "mcpServers": { "volta": {
"command": "/path/to/volta-studio",
"args": ["--mcp", "--root", "/Users/you/Music/volta-songs"] } } }
Desktop apps usually launch servers from /. Pass --root for every folder of songs they should reach. --headless never forwards to a running Studio, and --studio <pid> picks one Studio when several are open.
Freeform composition#
Ask for freeform or unguided composition to let the model choose its own musical approach and working process. In this repository, invoke $volta-freeform; an MCP client can use the local compose_freeform prompt with an ask brief and an optional path for the new song.
In this mode, the server directs the agent to volta://grounding/technical instead of the guided core, idioms and songwriting procedure. The resource contains factual syntax, type/unit, pattern, tempo, voice, track, mixer, arrangement and host behavior, the language traps that compile cleanly but misbehave, and pointers to the rest. The primer, the builtin reference and search_docs/read_doc remain available on demand. Production guides, genre recipes and corpus songs are skipped as musical guidance. The compiler and file-version protections still apply.
The local get_grounding tool also serves this resource with part: "technical". The hosted server serves the same resource. Its source is ui/src/grounding_technical.md, shared through the generated hosted grounding snapshot; it is separate from the in-app copilot's system block.
Use a fresh agent session for a comparison with guided composition: switching modes does not remove advice already loaded into that session. Reconnect the MCP server after rebuilding to get the new resource and prompt.
Which files it may touch#
It may read and write .volta files only under these folders:
- the folder the client started it in;
~/Documents/Volta;- each
--root; - in a Studio, the open song's folder.
Anything else is refused, and the refusal names the allowed folders.
The song library, the stdlib and the Learn lessons that ship with the Studio can be read but never written. The server finds them from its own executable: Contents/Resources in the installed app, or the checkout a development build came from. So an agent working in any folder still reaches the example songs, the stdlib modules and the lessons.
Tools#
Language and docs
| tool | what it does |
|---|---|
compile_volta | Compile a module: diagnostics, ok, the compiled module map (bindings by kind, tempo, markers) and the file's hash. With edits / adds, compile the module with those binding-level changes spliced in. A failing module lists every independent error — each broken binding's own error, a use of it folded in as (bdepends onxand was not checked) rather than repeated, and the lowering errors of bindings that do not depend on a broken one — so a second mistake is not hidden behind the first. A clean module that will still play silence somewhere, such as a sample name no reachable bank has, also gets silent: the doctor's findings and the folder the banks were looked up from. |
inspect_ir | The compiled IR the engine runs: the skeleton, or one binding's subtree. |
doctor | Why a module would be silent or suspicious, with the chain that causes it (Doctor: trace a silent signal), including sample names that match no bank. |
eval_expr | An expression's type, value and first-cycle events, in a song's scope. |
probe_kernel | Measure a user kernel, effect or kernel recipe offline through the engine. Name the subject by binding or at, the source text of the call. It returns the verdict, linear/memoryless, DC gain, resonant peak, decay time, a 40-point response, the transfer curve, the swept parameters and findings that name the failing case, such as "unstable above cut = 0.64×Nyquist (15.3 kHz)". values holds parameters by label or macro name. An oscillator or a processO source kernel is played as a tone instead: per pitch (110 Hz to 4.97 kHz) the reply's oscillator lists its level, alias floor and first harmonics. Measured, not listened to (See your DSP). |
lookup_builtin | Catalogue entries for up to eight builtins, with suggestions for unknown names. |
search_docs | Search this reference, the Learn lessons and example code. Each hit is citable as reference/<id>#<anchor>. |
read_doc | A whole reference page, or one section of it matched by heading, in 24 KB pages; without a section, it lists the page's headings. |
get_grounding | Grounding one part at a time: technical for freeform composition; primer, idioms, builtins, index, songwriting for the guided workflow. For clients whose model cannot read resources. |
format_volta | The volta fmt formatting of a module (returned, not written). |
Files
| tool | what it does |
|---|---|
read_volta | A file's text and hash. |
list_songs | Songs under the allowed folders and the shipped library, with catalogue descriptions and tags. |
create_volta | Write a new song. The path must not exist, and the module must compile clean. Sounds that will play silence are reported in silent, not refused. The song's history starts with it (below); why is recorded with it. |
apply_edit | Change an existing file: a whole source, or binding-level edits / adds. Reports silent like create_volta. The change becomes a version in the song's history, with your why. |
History (below)
| tool | what it does |
|---|---|
list_history | The song's versions, newest first, with authors, intents, ideas and markers. |
read_version | One version: its source, what changed against its parent, what it captured. |
diff_versions | What separates two versions, and a unified diff. |
keep_version | Keep a version as an idea (★), optionally named. |
restore_version | Put a version (or one part) back, protected first, with your why. |
The song
| tool | what it does |
|---|---|
song_structure | The form as the arrangement plays it: each section's start bar, length, scene, marker and tracks, plus tracks that never play and the length in bars and seconds (a moving varispeed adds length_seconds_note). A section's scene is the one the arrangement schedules there, even when another scene has the same tracks. shreds lists each shred's name, face (sequence/performer), whether it is endless, the macros it writes, the instruments it triggers, and a bounded unroll (≤ 48 ops by bar/beat: sets, lanes, notes with their on: track, records, start/stop cues; loop_restart; a performer's live_from). |
pattern_events | The notes the scheduler plays over a span, per track: bar, beat, note name, velocity, length. This is the MIDI-export note stream, gated to where each track is active. format: "compact" gives one line per bar instead, such as "C5@1 A4@2 D5@2.5 [A3,C4,E4]@3=90": note at beat, simultaneous notes in brackets, and =velocity only where it differs from the track's usual one. The compile's warnings come back as warnings ({kind, message}), because they are often why the notes differ from what was meant: a bare (1/8) rate is read as 0.125 Hz, and the IntegerFractionRate warning says so there. |
harmony_report | The song's harmony as the Lens reads it (The harmony Lens), from the same compile as pattern_events and the same note feed as the workbench's Report tab. chords: name, roman, exact (read from a chord or numeral token, else detected), bars (1-based, [from, to) over one pass), tracks, outside_key and at (the token or first atom: byte start/len, 1-based line/col, text). keys: each region's declared and detected key and confidence. findings, advisory and never compile errors: key_mismatch, clash (different tracks a minor second or ninth apart, or a tritone against the bass), out_of_key (clustered by atom, with a count) and progression (roman numerals per marker, scene or eight bars). Each has a severity (info/warn), bars, where, message and at. Some also carry a fix, a replacement for text at start/len to pass to apply_edit. key reads the song against another key. |
harmony_atlas | The harmony analysis over the song corpus (The harmony atlas): each server root's examples/songs/, compiled and read through the same feed as harmony_report. With no arguments, progressions per length ("2".."4"): key-relative roman n-grams (quality kept, inversions dropped, repeats collapsed by degree), each with songs (how many use it), count and up to five examples; n keeps one length, limit sets the rows. song (an atlas id, unique suffix or path) returns its fingerprint (keys, chords, vocabulary, exact_pct, mean_tension, top) and similar songs with a cosine score and the shared progressions. progression ("ii V I") returns the songs that use it. corpus counts files, analysed, chordless and failed songs. The first call compiles the corpus (about a minute); it is cached until a file changes. |
reharmonise | A whole-passage rewrite as the Lens's Passage tab makes it (The harmony Lens): move (modal_interchange, tritone_sub, relative, darker, brighter) over the song, or from_bar/to_bar. Only chords read from a chord or numeral token are rewritten (with a progression declaration, its numerals); each proposal carries from/to, romans, plays/outside counts and a fix for apply_edit; hand-stacked chords are counted as skipped. harmony_report also lists key_changes with pivot chords, each with a fix. |
case_view | The Rack case of a patch, track or function (See a patch as a case), from the same projection the workbench paints (volta_compiler::panels). Each panel has its kind (osc, filter, envelope, voice, expr…), title, label and span. Each knob has its value, unit, span, the literal span an edit replaces, editable, its modulation rings and cv sources, and its checked type when the module checks. Each input jack has its source and level. cables are the patch cables the case draws ({from: {panel: i} | {note: "pitch"} | {name: "x"}, to: i}), and summary counts modules, cables and panels shown as source. Spans are byte offsets into the module plus 1-based line/col. With song: true (or owner: "song" when no binding is named song), it returns the whole song as one case (See the whole song as a case): the same panels plus rows (track, bus, master, each a chain of panel indices left to right and its extra modulators) and not_drawn (mixer entries with no row). A send is a bus panel's input jack fed by the track's strip, its level the send amount's literal; a sampler panel lists its samples. Without owner, it lists the patches and tracks a case opens on. A patch defined in an imported library is read from that library's file. |
list_sounds | What a song can use: samples (the banks reachable from its folder, each with its number of variants, name:0 to name:<n-1>), presets (saved plugin presets), shelf (the patch shelf), modules (the stdlib and what each exports). A path that does not exist yet reads its folder, for a song about to be created. A path outside the allowed folders is refused, never answered with another folder's banks. |
list_prints | A saved song's bounceOf / bouncesOf / sampleOf defs and their pinned prints (Pin a print: keep a bounce as a song-local sample): pinned or not, current / recipe changed / unknown, whether a pin is a take. |
pin_print | pin / unpin / keep / reprint a binding's print, or take it with explicit macros held; writes the song's prints/ folder, never the source. Replies with the prints afterwards. |
Hearing and delivering
| tool | what it does |
|---|---|
measure_audio | Loudness (LUFS, true peak) and each track's RMS, peak and eight labeled bands. With edits / adds, it measures a change without writing it (below); with version, a version from the song's history (below). By default it renders the first 4 cycles (at most 8) flat, as the copilot does. With section (a marker or scene), bars ([from, to], 1-based) or whole_song, it renders through the arrangement and adds loudness per section: the song's energy curve. Each section row carries lufs, rms_db, true_peak_db and plr_db (true peak − LUFS: how hard a peak limiter must work to raise that section), and section_warning appears when every gated, non-silent section in the span lies within 2 LU of the others — a flat form, the signature of a master clipper or limiter pinning every section to one level. With per_track (the default for spans of 32 bars or fewer) it also measures each track's own output, captured in the same render: post-fader and track effects, on the master's scale before its inserts, with sends and their returns left out; each section then also lists its playing tracks' loudness (track_lufs), the balance section by section. compare_to returns the band-by-band difference from a second module. Bands average the whole span (up to 512 evenly spread windows). Each band is the mean-square level of the mono mix inside it, in dBFS on the same scale as rms_db (a sine at −9 dB RMS reads −9 in its band), so bands compare with each other directly; bands differ in width, so read them against pink noise, which reads, relative to mid, sub −1.0, low −0.6, low-mid −1.8, high-mid −3.0, presence −3.0, brilliance −5.3, air −4.3 dB. true_peak_db is the 8× delivery measurement. Every reply has master, tracks keyed by name, and reading (how to read it). It (and render_audio) carries silent like compile_volta. |
spectrogram | An image of a span: time across, log frequency up (30 Hz–20 kHz), bar lines marked, optionally one track alone. A model can read it for mud, masking, empty ranges and pumping. With track, a track it depends on (a duck source, a tapLive) still runs so the ducking is right, but none of that track's sound is drawn, including what it sends to a return the soloed track also uses. |
render_audio | Audio a person can play (with version, a version from the song's history — below): WAV clips of a span, optional stems, a solo track (not with stems), and return_audio to embed a short copy for clients that take audio. With whole_song (or no span; cycles sets the length of a song with no arrangement), the full export with its tail, as <out>/<song>/master.wav plus <track>.wav per stem; with solo too, that track alone for the whole song as a clip (<song>-whole-song-solo-<track>.wav, no tail, dither or loudness target); target_lufs prints it at that loudness, and max_loss (LU) caps what the limiter may cost — the print lands quieter instead and the reply says target_met: false with the shortfall_lu. A targeted reply always carries gain_db and limiter_loss_lu (Mastering and delivery § "A loudness-loss budget"). |
audition | Renders the same span of two versions, writes A and B, and measures the difference. When the client can ask its user, it asks which one works better. |
export_song | A deliverable: wav (dithered and stamped, as the Studio's Export writes it), stems, midi or dawproject. target_lufs and max_loss behave as on render_audio. |
Workbench skins (Skins § The agent loop)
| tool | what it does |
|---|---|
list_skins | The user's skin folder (each skin's modes, whether it resolves, its repair count and hash) and the built-in families a skin may extends. |
read_skin | A user skin's skin.json text and hash, plus its artwork, manifest, offered workspace and pack_hash; or a built-in theme's derived palette tokens per mode (what extends starts from). |
validate_skin | Resolve a skin.json without writing it: ok, document errors, and per mode the SkinReport — errors with JSON paths, the colours the readability solver repaired (authored, measured, resolved), warnings. |
preview_skin | The skin sheet as a PNG per mode (or the one mode names), with the report beside it, and the control-state sheet when the skin binds a face; with views (up to 4), real workbench captures under the skin on the bundled bench song, rendered by the volta-shot binary beside the server (development builds). Candidate assets and a workspace are previewed without being installed. A skin with errors has no picture. Needs a GPU adapter. |
write_skin | Validate, then write <skins folder>/<name>/skin.json only when the report has no errors; replacing an existing skin needs the expect_hash from read_skin (or a skin this session read or wrote). A running Studio picks it up within a second. |
customization_catalog | Everything a pack may name and every budget, generated from the validators' constants. |
validate_skin_pack / write_skin_pack | A whole pack (skin.json, PNG artwork, an offered workspace; the manifest is generated), checked as Import checks it; the write needs the pack_hash and installs nothing on a refusal. |
read_workspace / validate_workspace / write_workspace / preview_workspace | Layouts: presets and the one a pack offers, checked with their arrangement at three window sizes, written into a pack (never applied), and captured in isolation. |
The pack and workspace tools are described in Designing a workbench pack.
Audio goes to <song folder>/renders/, or to the folder named by out. A whole-song export gets its own subfolder, named after the song. out is always a folder unless it ends in an audio, MIDI or DAWproject extension (.wav, .mid, …), which names a file whose folder is used; a folder name with a dot in it (o-saw-0.95) is still a folder.
A stale server says so. A server started from a source checkout compares its binary's build time with the newest source file of the crates it is built from (at most every 30 s). When the tree is newer, or the binary was rebuilt after the server started, every tool result ends with a warning: line naming the changed file and the tree's HEAD: restart the server (scripts/volta-mcp.sh rebuilds) to pick up new builtins. Outside a checkout there is no check.
Reading a measurement. Each reply says this in its reading field:
- Bands are the mean power in each range (sub below 60 Hz, low 60–200, low-mid 200–500, mid 500–2 k, high-mid 2–4 k, presence 4–8 k, brilliance 8–12 k, air 12–20 k), in dBFS. They are averaged over windows spread across the whole span (before 2026-09-23 only the span's first ~5.5 s was read). They are absolute levels, so compare one band across tracks or versions, not bands against each other. −120 is the floor.
- A track's numbers come from that track's own output captured in the mix render, on the master's scale before its inserts (
tracks_notesays so). A flat or varispeed song renders each track alone instead. A master gain stage moves every track's number too.
Renders are reused. Measuring the whole song, then a section, then rendering that section as a clip used to render the same audio three times. The server keeps recent renders, keyed by the compiled song (imports included), its samples folder and the buffer (the mix, a soloed track, or a track's in-mix output). A span is served as a slice of a kept render that reaches its end and started no later than the span's own render would (its pre-roll downbeat, or bar 1 with full_context); the reply says cached: true, and context names the bar the kept render started from, so a section after a whole-song measurement reads "rendered from bar 1". The cache is bounded by VOLTA_MCP_RENDER_CACHE_MB (default 2048; 0 turns it off), and single tracks are evicted before the mix. A dense three-minute song is about 72 MB per buffer, so a whole-song measurement with every track alone is over a gigabyte.
compile_volta, measure_audio and inspect_ir are the copilot's own tools, running the same code, so the two surfaces agree on every result.
Trying a change before writing it#
measure_audio (and its compare_to), spectrogram, pattern_events, song_structure, doctor, inspect_ir, eval_expr, probe_kernel and each side of audition accept the same edits / adds as compile_volta. The tool analyzes the module with those changes spliced in and writes nothing, so a mix move can be measured before it is applied. measure_audio marks such a result with candidate and the spliced text's hash. compare_to: { path, edits } measures the saved file against the candidate, and audition with a: { path }, b: { path, edits } lets a person hear it. render_audio and export_song always use the saved file, so a candidate never overwrites its renders. A candidate that does not splice (an unknown binding) is an error prefixed edits:.
An edit's binding is any top-level declaration: name = …, an annotated name : T = …, a function name x y = …, each with or without priv / rec. mixer names the mixer block, so a fader pass is one edit rather than a whole-source rewrite. The edit's source is the whole new block, declaration line included.
Annotations (@doc "…", @category "…") sit above the declaration line and are not part of the block. An edit whose source starts at the declaration keeps the binding's annotations. An edit whose source starts with annotation lines merges them key by key:
- a key it writes replaces that key's line where it stood;
- a key new to the binding goes after the existing ones;
- a key it does not mention stays.
So an edit never duplicates an annotation and never drops one it did not name. To remove an annotation, write the whole source. The hosted server splices the same way.
Long renders#
A span render does not start at bar 1. It starts at the last section downbeat at least four bars before the span, so bar 121 costs a few bars of rendering instead of 120. Each track that is already playing there keeps the bar it entered on, so a phrase that counts from its entry keeps its place. Macro values set by earlier sections are restored before that section's own motion starts. What a pre-roll does not carry is state older than its downbeat: a counter's count, free-running random drift, a tail longer than the pre-roll. Pass full_context to walk from bar 1. Each result's context says which it did. A song with no arrangement, or with varispeed, always renders from bar 1, and its per-track numbers come from rendering each track alone. A varispeed song's span is cut where its bars actually play — each render maps bars to frames through its own recorded speed map, so a scene-ridden or knob-driven ratio measures the music it plays — and is not kept in the render cache.
Renders report progress to clients that send a progressToken, a few times a second. A client that gives up sends notifications/cancelled, and the render stops at its next check; the cancelled call gets no response, and a call cancelled while it waits in the queue never runs. Whole-song exports (render_audio with whole_song, export_song) send a heartbeat every five seconds instead, and run to the end once started.
How a change is written#
apply_edit writes only when both of these hold:
- The file is unchanged. It must still match the
expect_hashyour agent read (fromread_voltaorcompile_volta). If someone edited it in between, nothing is written and the agent is told to read it again. An agent may leaveexpect_hashout for a file this session has already read or written: the server then expects the version the session last saw, so a change anyone else made since is still refused. - The result compiles clean. Otherwise nothing is written and the errors come back.
The write is atomic: a temporary file, then a rename. The reply is the new hash and how many lines changed. A binding-level edit naming a binding that is not in the file is refused, never spliced into the wrong place.
Before the file moves, the change is recorded in the song's history (below): the file's current content is kept as a version first when the history does not already hold it, then the change becomes a version authored by your client, with why as its intent (without why it is titled by what changed). The reply's history names both. A history that cannot be written (another writer holds its lock past a short wait, or it is unreadable) refuses the edit: nothing is written.
With a Studio open#
Turn on View ▸ Assist ▸ Allow Agent Control. Launching with --agent-control, or with VOLTA_AGENT_CONTROL=1 set, turns it on at start. The setting is not saved: each launch starts with it off unless you pass the flag. Turning it off disconnects every agent.
While an agent is connected, the header shows agent connected · disconnect. Click it to disconnect. A toast appears whenever an agent connects or disconnects.
An agent cannot change your buffer on its own unless you also turn on View ▸ Assist ▸ Let Agents Apply Edits. This is off at every launch. With it off, the agent proposes a change and you decide.
These tools appear for the connected agent:
| tool | what it does |
|---|---|
studio_status | The open song's path, whether it has unsaved changes, the buffer hash, whether it is playing and where (bar.beat), tempo, meter, markers, scenes, tracks, and macros with their current values. |
studio_read_buffer | The buffer as it is on screen, which may differ from the file, with its hash. |
studio_transport | Play, stop, pause, resume, seek to a bar or a marker, a live tempo override, switch scene, loop a range of bars (from_bar to to_bar, inclusive), or stop looping: the same commands the header and the timeline send. |
studio_mix | Solo, mute or set the level (−60 to +6 dB) of one track, the way the mixer's keys and fader do. These are performance moves: nothing is written to source, and the next run restores the written mix. |
studio_set_macro | Turn a macro like a knob. This is a performance move and is not written to source. |
studio_propose_edit | Offer a change for you to review: a whole source, or binding-level edits / adds. It must compile clean. It opens in the Copilot panel as a diff card with verify & apply and discard. The agent gets a proposal_id. Accepted, it becomes a version authored by the client, with its why (else the label) as intent. |
studio_proposal_status | Where a proposal stands: pending, accepted (with applied_hash, the buffer's new hash), discarded, or superseded by a newer proposal or a Copilot reply. |
studio_apply_edit | Only with Let Agents Apply Edits on. Change the buffer the way the copilot's verify & apply does: only if the buffer still has expect_hash and the result compiles clean. It is one step in Undo, and a toast names it. Your working copy is kept in history first, and the change is a version of its own at once, authored by the client and explained by why (else the label); the reply names its version. With swap, the change is also loaded so you hear it. With the switch off, the agent is told to propose instead. |
studio_open | Open a .volta file from the allowed folders as the live song, the way File ▸ Open does. It is refused while the open song has unsaved changes. The reply names the file and the new buffer's hash. |
studio_save | Save the buffer the way ⌘S does: formatted on save, and a library song is saved as your own copy. The reply names the saved file and the new hash. |
verify & apply compiles the proposal again when you press it, so a proposal that stopped compiling (because the buffer changed underneath it) is not applied. The card says why.
measure_audio also accepts target: "live", which reads the playing transport's meters. Module tools called with neither path nor source work on the open buffer. apply_edit refuses the open song's file, so an agent cannot write underneath your unsaved changes.
The Studio also offers these resources:
volta://studio/bufferis the buffer as text. An agent that subscribes is told when it changes (your typing, Undo, an applied edit), at most twice a second.volta://studio/statusisstudio_statusas JSON. It changes every beat, so agents read it rather than subscribe to it.volta://studio/proposals/{id}is one proposal's status as JSON. An agent that subscribes is told when you accept or discard it.volta://studio/historyis the open song's history as JSON (list_history's shape, the newest 50). An agent that subscribes is told when a version is made, kept, restored or starts sounding.
The socket is ~/.volta/run/studio-<pid>.sock. Only your own user account can open it.
Song history#
Every song keeps a journal of versions (Save ideas, compare versions and record takes): each Run, Rack or mixer move, copilot apply and agent edit, with who made it and why. An agent reads it, keeps and restores versions, and its own edits are versions.
- Which history. A song open in a Studio with agent control (no
path, or the open song's path) is read live, from the Studio's memory, with thesoundingmarker; replies sayfrom: "studio". Any other file is read from its store on disk (from: "disk"):<workspace>/.studio-takes/<song>/for a song under~/Documents/Volta, else~/.volta/studio-takes/<hash>/— the store the Studio itself uses. list_history {path, filter?, part?, limit?}— newest first. Each row:v,title,authorandauthor_kind(you,copilot,agent,peer),intent(why),at_msandage,via(the surface:run,rack,mixer,copilot,agent,checkpoint, …),keepfor an idea,touches(the parts it changed), a 12-characterhashprefix,parent, andediting(where the draft started) /sounding(what plays — open songs only).sound: "not captured"marks a version an agent wrote on disk.filterisall,ideas,youoragents(Copilot and outside agents);partkeeps versions that touched a binding or control;limitdefaults to 20. The reply also listsideas,matchedandtotal.read_version {path, version}— the row, the wholesource,change(what moved against its parent in musical words, compiled when both versions compile),scope, andcontext: imports, control values, scene, whether its sounds were captured, warnings.diff_versions {path, a, b}—change,scope,identical(equal snapshots), and a unifieddiffof the sources, a → b.keep_version {path, version?, title?}— pins the version as an idea (★) and names it. Withoutversion, the open song keeps what it is sounding (★ Keep); a file keeps the version it is at.restore_version {path, version, part?, why, expect_hash?}— puts the version back, or onlypart(spliced into the song and compile-checked; a part that needs other changes from that version is refused). The current content is protected first: in the Studio, the History pane's restore (your draft becomes acheckpoint"before codex restore v3 — why"), one undo step, not run; a part restore is then the agent's own version. For a file, the file's content is kept as a checkpoint when the history does not already hold it, a whole-song restore moves the editing version to the restored one, a part restore is the agent's version, and the file is written throughapply_edit's hash gate (expect_hash, default the hash this session last read or wrote).
version is a number, "v12", "editing", "sounding" (open songs) or "latest". In a Studio, keep_version and restore_version need Let Agents Apply Edits, like studio_apply_edit.
Who an agent is. The author of an agent's versions is the name its client sent in initialize (clientInfo), else "agent" — attribution, not authentication: a client names itself. A Studio learns the name when the client connects, so an accepted proposal carries it too.
Writing a closed song's history. File writes take the store's .history.lock (the Studio's history writer takes the same lock) and read the history under it. An edit on disk records no sounds — no engine ran — so its version holds the source's resolved imports and initial control values and the warning recorded by an agent on disk; sound context not captured; the Studio captures the sounds the next time it runs the song. A Studio that has the same song open without agent control finds the store changed on its next save and merges the agent's versions into its journal instead of overwriting them (Save ideas, compare versions and record takes § Agent versions on disk).
Hear a past version#
measure_audio and render_audio take version, so an agent can A/B its own change before claiming it helped. Each reply carries version (the row) and rendered_with, which says how:
- a version whose sounds the Studio archived, over a span (
bars,section, ormeasure_audio's default first bars), renders through the history render path: its source, imports, control values, sample banks and plugin states from the store — the master only; - otherwise (a version an agent wrote on disk, a legacy record,
per_track,stemsorwhole_song), its source with its captured imports against the song folder's current banks at the source's initial control values —rendered_withnames which and why.
render_audio writes a version's files under renders/v<n>/ unless out names a folder. version cannot be combined with edits, adds or compare_to.
What else it offers#
Resources:
volta://grounding/coreis the language primer, the idioms and traps, the reference index and the builtin reference in one document (about 32k tokens). The server's instructions ask agents to read it once per session before guided work. Its four parts are also available separately.volta://grounding/technicalis the separate factual grounding for freeform composition.volta://grounding/pack(alsoget_groundingpartpack) is the in-app copilot's whole knowledge pack, byte for byte: the primer, the idioms, every guide with its section anchors, every builtin with its example, the corpus's common progressions and a groove table by genre, and example songs. It is several times the size ofcore; read it only when you want everything the copilot sees.volta://reference/<id>is any page of this reference.volta://guide/<name>holds the songwriting know-how Volta's own song-writing agents use:songwriting: the procedure, restated for these tools;production-library: where to start;arrangement,techniquesandrhythm-studies;- the genre cards:
genres-dance,genres-bass,genres-club,genres-experimental; sources;research: an imported atlas, whose code is illustrative;skins: writing a workbench skin — the loop, the schema and its vocabularies.
All of these are compiled into the binary.
Prompts:
explain,critique,fix,editandcontinue: the copilot's modes, rewritten for an agent that works through tools.compose: one song from a brief, following the songwriting procedure.compose_freeform: original music from a brief, with the model choosing its own musical approach and process.produce_set: an EP or album, starting with a coverage table and a contrast plan.
Hosted server#
For a client that cannot launch a local binary, such as a claude.ai custom connector, the community service hosts a smaller server at /api/mcp (https://volta.fm/api/mcp once deployed). It speaks MCP's Streamable HTTP transport in its simplest form:
- each POST carries one JSON-RPC message, or a batch of up to 32;
- a request gets a JSON answer, and a notification gets
202with no body; GETandDELETEget405: there is no server-initiated stream and no session to end.
It negotiates the same four protocol revisions as the local server and refuses an unknown MCP-Protocol-Version header with 400. A browser request whose Origin is not the site's own is refused with 403, which blocks DNS rebinding. Clients that run on a server send no Origin, so the check does not affect them. MCP_ALLOWED_ORIGINS admits other browser origins.
What it offers. It has no files, no audio and no Studio, so it has none of the tools that need them: measure_audio, apply_edit, read_volta, list_songs, eval_expr, probe_kernel (not yet ported to the hosted server) and the studio_* tools. The rest keep their local names, arguments and result shapes, but take module text as source instead of a path:
| tool | differs from the local server |
|---|---|
compile_volta | source is required, and edits / adds are spliced into it. Imports resolve only against a sources map ({ "<import path>": "<module text>" }); there is no library or registry lookup. There is no file_hash. Diagnostics come from the compiler's checker, so the doctor findings the desktop analyzer also reports as warnings are not included; call doctor for them. |
inspect_ir, format_volta | Take source instead of path. |
doctor | Takes source. It has no include_unplayed. |
lookup_builtin | Same. |
search_docs | Searches this reference only (up to six sections), without the Learn lessons and example code. |
read_doc | Only on the hosted server. It returns a guide with its outline, or one section by anchor. id accepts a search locator such as reference/filters#<anchor>. A guide over 64 KB is cut at a paragraph and marked truncated. |
The hosted grounding and reference resources match the local server's: volta://grounding/core and its four parts, volta://grounding/technical, volta://reference and volta://reference/{id}. They are built from the same reference corpus and builtin catalogue, with the same code ported to TypeScript, and tests check that both produce the same text. There are no prompts. The instructions say what the server can do and name the local server for everything else.
Data. The compiler wasm fills most of the Worker's compressed script budget (3 MB on Cloudflare's free plan, 10 MB paid), so the reference corpus and builtin catalogue are not bundled. They ship as static assets ([assets] over ui/assets in web/wrangler.toml, binding ASSETS, with run_worker_first so no asset is served by path), and mcpDataLoader (web/src/index.ts) reads them once per isolate. Other large data goes the same way.
Quotas. The hosted server calls no model, so its limits bound compiler time rather than spending. Each tools/call and resources/read counts as one call. Listing, initialize and ping are free.
- Anonymous callers get
MCP_ANON_DAILY_LIMITcalls a day per IP address (100 by default). - A request with a Volta session token (
Authorization: Bearer <token>) getsMCP_USER_DAILY_LIMITcalls a day per account (1000 by default). A token that does not verify is refused with401; it is not treated as anonymous. MCP_GLOBAL_DAILY_LIMITcaps everyone together.
The server keeps its own count, so these calls never use up copilot or command-bar allowance. Over the limit, a tool call returns an error result the agent can relay, and a resource read returns JSON-RPC error -32000. Request bodies are capped at 1 MiB. The endpoint skips the site's hourly POST limit, because one agent session makes dozens of requests.
Add it to claude.ai (Settings → Connectors → Add custom connector, at the time of writing): enter the URL https://volta.fm/api/mcp and no authentication. Claude Code: claude mcp add --transport http volta https://volta.fm/api/mcp, with --header "Authorization: Bearer <token>" for the signed-in quota.
A claude.ai connector calls the server from Anthropic's servers, not from your browser, so claude.ai users are expected to share the anonymous per-IP quota of those servers' addresses (not yet observed against a deployment). Only a signed-in session separates them, and a connector cannot send one until the server supports OAuth (Use Volta from your own AI agent (MCP) §10).
Limits#
- Measured, not heard. An agent reports numbers from a render or the meters, not listening, and every audio result says so.
- No plugins in renders. Songs with native plugins cannot be rendered offline yet.
- Clips are cuts. A
render_audioclip is cut from the arranged render with no fade or tail. Usewhole_songorexport_songfor a deliverable. - Stems leave out the returns. Span stems are each track's own output; a reverb or delay bus a track sends to is not in any stem.
- MIDI and DAWproject need the CLI. Those writers live in the
voltaCLI, which must sit beside the Studio binary or be onPATH.wavandstemsneed nothing extra. - Only a person can judge by ear.
auditioncan ask the user only when the client supports asking questions (MCP elicitation). Otherwise it returns the two files and the measured difference. - Platform. The Studio socket needs macOS or Linux.
--mcpruns anywhere the Studio builds. - Not in the browser. The browser workbench cannot host a server. The hosted server compiles and documents, but cannot render or write.
- Not persistent. Both agent switches reset at every launch.