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.
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
-
01
Download
maxforge.zip. -
02
Extract the top-level
maxforgedirectory into the Max packages directory. -
03
Restart Max, then create a
maxforge.syncobject or open its help patch.
~/Documents/Max 9/Packages/%USERPROFILE%\Documents\Max 9\Packages\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:
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
| Command | Purpose |
|---|---|
compile input.maxdsl -o output.maxpat | Compile DSL to a Max patch. |
compile input.maxdsl --clipboard | Emit compressed patch text pasteable into Max. |
validate input.maxdsl | Parse and validate without writing output. |
decompile patch.maxpat -o patch.maxdsl | Recover structural DSL and explicit positions. |
from-clipboard -o patch.maxdsl | Read compressed patch text from stdin and emit DSL. |
plan desired.maxdsl --scope voices -o plan.json | Create an ordered managed PatchPlan. |
doctor --input input.maxdsl | Validate project catalogs and derive abstraction ports. |
catalog cycle~ --json | Search effective project and built-in object metadata. |
bundle input.maxdsl -o package | Build 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])]
nameis 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.syncexternal. - Attributes become Max box properties; structural keys such as
id,maxclass,text, andpatching_rectare reserved. - Write
\@attributewhen the literal@attributemust remain inside anewobjtext 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)
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.
defaultSizeandcategoryare maxforge layout/grouping values, not Cycling '74 facts.- Use
python3 scripts/audit-object-catalog.pyin 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, andbundlesearch upward from the input DSL;--configselects a file explicitly.doctorvalidates root/imported catalogs and reads referenced abstraction metadata before compilation.catalog [query] --jsonlists 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 rootinlet/outletboxes and infers signal outlets from incoming source metadata.- An abstraction name must match its existing
.maxpatfilename. Normal compilation validates but does not embed it, so Max still needs its directory on the search path.bundlecopies referenced declared dependencies instead. - Imported catalogs cannot recursively import more catalogs. Accidental name collisions fail unless
override: trueis explicit. project.idis 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.
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..7is inclusive.- Use
for i in 0..6 step 2for a stepped range. - Expressions support loop variables, parentheses, comparisons,
+ - * / %, and! && ||. ifsupports an optionalelseblock, 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.
{
"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:
- 01Call
maxforge_helpwith{ "topic": "workflow" }. - 02Call
maxforge_statuswhen process or connection state is uncertain. - 03Before using a custom object, call
maxforge_catalogand confirm the loaded definition. - 04Call
maxforge_list_patches; select an exactpatcherIdandscope. - 05Call
maxforge_inspect_patch. Titles are display metadata, not identities. - 06When recent order matters, call
maxforge_get_live_edit_history. Checksupported,persistence,droppedEvents, session identity, andcomparisonBasis; this is structural evidence, not undo history. - 07If live edits exist, call
maxforge_review_live_changes. Read related changes throughreview.editClusters, follow theirchangeIndexesto raw before/after values, and treatinterpretationRisksas ambiguity prompts rather than proven human intent. - 08Adopt an accepted current baseline with
maxforge_adopt_live_changesand the exact reviewed token, or reconcile it with a concrete next complete DSL. - 09After adoption, replace the working source with returned
workingDsl. Otherwise callmaxforge_compile_planand review warnings, deletes, and structural recreation. - 10Call
maxforge_apply_dslwith the same target and DSL. Verify the revision and retain returnedworkingDsl; usemanualChanges: "merge"only after successful reconciliation.
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
| Tool | Responsibility |
|---|---|
maxforge_help | Agent-facing workflow, setup, recovery, and safety instructions. |
maxforge_status | Server, target, revision, baseline, and effective catalog state. |
maxforge_catalog | Read loaded built-in/custom object metadata and its deterministic digest; not a Max runtime probe. |
maxforge_reload_catalog | Atomically reload configured catalog files without restarting MCP or dropping Max registrations. |
maxforge_list_patches | List exact registered patcherId/scope targets. |
maxforge_create_patch | Create a new unsaved top-level patch through one controller-capable external. |
maxforge_open_patch | Open an existing Max-host .maxpat, inject one bridge object, and wait for registration. |
maxforge_save_patch | Save or save-as explicitly; apply never saves automatically and overwrite is opt-in. |
maxforge_close_patch | Close a registered patch; dirty-state discard is explicit and opt-in. |
maxforge_inspect_patch | Read boxes, patch cords, nested paths, ownership, and changes without screenshots. |
maxforge_get_live_edit_history | Read 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_identity | Inspect canonical project history identity, aliases, explicit decisions, and logical-forget state without requiring the old patch to be connected. |
maxforge_resolve_patch_history_identity | After 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_history | After 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_changes | Classify 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_changes | Accept a reviewed managed graph with an exact token, advance revision without replay, and return round-trip-checked workingDsl. |
maxforge_reconcile_patch | Three-way merge previous agent intent, live human edits, and next desired DSL without mutating Max. |
maxforge_compile_plan | Compile desired DSL to a read-only ordered PatchPlan. |
maxforge_apply_dsl | Compile, 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
| Attribute | Meaning |
|---|---|
@patcher_id | Stable MCP routing identity. Do not derive it from the window title. |
@scope | Exact managed ownership namespace. |
@controller 0|1 | Whether this patch may create new top-level patches. |
@host | MCP host; defaults to 127.0.0.1. A non-loopback host requires @token. |
@port | WebSocket port; defaults to 8766. |
@token | Shared URL-safe LAN token matching MAXFORGE_WS_TOKEN. |
@reconnect | Enable or disable automatic reconnect. |
@revision_state | Persisted 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
| Situation | Required action |
|---|---|
| Apply timed out | Do not retry. Call maxforge_help with recovery, inspect the live patch, and determine whether Max applied the revision. |
| MCP process restarted | Check maxforge_status, restored state path/revision, then inspect. Exact currentDsl is required only when persistence was disabled or unavailable. |
| Max or the patch restarted | Wait for registration, then status → list → inspect before compiling another plan. |
| Managed manual edit detected | Review first. Adopt the accepted current baseline with its exact token and retain returned workingDsl, or reconcile with a concrete next DSL. |
| Unmanaged manual edit detected | Review 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_TOKENenables 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.syncrejects 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.