v0.6.0
Added
backup— git-native, redeployable disaster recovery.n8n-decanter backup create <workflow>captures the workflow's full REST export into a
committed, versionedworkflows/<slug>/backups/<timestamp>.<versionId>.json
store — the fidelity MCP can't give (credential refs +descriptionkept;
pinData/staticDatastripped; each Code node'sjsCodestays a//@file:
placeholder, so no code is duplicated). It dedupes on an unchanged
versionIdand rolling-prunes the working set tobackupLimit(config,
default 20;0keeps all).backup restore <workflow> [--version <id> | --at <ts>]re-inlines the Code fromcode/and REST-POSTs a new,
unpublished workflow with node ids preserved — a real second version
history that survives the instance being lost; it prints credential-rebind
hints + the editor URL (publish is your next step).backup list <workflow>
shows the retained set. REST-only: needsN8N_API_KEY. The backup file is
not auto-committed (it carries credential refs and any embedded
secrets) — review it, thengit adddeliberately.- Live
workflow.jsonmirror — the review snapshot refreshes itself after
an agent restructures a workflow through the guard. When a structure edit
is forwarded throughmcp connect/mcp serve(a non-blocked
update_workflow), decanter now schedules a debounced backgroundpullof
that workflow, so the read-onlyworkflow.json(+ code files + state) stays
fresh with no manualpull. On by default; set"liveMirror": falsein
decanter.config.jsonto disable (CI / deterministic setups). It is
fire-and-forget (never blocks the agent's next tool call), git-gated
(safety-commits before pulling; skips with no git), and tracked-only. This
changesmcp connect/servedefault behavior (additive and disable-able —
not breaking). preflight— the whole verification ladder as one scored, read-only
gate.n8n-decanter preflight [workflow…]runs every safe check there
is — local static (layout,types) → instance read-only (connect,
access,parity,drift,snapshot,lifecycle,history,
capture) → pinned draft runs (test,simulate) — ordered fast→slow,
streaming each result, and condenses them into a score (0–100) and a
verdict (ready/caution/not ready, exit 0/1) with per-check
remediation. Profiles are explicit and deterministic:--quick(static +
sync), default (+test),--full(+simulate),--offline(static +
simulate, no instance). It brings executions into the gate — auto-
fetching the newest capture whenN8N_API_KEYis set (--no-fetchopts
out) and reading production run health (history, via MCP
search_executionsor the REST fallback). Coverage is first-class: every
skip names its unlock, and--require=<ids>turns a skipped check into a
hard fail;--fail-on=warnpromotes a caution to exit 1;--fail-fast
stops at the first failure.--jsonemits the full report (stable check
ids + remediation strings — the agent contract).preflightnever
mutates in any profile: no push, publish, restore, or draft write —
testruns in a never-mutate mode andsimulateheadless with
--network-noneforced on. The single gate to run beforepush/publish.test— instance-side pinned test runs (the recommended runtime
check).n8n-decanter test <workflow>runs the workflow on your
instance via MCPtest_workflow: the trigger and network/credentialed
nodes are pinned from a capture (--execution, default newest) or a
committed scenario (--scenario), logic nodes execute for real on the
instance-exact engine, and each node's output is diffed against the
capture (exit 1 on divergence;--triggerpicks the start node,
--jsonemits the report). The run targets the draft — the live
version is never affected. On a terminal, when local code differs from
the draft,testoffers to push it first (drift-guarded, draft-only)
and afterwards to keep or restore the pre-test draft (n8n version
history when available, byte-exact write-back below n8n 2.29);
non-interactive runs never mutate and say when they tested the draft
instead of local code.simulatestays the offline sibling —
pre-push/CI/isolation/version-rehearsal — and its docs now recommend
testfirst.mcp connect— the stdio MCP guard, auto-wired byinit. The default
way a coding agent reaches your instance's MCP server: the scaffolded
.mcp.json(andopencode.json) carry a static, secret-free
n8n-instanceentry ({"command":"n8n-decanter","args":["mcp","connect"]}),
so guarded instance access exists the momentinitruns — nothing to
start, no secret to manage (stdio pipes are private). Decanter holds the
credentials; the same guard rule asmcp serveapplies (see below).
Structure and lifecycle acts — creating/renaming/archiving workflows,
adding/renaming/wiring nodes — pass through; Code-node (jsCode) writes
are blocked toward the file +pushflow. Fail-closed on unparseable
input; an unreachable instance answers the agent with a JSON-RPC error
naming the host; logs go to stderr (stdout is protocol-only).mcp serve— the same guard as a localhost HTTP proxy, for agents
configured by URL: decanter holds the credentials (the
agent gets a per-session secret instead), every read and structure
operation forwards untouched (SSE included), and exactly one thing is
blocked —update_workflowcalls that write Code-node source, via either
ajsCodekey or asetNodeParameterop whose path targetsjsCode,
which get an instructive "edit the file + push" tool error. Fail-closed on unparseable
bodies, 127.0.0.1-only, body-size cap; the running endpoint + secret land
in a gitignored.decanter-proxy.json. The template gains a
mcp-route-check.mjssession hook that nudges agents whose MCP config
still points at the instance directly, and the sync-dirAGENTS.md
contract is now guard-first.
Removed
- Breaking: the structure/lifecycle verbs are gone —
rename,create,
node create(and its--tsflag), andnode rename. Those acts go
through n8n itself: the n8n editor, or n8n's MCP tools reached through
the newmcp connect/mcp serveguard (which is exactly what the
official n8n skills drive). Decanter's job is the reconcile: the next
pullre-caches a renamed workflow's name (folder stays put), renames a
renamed node's local file, and lands a new Code node as a source file. A
Code node added over MCP carries nojsCode(the guard blocks code in
addNode) — it now lands as an empty file whose firstpushseeds
the source, completing the guarded authoring loop. Two behaviors did not
survive the removal:$('…')refs inside local.tssources are no
longer rewritten on a node rename (n8n never sees.ts— update them by
hand after the pull), and validate-before-create is now the calling
agent's discipline (validate_workflowfirst, as the n8n skills teach). - Breaking: the
deleteverb is gone. Decanter no longer offers a hard
delete; retiring a workflow is an n8n act (archive it over MCP or in the
UI — reversible there, which is also where permanent deletion lives). - Breaking: the
duplicateverb is gone. MCP has no lossless full-JSON
create, so a faithful clone required the public API — rather than keep the
API dependency or ship a lossy SDK-code re-expression, the verb was
dropped. Duplicate workflows from the n8n UI andpullthe copy. - Breaking:
watch's browser-reload proxy is gone —browserReloadand
proxyPortconfig keys are no longer honored (silently ignored, not an
error). n8n 2.x reflects an MCP draft edit in the open editor natively
(soft canvas re-render, skipped — with a warning — while the tab has
unsaved edits), making decanter's injected<script>-reload proxy
redundant and, on that exact dirty-tab path, worse than doing nothing (a
hard reload would have clobbered the unsaved edits).watchnow just
prints the editor deep link with a note to keep the tab open; it updates
live on every push. - Breaking:
simulate --pinand per-nodefixtures/are gone — folded into
scenario. The per-nodeworkflows/<folder>/fixtures/<node>.json
mechanism and its precedence over captures are removed outright; a scenario
is now the only committed pin artifact and is always self-contained (no
fixture-over-capture layering to reason about).--pin's job — "make a
clean capture reproducible" — is nowscenario create --execution <id>. A
leftoverfixtures/dir is a hard error fromsimulate/checknaming
the replacement; there is no silent read-path or auto-migration for it
(unlike a leftovermocks/dir, which auto-migrates toscenarios/on
first touch — see thescenarionamespace under Added).
Fixed
- Verb-first error hints. Several CLI error/guidance messages suggested
verb-last commands (n8n-decanter <ref> simulate …,
n8n-decanter <ref> executions,n8n-decanter <ref> scenario …) that the
verb-first grammar rejects when copy-pasted; every one now prints the
verb-first form (n8n-decanter simulate <workflow> …,
n8n-decanter executions <workflow>,n8n-decanter scenario … <workflow>). - Refresh-token race (OAuth): two concurrent MCP calls — or
watchplus
a manualpushsharing.decanter-auth.json— could both redeem the
single-use refresh token, killing the session for the loser ("re-run
init"). Concurrent calls now share one redemption, a lost cross-process
race recovers by re-reading the winner's rotated auth file, and auth-file
writes are atomic. - MCP client hardening: a transient handshake failure no longer poisons
every later call in the same run; a 200-with-HTML answer (captive
portal/reverse proxy) gets a named error instead of a rawSyntaxError;
body-read timeouts use the friendly timeout message; a rate-limit
Retry-Afteris honored up to n8n's verified 5-minute window (with a
visible "waiting Ns" warning) and capped there against bogus-huge
headers; a dropped MCP session (404 with a session id) re-handshakes once
transparently; a token-refresh response without a
rotated refresh token keeps the old one; workflow lists that hit the
200-row page cap warn about truncation. initappends.decanter-auth.jsonto a pre-existing.gitignore
instead of only warning — the file holds the MCP refresh token.- Push verifies
.tsnodes after the write (marker hash vs. remote
body — catches server-side normalization), and watch's single-node pushes
run the same post-push verification as full pushes.
Changed
-
pullwith no argument now opens the picker on a fresh setup. On a
terminal,n8n-decanter pull(no ref) lists your workflows — local and
remote (over MCP) — so you can pick one to pull without knowing its id or
pre-listing it indecanter.config.json; picking a not-yet-local workflow
pulls it fresh. Previously its no-ref picker showed only already-pulled
workflows, so a first-everpullerrored withno workflow ids. Piped /
non-interactive runs are unchanged (they pull the configworkflowsset). -
The scaffolded MCP config is rebuilt around the guard + n8n's official
docs MCP.init's.mcp.json(andopencode.json) now wire two
servers:n8n-instance— themcp connectguard (see Added) — and
n8n-docs, n8n's first-party read-only docs MCP
(https://docs.n8n.io/~gitbook/mcp, public, no auth), replacing the
communityn8n-mcpserver. The docs server can't reach your instance, so
it can't bypass the guard — live workflow access goes only through
n8n-instance. The scaffolded Claude Code allowlist pre-approves
mcp__n8n-docsplus the offline/read verbspull,scenario, and
simulate; instance-mutating verbs still prompt. -
A body-equal push now re-registers a missing
@ts-n8nmarker — when a
.tsnode's compiled code already matches the remote but the marker is
gone (e.g. rewritten in the UI), push writes the node anyway so it is
recognized as TS-managed again (previously skipped as "in sync"). -
Converting a
.tsnode back to.jsis now supported symmetrically:
replace the file, re-point its//@file:placeholder, and push — the
push clears the remote@ts-n8nmarker even when the code is otherwise
identical, so the node stops being TS-managed (previously the stale
marker made the next pull resurrect the node as.ts). -
scenario createstrips the capture's embeddedworkflowData— committed
scenarios no longer duplicate every Code node's source in git; the compliance
guard warns about legacy scenarios that still embed it, and it now also flags
Python Code nodes honestly (theirpythonCodestays inline in
workflow.json; extraction is a planned feature). -
Template refresh (from the MCP pivot): the sync-dir
AGENTS.md
contract was rewritten around the MCP boundary (Code-node source = files +
decanter push; structure = n8n/MCP; knowledge skills recommended) with
matching.cursorrules, and.env.exampleis OAuth-first (MCP
credentials primary, the API key optional with a minimal scope list). -
N8N_API_KEYnow powers onlyexecutionsanddata-tables— the last
lifecycle verbs left the REST API, so the recommended key scopes shrink to
workflow:list,execution:read,execution:list, and thedataTable:*
read scopes (template/.env.examplewas rewritten OAuth-first to match). -
Breaking: the workflow code path now syncs over n8n's built-in MCP server —
decanter is the Code-node code layer, n8n owns structure (Plan 32).
pull/push/watch/status/publish/unpublishride
POST /mcp-server/httpinstead of the public REST API. What that means in
practice:- Pushes are draft-first.
pushwrites only each Code node'sjsCode
(an atomicupdate_workflowbatch with merge semantics) to the workflow's
draft; the live version never changes until an explicitpublish— or
the newpush --publish, which combines the two. The API-era
"auto-publish on push to an active workflow" behavior is gone. workflow.jsonis now a read-only structure snapshot. Pull refreshes
it for review diffs and the offline tooling; nothing pushes it. The
whole-workflow structural hashing, the structural drift guard, watch's
structural-conflict prompt (workflow.remote.json), and the.remote.js
conflict artifacts are all gone — the only drift guard left is the
per-node code check (--forcestill overrides it), and remote structure
changes never block a push (statusprints a snapshot-stale hint instead).- Structure acts live in n8n. Renames, new nodes, wiring, and new
workflows happen in the n8n editor or over n8n's MCP tools (through the
guard) — n8n rewrites connections and$('…')references server-side,
node ids stay stable, and the nextpullmakes local files follow. - Requires n8n ≥ ~2.20 with MCP access enabled, plus a per-workflow
"Available in MCP" opt-in. The picker shows MCP-unavailable workflows as a
third state (red⊘, sorted last) with enable guidance instead of a
failing pull;list --remotemarks them (--jsonaddsmcpAvailable)
and pull/push errors carry the same guidance. - The public API key becomes optional. Only the surfaces MCP cannot
serve still use it:executionsanddata-tablesfetches. The client
retries n8n's MCP rate limiting (429) with backoff automatically.
- Pushes are draft-first.
-
Breaking:
initis OAuth-first.initnow connects to the instance via
the standard MCP OAuth flow — browser consent, then a refresh token stored
in a new gitignored.decanter-auth.json(rotated on every refresh) —
with a paste-a-token fallback (N8N_MCP_TOKEN, minted in n8n → Settings →
MCP) for piped/headless runs. The public API key prompt is now optional.
Added
- New
scenarionamespace — named, committed pin-data sets, captured and/or
schema-scaffolded. A gap (a network node reached in the replay with no
pinned data) used to be a dead end.scenario create <workflow> ["<slug>"] [--execution <id>] [--scaffold]writes a tracked, self-contained
scenarioworkflows/<folder>/scenarios/<slug>.json(slug defaults to the
execution id) and flags which nodes to fill:--execution <id>promotes a
gitignored capture and flags each remaining gap;--scaffoldcalls n8n's
read-only MCP toolprepare_test_pin_dataand annotates every gap with its
output JSON Schema (no data — the tool is a schema oracle only); the two
compose, and a bare--scaffoldwith no capture builds a from-scratch set
where every pinnable node is a fill entry. You (or your IDE agent) add the
nodes'runData— no API key, the CLI never calls a model or invents
values — and replay it withsimulate --scenario <slug>/
test --scenario <slug>. Each node's pins carry a provenance
(capture/authored/scaffolded); a run on a scenario with any
non-capturenode is labeled "synthetic pins — proves executability, not
output correctness" (no per-node diff asserted;--jsonreports gain
syntheticPins/provenance), while a capture-only scenario keeps full
per-node diff and exit-1-on-divergence semantics.scenario check <workflow> ["<slug>"]structurally validates a scenario (or all of them)
offline — no Docker — with a node-named error if an item is malformed or
a flagged node is left empty;simulate --scenario/test --scenariorun the
same check on load. n8n publishes no execution-data JSON Schema, so decanter
checks the exact shape it replays. Committed → scenario-based replays are
reproducible for teammates and CI;scenario createwarns about PII and
refuses to overwrite an existing scenario. Amocks/dir from an earlier
unreleased build auto-migrates toscenarios/on first touch. simulatepreviews multi-batch loops in the viewer. In an interactive
terminal, a genuine multi-batch loop (previously a hard error) now caps the
loop to its first batch and opens that single iteration in the browsable
viewer, clearly labeled "iteration 1 of N — not a pass/fail check." Headless
/--json/--network-noneruns (scripts, CI) still hard-error, so an exit
code is never mistaken for a verified pass.
Fixed
- Value-taking flags no longer swallow a following verb. Writing a
value flag in its space-separated form without a value — e.g.
n8n-decanter --status pull— used to consume thepullverb as the
flag's value and then fail with a confusing "no verb" error. Such flags
(--status,--limit,--execution,--n8n-version,--scenario,
--filter,--search,--sort) now refuse to eat a known verb and report
--status needs a value (e.g. --status=success)instead.