Skip to content

v0.6.0

Choose a tag to compare

@buttjer buttjer released this 23 Jul 14:17
· 180 commits to main since this release
7995d22

Added

  • backup — git-native, redeployable disaster recovery. n8n-decanter backup create <workflow> captures the workflow's full REST export into a
    committed, versioned workflows/<slug>/backups/<timestamp>.<versionId>.json
    store — the fidelity MCP can't give (credential refs + description kept;
    pinData/staticData stripped; each Code node's jsCode stays a //@file:
    placeholder, so no code is duplicated). It dedupes on an unchanged
    versionId and rolling-prunes the working set to backupLimit (config,
    default 20; 0 keeps all). backup restore <workflow> [--version <id> | --at <ts>] re-inlines the Code from code/ 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: needs N8N_API_KEY. The backup file is
    not auto-committed (it carries credential refs and any embedded
    secrets) — review it, then git add deliberately.
  • Live workflow.json mirror — the review snapshot refreshes itself after
    an agent restructures a workflow through the guard.
    When a structure edit
    is forwarded through mcp connect / mcp serve (a non-blocked
    update_workflow), decanter now schedules a debounced background pull of
    that workflow, so the read-only workflow.json (+ code files + state) stays
    fresh with no manual pull. On by default; set "liveMirror": false in
    decanter.config.json to 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
    changes mcp connect/serve default 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 when N8N_API_KEY is set (--no-fetch opts
    out) and reading production run health (history, via MCP
    search_executions or 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=warn promotes a caution to exit 1; --fail-fast
    stops at the first failure. --json emits the full report (stable check
    ids + remediation strings — the agent contract). preflight never
    mutates
    in any profile: no push, publish, restore, or draft write —
    test runs in a never-mutate mode and simulate headless with
    --network-none forced on. The single gate to run before push/publish.
  • test — instance-side pinned test runs (the recommended runtime
    check).
    n8n-decanter test <workflow> runs the workflow on your
    instance via MCP test_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; --trigger picks the start node,
    --json emits the report). The run targets the draft — the live
    version is never affected. On a terminal, when local code differs from
    the draft, test offers 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. simulate stays the offline sibling —
    pre-push/CI/isolation/version-rehearsal — and its docs now recommend
    test first.
  • mcp connect — the stdio MCP guard, auto-wired by init. The default
    way a coding agent reaches your instance's MCP server: the scaffolded
    .mcp.json (and opencode.json) carry a static, secret-free
    n8n-instance entry ({"command":"n8n-decanter","args":["mcp","connect"]}),
    so guarded instance access exists the moment init runs — nothing to
    start, no secret to manage (stdio pipes are private). Decanter holds the
    credentials; the same guard rule as mcp serve applies (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 + push flow. 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_workflow calls that write Code-node source, via either
    a jsCode key or a setNodeParameter op whose path targets jsCode,
    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.mjs session hook that nudges agents whose MCP config
    still points at the instance directly, and the sync-dir AGENTS.md
    contract is now guard-first.

Removed

  • Breaking: the structure/lifecycle verbs are gone — rename, create,
    node create (and its --ts flag), and node rename.
    Those acts go
    through n8n itself: the n8n editor, or n8n's MCP tools reached through
    the new mcp connect/mcp serve guard (which is exactly what the
    official n8n skills drive). Decanter's job is the reconcile: the next
    pull re-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 no jsCode (the guard blocks code in
    addNode) — it now lands as an empty file whose first push seeds
    the source, completing the guarded authoring loop. Two behaviors did not
    survive the removal: $('…') refs inside local .ts sources 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_workflow first, as the n8n skills teach).
  • Breaking: the delete verb 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 duplicate verb 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 and pull the copy.
  • Breaking: watch's browser-reload proxy is gone — browserReload and
    proxyPort config 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). watch now just
    prints the editor deep link with a note to keep the tab open; it updates
    live on every push.
  • Breaking: simulate --pin and per-node fixtures/ are gone — folded into
    scenario.
    The per-node workflows/<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 now scenario create --execution <id>. A
    leftover fixtures/ dir is a hard error from simulate/check naming
    the replacement; there is no silent read-path or auto-migration for it
    (unlike a leftover mocks/ dir, which auto-migrates to scenarios/ on
    first touch — see the scenario namespace 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 watch plus
    a manual push sharing .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 raw SyntaxError;
    body-read timeouts use the friendly timeout message; a rate-limit
    Retry-After is 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.
  • init appends .decanter-auth.json to a pre-existing .gitignore
    instead of only warning — the file holds the MCP refresh token.
  • Push verifies .ts nodes 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

  • pull with 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 in decanter.config.json; picking a not-yet-local workflow
    pulls it fresh. Previously its no-ref picker showed only already-pulled
    workflows, so a first-ever pull errored with no workflow ids. Piped /
    non-interactive runs are unchanged (they pull the config workflows set).

  • The scaffolded MCP config is rebuilt around the guard + n8n's official
    docs MCP.
    init's .mcp.json (and opencode.json) now wire two
    servers: n8n-instance — the mcp connect guard (see Added) — and
    n8n-docs, n8n's first-party read-only docs MCP
    (https://docs.n8n.io/~gitbook/mcp, public, no auth), replacing the
    community n8n-mcp server. 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-docs plus the offline/read verbs pull, scenario, and
    simulate; instance-mutating verbs still prompt.

  • A body-equal push now re-registers a missing @ts-n8n marker — when a
    .ts node'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 .ts node back to .js is now supported symmetrically:
    replace the file, re-point its //@file: placeholder, and push — the
    push clears the remote @ts-n8n marker 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 create strips the capture's embedded workflowData — 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 (their pythonCode stays 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 .cursor rules, and .env.example is OAuth-first (MCP
    credentials primary, the API key optional with a minimal scope list).

  • N8N_API_KEY now powers only executions and data-tables — the last
    lifecycle verbs left the REST API, so the recommended key scopes shrink to
    workflow:list, execution:read, execution:list, and the dataTable:*
    read scopes (template/.env.example was 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/unpublish ride
    POST /mcp-server/http instead of the public REST API. What that means in
    practice:

    • Pushes are draft-first. push writes only each Code node's jsCode
      (an atomic update_workflow batch with merge semantics) to the workflow's
      draft; the live version never changes until an explicit publish — or
      the new push --publish, which combines the two. The API-era
      "auto-publish on push to an active workflow" behavior is gone.
    • workflow.json is 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 (--force still overrides it), and remote structure
      changes never block a push (status prints 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 next pull makes 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 --remote marks them (--json adds mcpAvailable)
      and pull/push errors carry the same guidance.
    • The public API key becomes optional. Only the surfaces MCP cannot
      serve still use it: executions and data-tables fetches. The client
      retries n8n's MCP rate limiting (429) with backoff automatically.
  • Breaking: init is OAuth-first. init now 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 scenario namespace — 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
    scenario workflows/<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; --scaffold calls n8n's
    read-only MCP tool prepare_test_pin_data and annotates every gap with its
    output JSON Schema (no data — the tool is a schema oracle only); the two
    compose, and a bare --scaffold with no capture builds a from-scratch set
    where every pinnable node is a fill entry. You (or your IDE agent) add the
    nodes' runDatano API key, the CLI never calls a model or invents
    values
    — and replay it with simulate --scenario <slug> /
    test --scenario <slug>. Each node's pins carry a provenance
    (capture/authored/scaffolded); a run on a scenario with any
    non-capture node is labeled "synthetic pins — proves executability, not
    output correctness
    " (no per-node diff asserted; --json reports 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 --scenario run 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 create warns about PII and
    refuses to overwrite an existing scenario. A mocks/ dir from an earlier
    unreleased build auto-migrates to scenarios/ on first touch.
  • simulate previews 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-none runs (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 the pull verb 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.