Documentation menu

DOCUMENTATION / v0.1

Use the smallest
maxforge surface that works.

Compile text into .maxpat files, generate large repeated graphs, or let an MCP-capable agent inspect and replace an explicitly managed patch scope. These are separate workflows with separate installation requirements.

Node.js 20+ Max 9 package available Protocol v1 experimental
Unofficial project

maxforge is not affiliated with, endorsed by, or sponsored by Cycling '74.

Choose a surface

Do not install the native external just to compile a file. Do not start the MCP server when a deterministic CLI command is enough. The extra layers only make sense when an agent must inspect or mutate an open Max patch.

Goal Use Requires Max running
Compile or validate .maxdsl npx maxforge No
Use maxforge from Node.js code npm install maxforge No
Generate thispatcher commands Library API Only when applying them
Inspect or replace an open patch with an agent maxforge-mcp + maxforge.sync Yes
Give an agent reliable operating instructions maxforge Skills No; Skills are instructions

Installation

Run the CLI without a global install

The package is published to npm. Pin a version in automation; use @latest for interactive evaluation.

npx maxforge@latest --help

Install the Node.js library

npm install maxforge

Install the native Max package

  1. 01
    Download maxforge.zip.
  2. 02
    Extract the top-level maxforge directory into the Max packages directory.
  3. 03
    Restart Max, then create a maxforge.sync object or open its help patch.
macOS~/Documents/Max 9/Packages/
Windows%USERPROFILE%\Documents\Max 9\Packages\
macOS signing

The macOS external is ad-hoc signed but not notarized. It must not be treated as Gatekeeper-safe.

Compile a first patch

Create basic.maxdsl:

basic.maxdslMAXDSL
patch "Basic Synth"

freq = number
mt = mtof
osc = cycle~ 440
mul = *~ 0.5
vol = gain~
dac = ezdac~

freq -> mt -> osc -> mul -> vol -> dac
vol[1] -> dac[1]

Validate, then compile:

npx maxforge@latest validate basic.maxdsl
npx maxforge@latest compile basic.maxdsl -o basic.maxpat

Open basic.maxpat in Max. The generated JSON is ordinary Max patcher data; maxforge is not required to keep the compiled file open.

CLI reference

CommandPurpose
compile input.maxdsl -o output.maxpatCompile DSL to a Max patch.
compile input.maxdsl --clipboardEmit compressed patch text pasteable into Max.
validate input.maxdslParse and validate without writing output.
decompile patch.maxpat -o patch.maxdslRecover structural DSL and explicit positions.
from-clipboard -o patch.maxdslRead compressed patch text from stdin and emit DSL.
plan desired.maxdsl --scope voices -o plan.jsonCreate an ordered managed PatchPlan.
doctor --input input.maxdslValidate project catalogs and derive abstraction ports.
catalog cycle~ --jsonSearch effective project and built-in object metadata.
bundle input.maxdsl -o packageBuild a Max package directory with declared custom dependencies.

Unknown objects fail by default because maxforge cannot safely infer their port shape. Use --allow-unknown only when you accept representative 1-inlet/1-outlet metadata and no upper-bound port rejection; Max must validate the real external.

Core DSL syntax

Patch metadata

patch "Title"
patch "Title" | "Description" | 800x600

Patch metadata is optional and maps to the real Max root patcher title, description, and rect fields. Without a declaration, maxforge stores no explicit title, an empty description, and a 640x480 rectangle; Max may use the saved filename as its visible window title. Quoted metadata uses JSON escapes and may contain |.

Objects

name = type [arguments...] [@attribute value...] [at(x, y[, width, height])]
  • name is unique inside its patcher and becomes a stable generated box ID.
  • The catalog contains 320 Max names/aliases with local identity evidence plus the project-owned maxforge.sync external.
  • Attributes become Max box properties; structural keys such as id, maxclass, text, and patching_rect are reserved.
  • Write \@attribute when the literal @attribute must remain inside a newobj text string.
  • Omit at(x, y) for automatic topological layout.
  • Use at(x, y, width, height) when a resized box must round-trip exactly.
osc = cycle~ 440
freq = number @minimum 0 @maximum 127
label = comment "Frequency"
trigger = message "start"
filter = lores~ 1000 0.5 at(240, 180)
Comments

Lines beginning with # are comments. Inline comments after a statement are not supported.

Catalog evidence and limits

The 321 catalog entries are compiler metadata, not a complete Max Object Reference: 239 use a fixed base shape, 45 use explicit argument rules, and 37 are marked dynamic. Dynamic examples include poly~, bpatcher, gen~, and jit.gl.slab; their stored port counts are representative.

  • Identity evidence comes from Max 9 reference XML, object indexes/mappings, and saved patchers.
  • Embedded Gen, Jitter Gen, and RNBO operators are excluded; they are not ordinary Max patcher boxes.
  • defaultSize and category are maxforge layout/grouping values, not Cycling '74 facts.
  • Use python3 scripts/audit-object-catalog.py in a checkout to repeat the local audit.

