v0.1Latest

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.

  1. Enter editor in the companion terminal.
  2. Within 60 seconds, import its private enrollment file in the editor's Script access → Pairing file control.
  3. Enter instances in the companion terminal. Match the open editor and retain its exact instanceId. A patch name is not an identity.
  4. Enroll a client for that ID using the least access needed below.
  5. 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

  1. Offline: create_patch({name: "Example"}), or open_patch with one patch JSON value/string or absolute path. Retain documentId and revision. Live: list_instances, select the exact authorized instanceId, then get_patch({instanceId}).
  2. Discover definitions with list_nodes and describe_node({nodeId: "osc.basic"}). Inspect live capabilities and versions as well as the catalog.
  3. Send validate_operations a request like the following. Then send that same request to apply_operations only 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.

  1. Call get_operation with the same target and operation ID. Inspect both authored and runtime status.
  2. Offline, export_patch({documentId}) returns a json string; an explicit path writes inside a configured root. close_document releases the session. For a live file copy, read the patch, open a separate offline document with open_patch({patch: snapshot.patch}), then export that document.
  3. To undo a live or offline batch, call get_patch again, then undo_operation with the target, a new UUID, the current expectedRevision and the original targetOperationId. 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.