You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* fix(partial-workflow): strip unknown node properties echoed by n8n GET
n8n's GET /workflows/{id} returns node properties that its
PUT/PATCH schema rejects (e.g. server-managed fields like `issues`,
`runIndex`, and previously-missing fields like `onError` and
`webhookId`).
- Add `onError` and `webhookId` to `workflowNodeSchema`
- Introduce `cleanNodeForApi()` to filter each node through an
allow-list of API-safe properties
- Apply filtering in both `cleanWorkflowForCreate` and
`cleanWorkflowForUpdate`
- Add tests covering the stripping behaviour and the new function
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
(cherry picked from commit 3638beb)
* Verify rollback against the server before reporting it as failed
A rollback PUT can persist and then throw. n8n's public API commits workflow
content before it checks publish permission, so a caller allowed to edit but not
to publish receives an error on a write that landed. The catch block then
recorded rollbackPerformed: false and warned that the workflow may be in a broken
state, when the content had in fact been restored. That invites a riskier
recovery action than doing nothing.
After the rollback PUT throws, re-read the workflow and compare the fields an
update actually sends, via cleanWorkflowForUpdate. Version identity cannot settle
it: a successful rollback writes a new version, so compareVersions() reports
'changed' either way.
(cherry picked from commit eb41626)
* Use deep equality on the update allowlist and ignore generated webhook ids
cleanWorkflowForUpdate() assigns a random webhookId to webhook nodes that lack
one, and does so in place. Comparing its output directly mutated both reads and
gave each a different id, so any workflow containing a webhook node could never
compare equal to itself and the verification silently never succeeded.
Clone before cleaning, drop the generated field, and compare with
isDeepStrictEqual instead of a hand-written serialiser. Tests now model an actual
reverted change rather than workflows that were identical throughout, and cover
the unrestored and webhook cases.
(cherry picked from commit 8e0b2a9)
* Compare webhook ids the workflow already carried
Only the ids the update allowlist generates for nodes that lack one are unstable between reads, so drop just those. A webhookId already present is real content, and a change to one means the prior state was not restored.
(cherry picked from commit d9ba578)
* test: cover GET→UPDATE workflow round-trips and n8n API quirks (#433)
Add unit and live-integration coverage for the common spread-from-GET
update pattern that let description/read-only fields slip through (#431).
- Unit: full GET-shaped payload cleaning, minimal payload, empty/unknown settings
- Integration: GET→UPDATE, spread rename, nested settings, description strip,
missing settings defaults, read-only field echo, minimal updates, settings filter
- Document n8n read/write asymmetry in tests/integration/n8n-api/README.md
(cherry picked from commit 46cd1c8)
* fix: stale hardcoded version in health check, deprecated API, typos
- healthCheck() was returning hardcoded version '2.24.1' instead of the
actual package version. imported PROJECT_VERSION and used that instead.
- res.finished was deprecated in Node 13, changed to res.writableEnded
- fixed "paralel" → "parallel" and "Concieved" → "Conceived" in CLAUDE.md
- replaced dead issue link (issues/XXX) with inline description
- removed unnecessary (this as any) cast in workflow-validator since
currentWorkflow is already properly typed on the class
(cherry picked from commit 63f3542)
* fix: retry a workflow write without the settings an n8n instance rejects as unknown
Settings are forwarded on purpose (the table trails n8n's releases), and n8n answers an
unknown key with a 400 that names the path but not the key. Instead of failing the whole
write, the client now retries without candidates in order: keys absent from the settings
table together, then known keys newest first, and reports what it left out through
onWarning. A rejected key is remembered for the client's lifetime. This replaces marking
timeSavedMode as derived (#1017): n8n 2.36 accepts and echoes it on PUT, so stripping it
would drop a real setting on current instances.
Also gives the round-trip tests from #925 their own describe block and rewrites the one
assertion that encoded the pre-2.70 settings allowlist.
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQmLW2QcubiwyuemrLs62d
* refactor: derive the node allowlist from the schema and tidy the settings ladder
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQmLW2QcubiwyuemrLs62d
* fix: remember only single-key settings rejections; guard node allowlist drift
Review round on the write-path branch:
- a batch of unknown settings keys dropped together is no longer remembered, since only
one of them may be the culprit; single-key steps still are
- the rollback comparison ignores generated webhook ids on both reads, so it no longer
depends on cleanWorkflowForUpdate mutating the snapshot in place; id-less nodes guarded
- customTelemetryTags added to the node schema (n8n 2.36's node schema lists it, so the
allowlist would have stripped it), and check:settings-drift now compares the node schema
against WRITABLE_NODE_PROPERTIES as well
- notes on rejectedSettings lifetime and the retry bound; small doc and test-name fixes
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQmLW2QcubiwyuemrLs62d
* fix: let a settings rejection on the group ladder's retry reach the settings ladder
Codex found that the confirmation retry without nodeGroups swallowed a settings-level 400
and rethrew the original groups error, so the settings ladder never ran. Also a health-check
version test and a README correction.
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQmLW2QcubiwyuemrLs62d
* test: pin that a batch of unknown settings keys is probed again; attribute readOnly to the right property
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQmLW2QcubiwyuemrLs62d
* chore: release 2.81.0
Conceived by Romuald Członkowski - www.aiadvisors.pl/en
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RQmLW2QcubiwyuemrLs62d
---------
Co-authored-by: blouf <blouf@blouf.org>
Co-authored-by: Ed St John <edward.stjohn@capitalontap.com>
Co-authored-by: Pitchfork-and-Torch <297513015+Pitchfork-and-Torch@users.noreply.github.com>
Co-authored-by: vitalii.semianchuk <fix20152@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+16Lines changed: 16 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
8
8
## [Unreleased]
9
9
10
+
## [2.81.0] - 2026-09-03
11
+
12
+
### Added
13
+
14
+
- **Workflow writes retry without the settings an n8n instance rejects as unknown.** Settings are forwarded untouched on purpose, because the bundled settings table trails n8n's releases and a property dropped up front is dropped silently. The cost was that a setting the instance's write schema does not accept failed the whole write with `request/body/settings must NOT have additional properties`, a message that never names the key, which is what users hit when a GET echoes a property such as `timeSavedMode` that their n8n version stores but does not accept on PUT ([#1017](https://github.com/czlonkowski/n8n-mcp/pull/1017)). The API client now retries without candidates in a fixed order: keys absent from the settings table first, together, then known keys from newest to oldest; keys that predate n8n 1.119.0 are never dropped, since instances that old still report their version and are filtered precisely. Each dropped key is reported in the tool response `warnings` and remembered for the client's lifetime so later writes skip the probe. `timeSavedMode` itself stays writable: n8n 2.36 accepts and echoes it, so marking it derived would have dropped a real setting on current instances.
15
+
16
+
### Fixed
17
+
18
+
-**Node properties that n8n echoes on GET but rejects on PUT are stripped before a write** ([#983](https://github.com/czlonkowski/n8n-mcp/pull/983)). n8n's node schema is `additionalProperties: false`, and a GET can carry `issues`, `runIndex` or `data`, so a round trip failed with `request/body/nodes/0 must NOT have additional properties`. `cleanWorkflowForCreate` and `cleanWorkflowForUpdate` now keep only the properties of the node schema; the allowlist is derived from that schema, so it cannot drift from it. `onError` and `webhookId` were missing from the schema and are accepted.
19
+
-**A rollback that n8n persisted before rejecting is no longer reported as failed** ([#979](https://github.com/czlonkowski/n8n-mcp/pull/979)). n8n's public API can commit workflow content and then throw on a later check, so `n8n_update_partial_workflow` could warn that a workflow was left broken when it had in fact been restored. After a rollback PUT throws, the handler re-reads the workflow and compares the writable fields with the pre-update snapshot; a match is reported as `rollbackVerifiedAfterError: true`. Webhook ids generated for the comparison are ignored; ids the workflow already carried are compared.
20
+
-**Engine health check reported a hard-coded version** ([#908](https://github.com/czlonkowski/n8n-mcp/pull/908)). `N8NMCPEngine.healthCheck()` returned `2.24.1` regardless of the installed version; it now reports the package version. The HTTP server's response logging reads `writableEnded` instead of the deprecated `finished`.
21
+
22
+
### Changed
23
+
24
+
- Round-trip tests for GET→UPDATE workflow writes ([#925](https://github.com/czlonkowski/n8n-mcp/pull/925), [#433](https://github.com/czlonkowski/n8n-mcp/issues/433)): unit tests for the cleaners and integration tests against a live instance for the spread-a-GET-into-an-update patterns the n8n API is particular about.
0 commit comments