See the repository's catalog evidence and limits before adding names or relying on dynamic port metadata.

Project externals and abstractions

Declare third-party externals and reusable .maxpat abstractions in a strict maxforge.config.json. Inline DSL subpatchers remain embedded and need no configuration.

{
  "$schema": "https://2bit.jp/maxforge/schema/config-v1.json",
  "schemaVersion": 1,
  "project": {
    "id": "studio_patchset",
    "name": "Studio Patchset"
  },
  "objects": [{
    "name": "vendor.filter~",
    "kind": "external",
    "ports": {
      "mode": "fixed",
      "inlets": 2,
      "outlets": ["signal", ""]
    }
  }],
  "abstractions": [{
    "name": "studio.voice",
    "path": "./patchers/studio.voice.maxpat",
    "ports": "derive"
  }]
}
  • compile, validate, plan, and bundle search upward from the input DSL; --config selects a file explicitly.
  • doctor validates root/imported catalogs and reads referenced abstraction metadata before compilation.
  • catalog [query] --json lists project metadata or searches built-ins when a query is supplied.
  • Fixed ports are exact. Bounded argument mode derives counts from one integer argument or argument count. Dynamic ports require a representative shape but do not impose a false upper connection bound.
  • ports: "derive" counts root inlet/outlet boxes and infers signal outlets from incoming source metadata.
  • An abstraction name must match its existing .maxpat filename. Normal compilation validates but does not embed it, so Max still needs its directory on the search path. bundle copies referenced declared dependencies instead.
  • Imported catalogs cannot recursively import more catalogs. Accidental name collisions fail unless override: true is explicit.
  • project.id is the stable MCP persistence namespace. Imported catalogs cannot declare it, and unrelated projects must not share it.

Use the public project config schema and shared catalog schema. A valid catalog is compiler metadata only: it does not install an external, configure Max's search path, or prove runtime availability.

bundle writes the main patch under patchers/, follows declared abstraction dependencies, copies declared .mxo/.mxe64 artifacts under externals/, and writes package-info.json. Missing dependency paths and destination basename collisions are errors. Object arguments that refer to arbitrary media or data files are not inferred and must be packaged separately.

for, if, and arithmetic

Expansion runs before ordinary parsing. This is the feature intended for large families of similar Max objects.

voice_bank.maxdslMAXDSL
dac = ezdac~ at(430, 420)

