fix(issue-248): use unconditional empty settings object for cloud API compatibility

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
This commit is contained in:
czlonkowski
2025-10-02 16:33:11 +02:00
parent fe1e3640af
commit 99518f71cf
5 changed files with 79 additions and 25 deletions

48
.mcp.json.bk Normal file
View File

@@ -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"
}
}
}
}

Binary file not shown.

0
src/database/nodes.db Normal file
View File

View File

@@ -134,17 +134,23 @@ export function cleanWorkflowForUpdate(workflow: Workflow): Partial<Workflow> {
} = workflow as any; } = workflow as any;
// CRITICAL FIX for Issue #248: // CRITICAL FIX for Issue #248:
// The n8n API update endpoint has a strict schema that REJECTS settings updates. // The n8n API has version-specific behavior for settings in workflow 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)
// //
// Solution: Remove settings entirely from update requests. The n8n API will // PROBLEM:
// preserve existing workflow settings when they are omitted from the update payload. // - Some versions reject updates with settings properties (community forum reports)
// This prevents "settings must NOT have additional properties" errors while // - Cloud versions REQUIRE settings property to be present (n8n.estyl.team)
// ensuring workflow updates (name, nodes, connections) work correctly. // - Properties like callerPolicy and executionOrder cause "additional properties" errors
if (cleanedWorkflow.settings) { //
delete cleanedWorkflow.settings; // 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; return cleanedWorkflow;
} }

View File

@@ -344,12 +344,12 @@ describe('n8n-validation', () => {
expect(cleaned).not.toHaveProperty('shared'); expect(cleaned).not.toHaveProperty('shared');
expect(cleaned).not.toHaveProperty('active'); 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.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 = { const workflow = {
name: 'Test Workflow', name: 'Test Workflow',
nodes: [], nodes: [],
@@ -357,10 +357,10 @@ describe('n8n-validation', () => {
} as any; } as any;
const cleaned = cleanWorkflowForUpdate(workflow); 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 = { const workflow = {
name: 'Test Workflow', name: 'Test Workflow',
nodes: [], nodes: [],
@@ -375,11 +375,11 @@ describe('n8n-validation', () => {
const cleaned = cleanWorkflowForUpdate(workflow); const cleaned = cleanWorkflowForUpdate(workflow);
// Settings should be removed entirely (n8n API doesn't support updating settings) // Settings replaced with empty object (satisfies both API versions)
expect(cleaned).not.toHaveProperty('settings'); 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 = { const workflow = {
name: 'Test Workflow', name: 'Test Workflow',
nodes: [], nodes: [],
@@ -393,11 +393,11 @@ describe('n8n-validation', () => {
const cleaned = cleanWorkflowForUpdate(workflow); const cleaned = cleanWorkflowForUpdate(workflow);
// Settings must be removed (n8n API rejects updates with settings properties) // Settings replaced with empty object (n8n API rejects updates with settings properties)
expect(cleaned).not.toHaveProperty('settings'); 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 = { const workflow = {
name: 'Test Workflow', name: 'Test Workflow',
nodes: [], nodes: [],
@@ -417,9 +417,9 @@ describe('n8n-validation', () => {
const cleaned = cleanWorkflowForUpdate(workflow); 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 // 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', () => { it('should handle workflows without settings gracefully', () => {
@@ -430,7 +430,7 @@ describe('n8n-validation', () => {
} as any; } as any;
const cleaned = cleanWorkflowForUpdate(workflow); 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('active');
expect(forUpdate).not.toHaveProperty('tags'); expect(forUpdate).not.toHaveProperty('tags');
expect(forUpdate).not.toHaveProperty('meta'); 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([]); expect(validateWorkflowStructure(forUpdate)).toEqual([]);
}); });
}); });