From 99518f71cf5f4d82456740265ea48a17c5fc958a Mon Sep 17 00:00:00 2001 From: czlonkowski <56956555+czlonkowski@users.noreply.github.com> Date: Thu, 2 Oct 2025 16:33:11 +0200 Subject: [PATCH] fix(issue-248): use unconditional empty settings object for cloud API compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #248 required three iterations to solve due to n8n API version differences: 1. First attempt: Whitelist filtering - Failed: API rejects ANY settings properties via update endpoint 2. Second attempt: Complete settings removal - Failed: Cloud API requires settings property to exist 3. Final solution: Unconditional empty settings object - Success: Satisfies both API requirements Changes: - src/services/n8n-validation.ts:153 - Changed from conditional `if (cleanedWorkflow.settings)` to unconditional - Always sets `cleanedWorkflow.settings = {}` - Works for both cloud (requires property) and self-hosted (rejects properties) - tests/unit/services/n8n-validation.test.ts - Updated all 4 tests to expect `settings: {}` instead of removed settings - Tests verify empty object approach works for all scenarios Tested: - ✅ localhost workflow (wwTodXf1jbUy3Ja5) - ✅ cloud workflow (n8n.estyl.team/workflow/WKFeCRUjTeYbYhTf) - ✅ All 72 unit tests passing References: - https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916 - Tested with @agent-n8n-mcp-tester on production workflows --- .mcp.json.bk | 48 +++++++++++++++++++++ data/nodes.db | Bin 60383232 -> 60383232 bytes src/database/nodes.db | 0 src/services/n8n-validation.ts | 26 ++++++----- tests/unit/services/n8n-validation.test.ts | 30 ++++++------- 5 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 .mcp.json.bk create mode 100644 src/database/nodes.db diff --git a/.mcp.json.bk b/.mcp.json.bk new file mode 100644 index 0000000..29fa15b --- /dev/null +++ b/.mcp.json.bk @@ -0,0 +1,48 @@ +{ + "mcpServers": { + "puppeteer": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-puppeteer" + ] + }, + "brightdata-mcp": { + "command": "npx", + "args": [ + "-y", + "@brightdata/mcp" + ], + "env": { + "API_TOKEN": "e38a7a56edcbb452bef6004512a28a9c60a0f45987108584d7a1ad5e5f745908" + } + }, + "supabase": { + "command": "npx", + "args": [ + "-y", + "@supabase/mcp-server-supabase", + "--read-only", + "--project-ref=ydyufsohxdfpopqbubwk" + ], + "env": { + "SUPABASE_ACCESS_TOKEN": "sbp_3247296e202dd6701836fb8c0119b5e7270bf9ae" + } + }, + "n8n-mcp": { + "command": "node", + "args": [ + "/Users/romualdczlonkowski/Pliki/n8n-mcp/n8n-mcp/dist/mcp/index.js" + ], + "env": { + "MCP_MODE": "stdio", + "LOG_LEVEL": "error", + "DISABLE_CONSOLE_OUTPUT": "true", + "TELEMETRY_DISABLED": "true", + "N8N_API_URL": "http://localhost:5678", + "N8N_API_KEY": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiY2ExOTUzOS1lMGRiLTRlZGQtYmMyNC1mN2MwYzQ3ZmRiMTciLCJpc3MiOiJuOG4iLCJhdWQiOiJwdWJsaWMtYXBpIiwiaWF0IjoxNzU4NjE1ODg4LCJleHAiOjE3NjExOTIwMDB9.zj6xPgNlCQf_yfKe4e9A-YXQ698uFkYZRhvt4AhBu80" + } + + } + } +} \ No newline at end of file diff --git a/data/nodes.db b/data/nodes.db index e61ec2dbddcb1daa64430c5b00afb36bbed36e5d..1554ca2065c76b608d1ca0b2c4a19fa0f64a24de 100644 GIT binary patch delta 3465 zcmWmDQp^c4OPNZLT_Xp;j8Fu(l;$fJa zFlI=GfPexs0|N4N3wB3Y5G zC{|P}nibuOVa2p!S+T7+R$MEd72ir=CA1P*iLE47QY)F2+)81kv{G5Atu$6zE1i|z z%3x)*GFh3eELK)4o0Z+lVdb=PS-Gt|R$eQgmES606|@Rjg{>l1QLC6$+$v#}v`Sf} ztuj_wtDIHds$f;LDp{4SDppminpNGZVb!#1S+%V?R$Z%}Ro`l0HMAO8jjbkDQ>&Rp zpXOEztEJV-YHhW#+FI?b_Erb0qt(gkY<01^THUPfRu8ME)ywK_^|AU|{jC1h0BfK% z$Qo=7v4&d1tl`!OYos;GvNhToV~w?ftq^ORHQt(FO|&LildUP%RBM_w-I`&|v}Ree ztvS|QYo0aVT3{`-7Fmm}CDu}FnYG+nVXd@QS*xuz)>>I$#~N4q1n-Bi2#tn04GbVV$&2S*NWt)>-SEb>8~T`rW!< zU9>J)m#r(-RqGGyPwSd>-MV4jv~F3qtvl9T>z;MrdSE@Y{<8kI9$AmAC)QKznf2Uy zVZF3oS+A`()<4$2)?4eH_1^kmeY8GVpRF&}SL>Vg-TGnuXZ;LTs0j*%Km;K)!XPZd zAv_`=A|fF&q97`wAv$6pCSoBr;vg>KAwCiyArc`mk{~IPAvsbYB~l?Z(jYC;Aw4o6 zBQhZ~vLGw6AvYy&_p*|X*AsV4EnxH9~;aC6WXn~e!h1O_;wrGd;=zxysgwE)K zuIPsD=z*T-h2H3czUYVk7=VEoguxhsp%{kY7=e)(1!FYEU@U?Wf^is+37CjUn2afy zifNdR8JLM#n2kA@i+Pxj1z3nhSd1lDie*@i6V2o4AGBxP!a6hx>SdhxiMB;}IU?37+B^p5p~x;uT)w4gSHuc#C&4 z_=<1%jvx3BKSKp8ko^&eAcRI3ghe=nM+8JfBt%9OL`5`2M-0S7EW}0}#6>*BM*<{7 zA|yrCS*nyWJNY)M-JpfF62fYArwXt z6h$!fanK&=RfC8g0-P?a&?_&=H-`8C}p7-OwF9&=bAT8-36h{m>r+Fc5<<7(*}=!!R5p zFcPC+jK&y@MKD4z4&yNa6EO*sF$GgG4bw3LGcgOZF$Z%o5A(4A3$X}`u>?!849l?s zE3pczu?B0g4(qW28?gzSu?1VP4coB;JFyG9u?Ksx5BqTd2XP38aRf(k499T-Cvgg= zaRz5`4(IV3e#Zq|#3fwD6@eSYc1OMS?V6cMNAAtx$XoNvnghO~lKtx1B zWJEz!L_>7MKup9!Y{Wra#6x@}Ktd!!VkALQBtvqfKuV-SYNSD0q(gdSKt^OjW@JHD zWJ7l3Ku+XBZsb8;8KuMHBX_P@(ltXz`Kt)tSWmG{`R6}*t zKuy#_ZPYCfiG{dj{&Cvoa(F(2625r#}?a=`p(FvW=1zph%-O&R* z(F?uN2Yt~G{V@OoF$jY(1Vb?l!!ZIQF$%_LjKNq0BLw3x9uqJTlQ0=mFcs4<9WyW! OvoJg8faU}(ZuURl1PYV@ delta 3465 zcmWmDn|p)|rwiM_@?sFj=9jKr6@!ZH2MITH&nlRs<`e70HTh zMX{n<(X8lJ3@fG;Y{jx-TXC$oRy-@dmB319C9)D*Nvxz+GAp^2!b)kSvQk@Vth81- zE4`J$%4lV>GFw@!tX4KFyOqPrY2~tVTY0R!Rz54gRlq7}6|xFjMXaJ$F{`*$!YXN% zvPxTJtg==)tGrdgs%TZRDqB^os#Z0tx>dufY1Oi7TXn3uRz0h})xc_KHL@C8O{}IC zeVSR#trk{GtCiK-YGbvv+F9+b4pv92lhxVkVs*8;S>3H3R!^&!)!XW0^|kt0{jCAk zKx>dS*cxIDwT4;4tr6Bp%ho7sv^B;GvBp~CtntDCNurZvl& zZOyUfTJx;=)&gsxwa8j*EwPqb%dF+r3Tvgc%35u$vDRAato7CgYooQv+H7sHwp!b) z?bZ%!r?t!4ZSAr4TKla1)&c9Fb;vqw9kGsD$E@Sl3G1YF$~tYGvCdlOtn=1y*6-E@ z>!NkZx@=vsu3CRse_Ge9>(&kHrgh7@ZQZf%TKBB`)&uLI^_TUx^~ic`J+Yoz&#dRx z3+tuz%6e_RvHr3Cwcc9qtoPOj>!bC_`fPo%zFOa`@753NKkH|RLQPO81R@Bb5e8uq z4&f025fKTI5d~2Z4bc$;F%gVdh>bXii+G5S1W1TPNQ@*%ieyNR6iA6wNR2c|i*!hj z49JK~$c!w=ifqV^9LR}W$c;S6i+sqB0w{<=D2yT~iee~^5-5pMD2*~Gi*hKB3aE%m zsEjJ8ifX8i8mNg{sEsMDhF~a$VK_!$B#coQjWGzpSd7DXOu$4;!emUr zR7}Hk%)m^{!fedJT+G9KEWko6!eT7JQY^!AtiVdF!fLF+TCBr*Y`{ir!e(s2R&2v| z?7&X!!fx!rUhKnu9Kb;w!eJc2Q5?f@oWMz(!fBkrS)9Xp{D$9g0T*!zmvIGG@dy6I zHC)FH+{7*1#vR16wJj7r48;|f9Pw*7a@EkAj60h(YZ}1QP#aq0?dwjr0e8OjZ z!B>34cl^MA_!%lhf$Wb!1R*rSAS}WmJR%?>A|W!OAS$9EI$|Iuf)NX`5eIP*5Al%z z36ThikpxMR49SrKDUk}Pkp^jz4(X8r8IcK@kp)?i4cU6bB~c2cQ3hpE4&_k+6;TP5Q3X{|4b@QtHBk$-Q3rKV5B1Ri4bcdV(F9HL ztA8^zM+>w>E3`%%v_(6#M+bC7Cv-*^bVWCGM-TKwFZ4zq^hH1P#{dk(APmM348<@E z#|VssF$$wG1|b-WaTt#Yn21T3j47CkX_$@~n2A}KjX9W$d6pfzIEhm@ zjWallb2yLR@H;NxA}--FuHY*Ez@NB=>$riNxP{xegS)tg`*?td_zQpI5gy|Sp5hsv z;{{&g6<*^F{=vU^i+6aB5BP{r_>3?3if{OiANUVH149(V{s=@6LL&^qA{@da0wN+3 zA|nc-A{wG224W%@u@D<^5Etb93@Z^rBE7WP!{D-9u-g#l~5T~P!-is z9W_uBwNM*%P#5)39}UnDjnEiP&=kM=H$!u@KufejYqUXIv_pGzKu2^!XLLbVbVGOa zKu`2SZ}dT5^h19Pz(5SbU<|=f48w4Yz(^RQFdAbJg0UEf@tA;#n1sogf~lB>>6n3; Pn1$Iv2Q(*Want_+>xBxH diff --git a/src/database/nodes.db b/src/database/nodes.db new file mode 100644 index 0000000..e69de29 diff --git a/src/services/n8n-validation.ts b/src/services/n8n-validation.ts index 120f80e..a94c225 100644 --- a/src/services/n8n-validation.ts +++ b/src/services/n8n-validation.ts @@ -134,17 +134,23 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial { } = workflow as any; // CRITICAL FIX for Issue #248: - // The n8n API update endpoint has a strict schema that REJECTS settings updates. - // Properties like callerPolicy and executionOrder cannot be updated via the API - // (see: https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916) + // The n8n API has version-specific behavior for settings in workflow updates: // - // Solution: Remove settings entirely from update requests. The n8n API will - // preserve existing workflow settings when they are omitted from the update payload. - // This prevents "settings must NOT have additional properties" errors while - // ensuring workflow updates (name, nodes, connections) work correctly. - if (cleanedWorkflow.settings) { - delete cleanedWorkflow.settings; - } + // PROBLEM: + // - Some versions reject updates with settings properties (community forum reports) + // - Cloud versions REQUIRE settings property to be present (n8n.estyl.team) + // - Properties like callerPolicy and executionOrder cause "additional properties" errors + // + // SOLUTION: + // - ALWAYS set settings to empty object {}, regardless of whether it exists + // - Empty object satisfies "required property" validation (cloud API) + // - Empty object has no "additional properties" to trigger errors (self-hosted) + // - n8n API interprets empty settings as "no changes" and preserves existing settings + // + // References: + // - https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916 + // - Tested on n8n.estyl.team (cloud) and localhost (self-hosted) + cleanedWorkflow.settings = {}; return cleanedWorkflow; } diff --git a/tests/unit/services/n8n-validation.test.ts b/tests/unit/services/n8n-validation.test.ts index acd21c9..583a768 100644 --- a/tests/unit/services/n8n-validation.test.ts +++ b/tests/unit/services/n8n-validation.test.ts @@ -344,12 +344,12 @@ describe('n8n-validation', () => { expect(cleaned).not.toHaveProperty('shared'); expect(cleaned).not.toHaveProperty('active'); - // Should keep name but remove settings (n8n API limitation) + // Should keep name but replace settings with empty object (n8n API limitation) expect(cleaned.name).toBe('Updated Workflow'); - expect(cleaned).not.toHaveProperty('settings'); + expect(cleaned.settings).toEqual({}); }); - it('should not add default settings for update', () => { + it('should add empty settings object for cloud API compatibility', () => { const workflow = { name: 'Test Workflow', nodes: [], @@ -357,10 +357,10 @@ describe('n8n-validation', () => { } as any; const cleaned = cleanWorkflowForUpdate(workflow); - expect(cleaned).not.toHaveProperty('settings'); + expect(cleaned.settings).toEqual({}); }); - it('should remove settings entirely to prevent API errors (Issue #248 - corrected fix)', () => { + it('should replace settings with empty object to prevent API errors (Issue #248 - final fix)', () => { const workflow = { name: 'Test Workflow', nodes: [], @@ -375,11 +375,11 @@ describe('n8n-validation', () => { const cleaned = cleanWorkflowForUpdate(workflow); - // Settings should be removed entirely (n8n API doesn't support updating settings) - expect(cleaned).not.toHaveProperty('settings'); + // Settings replaced with empty object (satisfies both API versions) + expect(cleaned.settings).toEqual({}); }); - it('should remove settings with callerPolicy (Issue #248 - API limitation)', () => { + it('should replace settings with callerPolicy (Issue #248 - API limitation)', () => { const workflow = { name: 'Test Workflow', nodes: [], @@ -393,11 +393,11 @@ describe('n8n-validation', () => { const cleaned = cleanWorkflowForUpdate(workflow); - // Settings must be removed (n8n API rejects updates with settings properties) - expect(cleaned).not.toHaveProperty('settings'); + // Settings replaced with empty object (n8n API rejects updates with settings properties) + expect(cleaned.settings).toEqual({}); }); - it('should remove all settings regardless of content (Issue #248 - API design)', () => { + it('should replace all settings regardless of content (Issue #248 - API design)', () => { const workflow = { name: 'Test Workflow', nodes: [], @@ -417,9 +417,9 @@ describe('n8n-validation', () => { const cleaned = cleanWorkflowForUpdate(workflow); - // Settings removed due to n8n API limitation (cannot update settings via API) + // Settings replaced with empty object due to n8n API limitation (cannot update settings via API) // See: https://community.n8n.io/t/api-workflow-update-endpoint-doesnt-support-setting-callerpolicy/161916 - expect(cleaned).not.toHaveProperty('settings'); + expect(cleaned.settings).toEqual({}); }); it('should handle workflows without settings gracefully', () => { @@ -430,7 +430,7 @@ describe('n8n-validation', () => { } as any; const cleaned = cleanWorkflowForUpdate(workflow); - expect(cleaned).not.toHaveProperty('settings'); + expect(cleaned.settings).toEqual({}); }); }); }); @@ -1309,7 +1309,7 @@ describe('n8n-validation', () => { expect(forUpdate).not.toHaveProperty('active'); expect(forUpdate).not.toHaveProperty('tags'); expect(forUpdate).not.toHaveProperty('meta'); - expect(forUpdate).not.toHaveProperty('settings'); // Should not add defaults for update + expect(forUpdate.settings).toEqual({}); // Settings replaced with empty object for API compatibility expect(validateWorkflowStructure(forUpdate)).toEqual([]); }); });