API, CLI and MCP
Use JavaScript or TypeScript to build patches, the command line to process files, and MCP to give an agent structured patch tools. These interfaces edit the authored patch: the nodes, connections and settings you save. They do not execute arbitrary JavaScript inside Throughline or add a code editor to the UI.
Availability and prerequisites
These instructions describe the source-built 0.1.0 SDK and CLI. The SDK has a public-package build; the CLI package is private. Use the local archives below; a package manifest is not evidence that a package is available from npm. The tooling is separate from the plugin installer.
Use Node.js 22.12 or newer for the combined workflow, including its built-in WebSocket support. Building from source uses the repository's pinned pnpm 10.33.4 and its lockfile. The pure SDK also supports modern browser bundlers without filesystem access. The shell examples use a POSIX shell. Plugin platform requirements are in Installation. The live native transport has macOS JUCE proof; these instructions do not claim equivalent live-plugin verification on Windows or other DAW/format combinations.
Offline MCP, live MCP and the live SDK are available in the current source build.
Live MCP was integrated in
PR #1614.
Check your installed throughline mcp --help for --live-url; older archives
may contain only the offline server.
The release audit
remains separate from this guide.
| Choose | Useful for | Needs an open editor? |
|---|---|---|
| JS/TS offline SDK | Typed builders, repeatable scripts, in-memory patches | No |
| CLI semantic commands | File pipelines and catalog discovery | No |
| Offline MCP | Agent authoring in isolated document sessions | No |
| Live SDK | Reading, editing and guarded undo in a paired instance | Yes |
| Live MCP | The same scoped live workflow through an agent client | Yes |
Offline validation proves document compatibility and supported operations. It does not start audio, load referenced media, or prove native playback. The browser host normally provides UI-only evidence.
Install and verify the tools
From a source checkout:
pnpm install --frozen-lockfile
pnpm -C products/throughline/packages/api pack
pnpm -C packages/cli pack
In a separate working directory, replace the archive paths with your checkout:
npm init -y
npm install /absolute/checkout/products/throughline/packages/api/throughline-api-0.1.0.tgz
npm install /absolute/checkout/packages/cli/throughline-cli-0.1.0.tgz
./node_modules/.bin/throughline --help
./node_modules/.bin/throughline catalog osc.basic
The rest of this guide uses throughline for that installed executable. Use its
absolute path in agent configuration. A global local-archive install with
npm install -g /absolute/path/to/throughline-cli-0.1.0.tgz is another option.
SDK exports are ESM; use .mjs for JavaScript or NodeNext modules for TypeScript.
Create, edit and load a patch
Save this as create-patch.mjs in the external project:
import { createPatch } from "@throughline/api";
import { exportPatchFile } from "@throughline/api/node";
const document = createPatch({ name: "My oscillator" });
const receipt = document.edit(
{ operationId: crypto.randomUUID(), expectedRevision: document.read().revision },
(draft) => {
const oscillator = draft.addNode("osc.basic", {
id: "osc",
position: { x: 0, y: 0 },
params: { waveform: "sine", pitch: 69, amp: 0.05 },
});
const output = draft.addNode("io.audioOut", {
id: "out",
position: { x: 320, y: 0 },
});
draft.connect(oscillator, "signal", output, "audio", "signal");
}
);
if (receipt.documentStatus !== "accepted") throw new Error(JSON.stringify(receipt.diagnostics));
await exportPatchFile(document, "patch.json");
document.close();
Run node create-patch.mjs. The output refuses to replace an existing file.
openPatch(jsonOrObject) opens an in-memory patch; openPatchFile(path) from
@throughline/api/node opens a file. document.exportJSON() returns text without
writing. File calls use your process's permissions and never load referenced media.
Use listNodes() and describeNode("osc.basic") to inspect availability,
parameters and port IDs. In TypeScript, NodeParameters<"osc.basic"> supplies
parameter types; the builder checks node-specific input and output port IDs.
JavaScript receives the same runtime validation.
Save this semantic batch as operations.json:
[
{ "type": "node.setParams", "nodeId": "osc", "params": { "pitch": 72 } },
{ "type": "node.setPosition", "nodeId": "osc", "position": { "x": 80, "y": 80 } }
]
Then run:
throughline catalog --search osc --limit 10
throughline open patch.json |
throughline validate - --operations operations.json |
throughline apply - --operations operations.json |
throughline export - -o edited.json
throughline inspect edited.json --validate --audit --json
validate emits the unchanged patch after checking the candidate. apply commits
the batch. Each CLI invocation has its own offline session; revisions do not
carry across CLI processes. Use -o for protected file output, and --overwrite
only for deliberate replacement. Shell > can truncate a destination before the
CLI validates it. Semantic failures emit JSON diagnostics on stderr and exit 1.
Open a disposable Throughline instance, turn down the host output, and load
edited.json through the existing patch file import UI. Confirm the Oscillator,
Audio Output and their cable are visible, and inspect the oscillator pitch.
This patch produces a continuous tone in an audio-capable plugin. Verify native
playback there; the browser canvas alone cannot prove it sounds.
The repository's
automation examples
include executable JavaScript, strict TypeScript and an offline MCP driver.
node products/throughline/docs/examples/automation/verify.mjs builds and tests
them in a temporary external project without contacting an open editor.
Supported editing scope
A batch supports ordinary root nodes and direct edges: node.add, node.remove,
node.duplicate, node.setPosition, node.setParams, edge.connect and
edge.disconnect. The SDK validates the whole candidate before committing once.
A rejected batch has no partial authored changes. Hidden or unavailable nodes,
runtime-owned parameters, and edits requiring group, Polyphony Zone, Macro or
asset ownership changes reject. Existing complex patch content is preserved;
read support does not imply authoring support for every field.
Connections use explicit port IDs. They do not drag a cable, insert UI adapters,
or invoke assisted canvas recipes. Existing throughline patch --apply is the
separate RFC 6902 file facility. render and repl retain their offline engine
behavior; their output does not prove native DSP parity.
Pair an open editor
Run the companion in its own terminal. For the macOS JUCE webview:
throughline serve --allow-origin juce://juce.backend
For a browser development editor, pass its actual origin instead, such as
--allow-origin http://localhost:5173. Keep localhost and the exact origin;
wildcards and a different host address are not interchangeable.
- Enter
editorin the companion terminal. - Within 60 seconds, import its private enrollment file in the editor's Script access → Pairing file control.
- Enter
instancesin the companion terminal. Match the open editor and retain its exactinstanceId. A patch name is not an identity. - Enroll a client for that ID using the least access needed below.
- Connect that client within 60 seconds. Editor and client enrollment files are separate, single-use credentials. Do not put secrets in logs or issues.
| Companion command | Permission for the named instances |
|---|---|
client <instance-id> |
Reads and operation lookup |
client-write <instance-id> |
Reads and parameter writes |
client-operations <instance-id> |
Reads, parameters and root graph edits |
client-undo <instance-id> |
Reads and guarded undo |
client-all <instance-id> |
Reads, parameters, root graph edits and undo |
Commands accept space-separated IDs for an explicitly authorized set.
With --pairing-file, import the editor file before creating the client file:
the companion replaces its own file with the next enrollment. Existing unrelated
files are refused. Default enrollment files are private temporary files.
For a read-only SDK check, save read-live.mjs:
import { readFile } from "node:fs/promises";
import { connectLive } from "@throughline/api";
const [file, instanceId] = process.argv.slice(2);
if (!file || !instanceId) throw new Error("Supply client pairing file and exact instanceId");
const enrollment = JSON.parse(await readFile(file, "utf8"));
const live = await connectLive({ url: enrollment.endpoint, secret: enrollment.secret });
try {
const instances = await live.listInstances();
const target = instances.find((item) => item.instanceId === instanceId);
if (!target) throw new Error("Selected instance is not connected and authorized");
console.log(target);
const instance = live.instance(instanceId);
const snapshot = await instance.patch.read();
console.log(snapshot.patch.name, snapshot.revision);
console.log(await instance.catalog());
} finally {
live.close();
}
Run node read-live.mjs /absolute/client-pairing.json EXACT_INSTANCE_ID.
For writes in the same connection, read first and call
instance.patch.applyOperations({ operationId, expectedRevision, operations }).
Parameter-only clients use instance.patch.applyParameters(...).
Query instance.operation(operationId) for the current receipt.
To undo, read again and call
instance.patch.undoOperation({ operationId: crypto.randomUUID(), expectedRevision: current.revision, targetOperationId }).
Check capabilities before choosing a method. The
SDK reference
contains complete live examples and resource limits.
Configure an agent client
The server uses local stdio: the agent client launches the executable and
exchanges protocol messages over stdin/stdout. It is not an HTTP MCP endpoint.
For a client accepting mcpServers JSON, configure:
{
"mcpServers": {
"throughline": {
"command": "/absolute/project/node_modules/.bin/throughline",
"args": ["mcp", "--root", "/absolute/path/to/patches"]
}
}
}
Create the root directory first. Omit --root for in-memory-only access; repeat
it for more explicit roots. File tool paths must be absolute and inside a root.
Symlinks below roots and path escapes reject. Existing output requires
overwrite: true. Keep roots under your control.
For Codex, express the same process in its MCP TOML configuration:
[mcp_servers.throughline]
command = "/absolute/project/node_modules/.bin/throughline"
args = ["mcp", "--root", "/absolute/path/to/patches"]
Client configuration syntax belongs to the client. These examples describe stdio
integration, not a certification of every client/version. Restart or reload the
MCP server after configuration. Confirm tool discovery includes create_patch,
describe_node and get_patch; start with an in-memory patch to verify setup.
For live MCP, keep the paired editor and companion running. Use the endpoint from the client enrollment and supply its secret only in the server environment:
{
"mcpServers": {
"throughline-live": {
"command": "/absolute/project/node_modules/.bin/throughline",
"args": ["mcp", "--live-url", "ws://localhost:47831/live"],
"env": { "THROUGHLINE_MCP_SECRET": "CLIENT_ENROLLMENT_SECRET" }
}
}
}
Replace both values with the actual enrollment. Protect this configuration as a
credential. A newly launched MCP process needs fresh enrollment; the consumed
secret is not a reusable startup password. Optional --root enables offline file
access in the same process. A root does not grant live access.
MCP operation sequence
- Offline:
create_patch({name: "Example"}), oropen_patchwith onepatchJSON value/string or absolutepath. RetaindocumentIdandrevision. Live:list_instances, select the exact authorizedinstanceId, thenget_patch({instanceId}). - Discover definitions with
list_nodesanddescribe_node({nodeId: "osc.basic"}). Inspect live capabilities and versions as well as the catalog. - Send
validate_operationsa request like the following. Then send that same request toapply_operationsonly if valid and the intended edit is authorized.
{
"documentId": "DOCUMENT_ID_FROM_CREATE_PATCH",
"operationId": "550e8400-e29b-41d4-a716-446655440000",
"expectedRevision": "REVISION_FROM_READ",
"operations": [
{ "type": "node.add", "nodeType": "osc.basic", "nodeId": "osc", "position": { "x": 0, "y": 0 } }
]
}
Generate your own UUID for a new operation. For live requests replace
documentId with instanceId; never supply both. Validation does not reserve
or commit the candidate, so another edit can still invalidate its revision.
- Call
get_operationwith the same target and operation ID. Inspect both authored and runtime status. - Offline,
export_patch({documentId})returns ajsonstring; an explicitpathwrites inside a configured root.close_documentreleases the session. For a live file copy, read the patch, open a separate offline document withopen_patch({patch: snapshot.patch}), then export that document. - To undo a live or offline batch, call
get_patchagain, thenundo_operationwith the target, a new UUID, the currentexpectedRevisionand the originaltargetOperationId. Check the undo's own receipt too.
Resources provide bounded pages for large snapshots. Use the resource's first
revision for all following pages and restart the read if it becomes stale.
Subscriptions send compact change notices; read again for current content.
If live discovery fails, resource listing still exposes offline documents and
the catalog; list_instances reports the live error. Subscription retries back
off during outages and never replay writes.
The CLI/MCP reference
owns exact resource URLs, paging fields, protocol limits and tool inventory.
Understand revisions, receipts and recovery
An operation ID identifies one intended request. A revision says which snapshot you based it on. Return revisions unchanged: they are opaque tokens, not counters you increment. UI edits, host restore and other persisted changes can invalidate a live token. Two writes based on the same old state cannot both commit. Native publication revisions are separate evidence.
A receipt records the request's result. documentStatus: "accepted" means
the authored edit committed. It does not mean the native audio engine has applied
it. A plugin receipt may begin with runtimeStatus: "pending"; query the same
operation for correlated native settlement. applied is completion evidence for
that candidate. not-applicable describes offline/browser-only operation;
not-required does not claim a new audio publication.
failed, superseded, skipped, stale or indeterminate do not become success
because time passed. An accepted authored edit can remain visible while the
native engine keeps the last playable graph. Undo itself has a new receipt and
can require native preparation.
| Situation | What to do |
|---|---|
revision_conflict offline or stale_revision live |
Read again, inspect intervening changes, re-plan deliberately with a new ID. Do not just substitute a revision into an uncertain request. |
| Timeout or lost connection after a write | Preserve the exact request and UUID. Reconnect to the same surviving session and query it. An identical retained request may be retried; a fresh UUID could duplicate work. |
idempotency_conflict |
The ID was reused with different content. Recover the original request; do not mutate its identity. |
operation_unknown |
The ledger cannot prove the outcome, including expired receipts. Compare current authored/native state before deciding any new action. This is not evidence the request failed. |
undo_conflict |
A later graph edit or host change blocks undo. Preserve it. Inspect the difference and agree a new edit instead of restoring an old whole-patch snapshot. |
busy |
Stop sending writes and inspect unsettled operations. Terminal receipts have bounded retention; unresolved native operations are not evicted just because they are old. |
unavailable, hidden or disconnected editor |
Bring the same editor back and use Reconnect where appropriate. Do not retarget another instance with the same name. |
| Closed/reloaded editor or restarted companion | Old identity/authority may be gone. Pair afresh and establish a new baseline. This cannot prove an old uncertain write's outcome. |
| Pairing fails | Check localhost endpoint, exact allowed origin, 60-second expiry, correct editor/client file and the requested instance scope. |
| Read works but write fails | Check the explicit grant and advertised operation capability. A read-only enrollment cannot authorize edits. |
| Version mismatch | Compare SDK, API protocol, patch schema and catalog versions. Rebuild/install matching artifacts; do not bypass negotiation. MCP protocol negotiation is separate. |
| File request fails | Check absolute path, configured root, existing parent directory, symlinks, output existence and size limits. |
Terminal receipts are retained for up to 15 minutes with count/byte limits; live unsettled receipts remain while their editor survives. Reconnect grace is five minutes. Closing/reloading an editor loses its live ledger and undo authority. There is no closed-window control or native read transport in this workflow. Use Forget pairing to revoke the editor session when finished.