for i in 0..7 {
  osc_${i} = cycle~ ${110 + i * 27.5} at(${40 + i * 110}, 80)
  amp_${i} = *~ 0.125 at(${40 + i * 110}, 140)
  osc_${i} -> amp_${i} -> dac

  if i < 4 {
    meter_${i} = meter~ at(${40 + i * 110}, 210)
    amp_${i} -> meter_${i}
  }
}
  • 0..7 is inclusive.
  • Use for i in 0..6 step 2 for a stepped range.
  • Expressions support loop variables, parentheses, comparisons, + - * / %, and ! && ||.
  • if supports an optional else block, including conventional } else { syntax.
  • Expansion rejects non-finite arithmetic and is capped at 100,000 loop iterations and output lines.
  • ${expr} works in names, arguments, attributes, positions, and connections.
  • Expressions are numeric only. There are no functions, arrays, strings, or modulo operator.

Connections and ports

a -> b -> c
source[1] -> destination[2]

Ports are zero-indexed from the left. A source suffix selects an outlet; a destination suffix selects an inlet. maxforge validates indices when it knows the object shape. It deliberately skips a fabricated upper bound for dynamicPorts and --allow-unknown objects; Max remains the final authority for those connections.

Objects such as gate, route, pack, unpack, trigger, matrix~, and selector~ derive port counts from their arguments.

Subpatchers

fx = p delay_fx {
  input = inlet signal "audio input"
  output = outlet signal "audio output"
  buffer = tapin~ 500
  tap = tapout~ 250
  feedback = *~ 0.4

  input -> buffer -> tap -> feedback -> buffer
  tap -> output
}

Subpatchers may be nested. Only the real Max objects inlet and outlet are emitted, and they are valid only inside a subpatcher. The maxforge-only signal modifier marks signal ports; the port count determines the parent object's visible shape.

Live MCP control

Live control requires three pieces: an MCP-capable agent, the Node.js maxforge-mcp server, and one native maxforge.sync object in each target patch. The npm package does not install the Max external.

mcp.jsonJSON
{
  "mcpServers": {
    "maxforge": {
      "command": "npx",
      "args": [
        "-y",
        "--package=maxforge@latest",
        "maxforge-mcp"
      ]
    }
  }
}

The stdio server listens for native clients on ws://127.0.0.1:8766 by default. Set MAXFORGE_WS_TOKEN to publish an authenticated bridge on 0.0.0.0, then use the same @token in Max. LAN transport is plaintext and is intended only for trusted networks.

If desired DSL uses a project external or abstraction, add an absolute MAXFORGE_CONFIG path to the server's env. After catalog edits, call maxforge_reload_catalog and verify the new digest with maxforge_catalog; restarting MCP is unnecessary. MCP intentionally does not discover configuration from its working directory.

Put a stable project.id in that config when managed state and ordered edit evidence must survive MCP restarts. Maxforge stores atomic state and a separate append-only NDJSON journal under ~/.maxforge/projects/<project.id>/. Without a project ID, persistent edit history is disabled rather than merged into a shared default cache.

Saved paths remain locators. When a path warning is ambiguous, inspect the append-only identity ledger instead of guessing. Explicit rekey, merge, and logical forget decisions affect historical lookup only; they never rewrite live Max routing, cross scopes, or claim physical erasure.

When the human explicitly requests deletion of retained edit evidence, close every Max client and use maxforge_erase_project_history with the exact project ID and confirmation phrase. It deletes maxforge-owned history chunks and the identity ledger and clears retained MCP observations. It does not delete Max/DSL/config files or desired-state cache, and it cannot guarantee secure overwrite on SSDs, backups, or filesystem snapshots.

Persistent history is single-writer. Startup creates writer-v1.lock and rejects a second maxforge-mcp process using the same history directory. Clean shutdown releases it. After a crash, verify the recorded PID is stopped before manual removal; automatic stale-lock deletion would race a new writer. This is exclusion, not multi-writer merging.

Do not apply blind

Use this sequence for every unfamiliar or ambiguous live session:

  1. 01
    Call maxforge_help with { "topic": "workflow" }.
  2. 02
    Call maxforge_status when process or connection state is uncertain.
  3. 03
    Before using a custom object, call maxforge_catalog and confirm the loaded definition.
  4. 04
    Call maxforge_list_patches; select an exact patcherId and scope.
  5. 05
    Call maxforge_inspect_patch. Titles are display metadata, not identities.
  6. 06
    When recent order matters, call maxforge_get_live_edit_history. Check supported, persistence, droppedEvents, session identity, and comparisonBasis; this is structural evidence, not undo history.
  7. 07
    If live edits exist, call maxforge_review_live_changes. Read related changes through review.editClusters, follow their changeIndexes to raw before/after values, and treat interpretationRisks as ambiguity prompts rather than proven human intent.
  8. 08
    Adopt an accepted current baseline with maxforge_adopt_live_changes and the exact reviewed token, or reconcile it with a concrete next complete DSL.
  9. 09
    After adoption, replace the working source with returned workingDsl. Otherwise call maxforge_compile_plan and review warnings, deletes, and structural recreation.
  10. 10
    Call maxforge_apply_dsl with the same target and DSL. Verify the revision and retain returned workingDsl; use manualChanges: "merge" only after successful reconciliation.
Desired state, not commands

desiredDsl is a complete desired-state declaration for the owned scope. Omitting an existing managed object requests its deletion; it is not an imperative “add these boxes” fragment.

Tool reference

ToolResponsibility
maxforge_helpAgent-facing workflow, setup, recovery, and safety instructions.
maxforge_statusServer, target, revision, baseline, and effective catalog state.
maxforge_catalogRead loaded built-in/custom object metadata and its deterministic digest; not a Max runtime probe.
maxforge_reload_catalogAtomically reload configured catalog files without restarting MCP or dropping Max registrations.
maxforge_list_patchesList exact registered patcherId/scope targets.
maxforge_create_patchCreate a new unsaved top-level patch through one controller-capable external.
maxforge_open_patchOpen an existing Max-host .maxpat, inject one bridge object, and wait for registration.
maxforge_save_patchSave or save-as explicitly; apply never saves automatically and overwrite is opt-in.
maxforge_close_patchClose a registered patch; dirty-state discard is explicit and opt-in.
maxforge_inspect_patchRead boxes, patch cords, nested paths, ownership, and changes without screenshots.
maxforge_get_live_edit_historyRead bounded, ordered 75 ms structural observations. Arrival time is not an edit timestamp; check drop and comparison metadata. This is not Max undo history or proof of intent.
maxforge_get_patch_history_identityInspect canonical project history identity, aliases, explicit decisions, and logical-forget state without requiring the old patch to be connected.
maxforge_resolve_patch_history_identityAfter human confirmation and source closure, append a rekey, merge, or logical-forget decision. It does not rewrite live routing or erase original evidence.
maxforge_erase_project_historyAfter explicit human confirmation and full Max disconnection, delete retained edit-history chunks and the identity ledger and clear their in-memory copy. Secure overwrite and deletion of project source/state files are out of scope.
maxforge_review_live_changesClassify structural evidence, correlate related changes into identity-bound edit clusters, expose interpretation risks and clarification candidates, and provide proposed working DSL when lossless.
maxforge_adopt_live_changesAccept a reviewed managed graph with an exact token, advance revision without replay, and return round-trip-checked workingDsl.
maxforge_reconcile_patchThree-way merge previous agent intent, live human edits, and next desired DSL without mutating Max.
maxforge_compile_planCompile desired DSL to a read-only ordered PatchPlan.
maxforge_apply_dslCompile, send, acknowledge, and capture a new inspection baseline.

Live edit history is bounded and collapses edits inside one debounce window. Project-scoped NDJSON survives MCP restart; reconnect creates a new session baseline instead of comparing across sessions. Saved paths are locators, not patch identities. Selection and gesture boundaries remain unavailable. Edit clusters are structural correlations, not recorded gestures or semantic conclusions. Follow each cluster's changeIndexes to the exact raw before/after changes. Use clarificationRecommendedFor to focus reasoning, but ask a human only when unresolved interpretations would change the next mutation.

maxforge.sync

The external owns WebSocket transport, stable patch registration, preflight validation, live structural inspection, and Max SDK mutation. It does not need node.script, thispatcher, a separate WebSocket object, routers, or bootstrap patch cords.

maxforge.sync @patcher_id main_patch @scope voices @controller 0
AttributeMeaning
@patcher_idStable MCP routing identity. Do not derive it from the window title.
@scopeExact managed ownership namespace.
@controller 0|1Whether this patch may create new top-level patches.
@hostMCP host; defaults to 127.0.0.1. A non-loopback host requires @token.
@portWebSocket port; defaults to 8766.
@tokenShared URL-safe LAN token matching MAXFORGE_WS_TOKEN.
@reconnectEnable or disable automatic reconnect.
@revision_statePersisted optimistic-concurrency revision.

Managed scripting names follow maxforge_<scope>_obj_<dsl-name>. The external must not modify boxes outside that exact namespace.

Set @controller 1 only on the single patch that may service maxforge_create_patch. Normal managed targets should leave it at 0; patch creation is rejected unless exactly one connected controller is available.

Ambiguous state requires inspection

SituationRequired action
Apply timed outDo not retry. Call maxforge_help with recovery, inspect the live patch, and determine whether Max applied the revision.
MCP process restartedCheck maxforge_status, restored state path/revision, then inspect. Exact currentDsl is required only when persistence was disabled or unavailable.
Max or the patch restartedWait for registration, then status → list → inspect before compiling another plan.
Managed manual edit detectedReview first. Adopt the accepted current baseline with its exact token and retain returned workingDsl, or reconcile with a concrete next DSL.
Unmanaged manual edit detectedReview it. Unmanaged-only edits are reported but do not block managed mutation.

Install operating instructions

Skills do not install the npm package or Max external. They give compatible agents repository-specific instructions.

npx skills add 2bbb/maxforge --list
npx skills add 2bbb/maxforge --skill maxforge
npx skills add 2bbb/maxforge --skill maxforge-mcp

Use maxforge for offline authoring and compilation. Use maxforge-mcp for target selection, plan review, acknowledgement, inspection, and recovery discipline.

Current limits and security boundary

  • Live synchronization is experimental protocol version 1.
  • Project catalog entries describe compiler serialization and ports. They do not prove that the external binary, abstraction search path, architecture, or dependencies exist on the Max host.
  • A plan is fully validated before mutation, but arbitrary Max SDK mutation has no implemented rollback after a runtime failure.
  • Inspection is structural. It does not serialize arbitrary object attributes, DSP values, UI values, patchline colors, hidden state, or line midpoints.
  • Reverse compilation preserves structure and explicit positions; it does not reproduce the exact original DSL source text.
  • The MCP bridge stays on loopback without a token. Setting MAXFORGE_WS_TOKEN enables authenticated trusted-LAN access; the WebSocket remains plaintext and must not be exposed directly to the Internet.
  • The ownership marker is the reserved scripting-name namespace. The native consumer treats such boxes as owned, but MCP reconciliation rejects newly introduced reserved identities because inspection lacks enough metadata for safe adoption.
  • Reconciliation preserves observed edits to existing managed identities. Arbitrary object attributes omitted from structural inspection cannot be merged.
  • Moving a managed box to another patcher path is not merged as a move; express the intended delete/add in complete DSL and resolve the conflict explicitly.
  • MCP apply plans carry the inspected structure token. If a box or cord changes before native mutation, maxforge.sync rejects the stale plan.
  • The macOS external is not notarized.

Detailed references

This page is the operational guide. Use the repository documents below for exact schemas and contracts.