# Workflow Management Tools Guide Complete guide for creating, updating, and managing n8n workflows. --- ## Tool Availability **Requires n8n API**: All tools in this guide need `N8N_API_URL` and `N8N_API_KEY` configured. If unavailable, use template examples and validation-only workflows. --- ## n8n_create_workflow **Speed**: 100-500ms **Use when**: Creating new workflows from scratch **Syntax**: ```javascript n8n_create_workflow({ name: "Webhook to Slack", // Required nodes: [...], // Required: array of nodes connections: {...}, // Required: connections object settings: {...}, // Optional: workflow settings parentFolderId: "abc123" // Optional: folder to create in (n8n 2.32+; omit = project root) }) ``` **Returns**: Created workflow with ID **Example**: ```javascript n8n_create_workflow({ name: "Webhook to Slack", nodes: [ { id: "webhook-1", name: "Webhook", type: "n8n-nodes-base.webhook", // Full prefix! typeVersion: 2, position: [250, 300], parameters: { path: "slack-notify", httpMethod: "POST" } }, { id: "slack-1", name: "Slack", type: "n8n-nodes-base.slack", typeVersion: 2, position: [450, 300], parameters: { resource: "message", operation: "post", channel: "#general", text: "={{$json.body.message}}" } } ], connections: { "Webhook": { "main": [[{node: "Slack", type: "main", index: 0}]] } } }) ``` **Notes**: - Workflows created **inactive** (activate with `activateWorkflow` operation) - Auto-sanitization runs on creation - Validate before creating for best results - `parentFolderId` places the workflow in a folder at creation (find or create folders with `n8n_manage_folders`). On n8n < 2.32 the whole create is rejected with a 400 naming the field — retry without it --- ## n8n_update_partial_workflow (MOST USED!) **Speed**: 50-200ms | **Uses**: 38,287 (most used tool!) **Use when**: Making incremental changes to workflows **Common pattern**: 56s average between edits (iterative building!) ### 21 Operation Types **Node Operations** (7 types): 1. `addNode` - Add new node 2. `removeNode` - Remove node by ID or name 3. `updateNode` - Update node properties (use dot notation) 4. `patchNodeField` - Surgical string edits via strict find/replace (see below) 5. `moveNode` - Change position 6. `enableNode` - Enable disabled node 7. `disableNode` - Disable active node **Connection Operations** (5 types): 8. `addConnection` - Connect nodes (supports smart params) 9. `removeConnection` - Remove connection (supports ignoreErrors) 10. `rewireConnection` - Change connection target 11. `cleanStaleConnections` - Auto-remove broken connections 12. `replaceConnections` - Replace entire connections object **Metadata Operations** (5 types): 13. `updateSettings` - Workflow settings 14. `updateName` - Rename workflow 15. `setNodeGroups` - Replace the canvas groups (n8n 2.28+; see below) 16. `addTag` - Add tag 17. `removeTag` - Remove tag **Activation Operations** (2 types): 18. `activateWorkflow` - Activate workflow for automatic execution 19. `deactivateWorkflow` - Deactivate workflow **Project Management Operations** (2 types): 20. `transferWorkflow` - Transfer workflow to a different project (enterprise/cloud) 21. `moveToFolder` - Move workflow into a folder, or to the project root with `parentFolderId: null` (n8n 2.32+) ### Intent Parameter (IMPORTANT!) Always include `intent` for better responses: ```javascript n8n_update_partial_workflow({ id: "workflow-id", intent: "Add error handling for API failures", // Describe what you're doing operations: [...] }) ``` ### Smart Parameters **IF nodes** - Use semantic branch names: ```javascript { type: "addConnection", source: "IF", target: "True Handler", branch: "true" // Instead of sourceIndex: 0 } { type: "addConnection", source: "IF", target: "False Handler", branch: "false" // Instead of sourceIndex: 1 } ``` **Switch nodes** - Use semantic case numbers: ```javascript { type: "addConnection", source: "Switch", target: "Handler A", case: 0 } { type: "addConnection", source: "Switch", target: "Handler B", case: 1 } ``` ### AI Connection Types (8 types) **Full support** for AI workflows: ```javascript // Language Model { type: "addConnection", source: "OpenAI Chat Model", target: "AI Agent", sourceOutput: "ai_languageModel" } // Tool { type: "addConnection", source: "HTTP Request Tool", target: "AI Agent", sourceOutput: "ai_tool" } // Memory { type: "addConnection", source: "Window Buffer Memory", target: "AI Agent", sourceOutput: "ai_memory" } // All 8 types: // - ai_languageModel // - ai_tool // - ai_memory // - ai_outputParser // - ai_embedding // - ai_vectorStore // - ai_document // - ai_textSplitter ``` ### Property Removal with null Remove properties by setting them to `null`: ```javascript // Remove a property { type: "updateNode", nodeName: "HTTP Request", updates: { onError: null } } // Migrate from deprecated property { type: "updateNode", nodeName: "HTTP Request", updates: { continueOnFail: null, // Remove old onError: "continueErrorOutput" // Add new } } ``` ### patchNodeField (Surgical String Edits) Use `patchNodeField` for strict find/replace edits on string fields — code, HTML, email templates, JSON bodies. Unlike `updateNode` with `__patch_find_replace` (which silently warns on misses), `patchNodeField` is strict: it errors if the find string is not found, and errors if multiple matches are found (preventing ambiguous replacements). **When to use which**: - `patchNodeField` — preferred for most string edits. Strict error handling catches mistakes early. - `updateNode` with `__patch_find_replace` — legacy approach. Tolerant (warns but continues on miss). Use only when you want lenient behavior. **Syntax**: ```javascript { type: "patchNodeField", nodeName: "Code", // or nodeId fieldPath: "parameters.jsCode", // Dot-notation path to the string field patches: [ { find: "const limit = 10;", replace: "const limit = 50;", replaceAll: false, // Default: false. Set true to replace all occurrences regex: false // Default: false. Set true to treat find as regex } ] } ``` **Examples**: ```javascript // Basic strict find/replace in code n8n_update_partial_workflow({ id: "wf-123", intent: "Update API limit", operations: [{ type: "patchNodeField", nodeName: "Code", fieldPath: "parameters.jsCode", patches: [{find: "const limit = 10;", replace: "const limit = 50;"}] }] }) // Replace all occurrences of a URL n8n_update_partial_workflow({ id: "wf-123", intent: "Migrate API domain", operations: [{ type: "patchNodeField", nodeName: "Code", fieldPath: "parameters.jsCode", patches: [{find: "api.old.com", replace: "api.new.com", replaceAll: true}] }] }) // Regex-based replacement (whitespace-insensitive) n8n_update_partial_workflow({ id: "wf-123", intent: "Update limit with regex", operations: [{ type: "patchNodeField", nodeName: "Code", fieldPath: "parameters.jsCode", patches: [{find: "const\\s+limit\\s*=\\s*\\d+", replace: "const limit = 100", regex: true}] }] }) // Multiple sequential patches on an email template n8n_update_partial_workflow({ id: "wf-123", intent: "Update email footer", operations: [{ type: "patchNodeField", nodeName: "Set Email", fieldPath: "parameters.assignments.assignments.6.value", patches: [ {find: "© 2025", replace: "© 2026"}, {find: "
Unsubscribe
", replace: ""} ] }] }) ``` **Error behavior** (this is what makes it strict): - **Find string not found** → operation fails with error (not a silent warning) - **Multiple matches without `replaceAll`** → operation fails (ambiguity detected) - Patches are applied sequentially — order matters **Security limits**: - Max 50 patches per operation - Regex patterns capped at 500 characters - Regex only on fields under 512KB - ReDoS-safe: rejects nested quantifiers like `(a+)+` and overlapping alternations like `(\w|\d)+` - Prototype pollution protection on field paths ### Activation Operations ```javascript // Activate workflow n8n_update_partial_workflow({ id: "workflow-id", intent: "Activate workflow for production", operations: [{type: "activateWorkflow"}] }) // Deactivate workflow n8n_update_partial_workflow({ id: "workflow-id", intent: "Deactivate workflow for maintenance", operations: [{type: "deactivateWorkflow"}] }) ``` ### Example Usage ```javascript n8n_update_partial_workflow({ id: "workflow-id", intent: "Add transform node after IF condition", operations: [ // Add node { type: "addNode", node: { name: "Transform", type: "n8n-nodes-base.set", position: [400, 300], parameters: {} } }, // Connect it (smart parameter) { type: "addConnection", source: "IF", target: "Transform", branch: "true" // Clear and semantic! } ] }) ``` ### Canvas Groups (n8n 2.28+) A canvas group is a named frame drawn around a connected run of non-trigger nodes. Purely presentational — it changes nothing about execution — but **n8n validates groups on every write, including writes that have nothing to do with grouping.** That is the part worth understanding, because it used to make unrelated edits fail. **You do not have to manage groups to edit a grouped workflow.** n8n-mcp reconciles them with the graph you produce, once, at the end of a diff: - A grouped node you remove is pruned from its group; the group survives on its remaining members. - A group left with no members is dropped. - A group n8n refuses (its members are no longer a single connected run) is ungrouped so your edit still lands. **Nodes and connections are never altered to save a group.** Every adjustment comes back in `details.warnings`. Read them — they are the only signal that a user's grouping changed as a side effect of your edit. **Authoring groups** with `setNodeGroups`. It replaces the whole list, like `replaceConnections`: ```javascript n8n_update_partial_workflow({ id: "workflow-id", intent: "Group the enrichment steps", operations: [{ type: "setNodeGroups", nodeGroups: [ { name: "Enrich lead", nodeNames: ["Fetch company", "Score lead"] }, { name: "Notify", nodeIds: ["a1b2c3d4"] } // ids work too ] }] }) ``` - Pass **every group you want to keep** — anything you omit is gone. - `nodeGroups: []` ungroups everything. - Give each group `nodeNames` **or** `nodeIds`, not both populated. The group `id` is generated unless you supply one. - `description` is optional, max 155 characters, and only exists on n8n 2.32+. On older instances it is dropped with a warning rather than failing the write. **Creating a workflow with groups.** `n8n_create_workflow` and `n8n_update_full_workflow` take the same field, in n8n's own shape — members are node **IDs**, because you are sending those nodes in the same payload: ```javascript n8n_create_workflow({ name: "Lead pipeline", nodes: [...], connections: {...}, nodeGroups: [{ name: "Enrich lead", nodeIds: ["fetch-company", "score-lead"] }] }) ``` On a full update the field follows the same rule as `settings`: **omit it to keep the stored groups**, pass `[]` to ungroup everything. An id that is not in `nodes[]` is an error, not something to repair quietly — you supplied both halves, so a mismatch is a bug in the request. **What n8n will reject.** Members must form a single connected run with one way in and one way out, and a trigger can never be inside a group. n8n decides this, not n8n-mcp — so its message is returned to you verbatim. A group *you* asked for in this request is never silently discarded: if n8n refuses it you get an error, not a quiet success. ``` Node group "Enrich lead" (…) must form a single connected subgraph with a single entry and exit. Node group "Enrich lead" (…) cannot contain trigger nodes: Manual Trigger. ``` When that happens, fix the selection — usually by including the node that sits between two members, or excluding the trigger — rather than retrying the same shape. **Older instances.** Canvas groups do not exist before n8n 2.28. Authoring them there saves the workflow without groups and warns; it does not fail. **Reading groups.** `n8n_get_workflow` returns `nodeGroups` in `full`, `details` and `structure` modes when the workflow has any. `mode: "active"` returns the *published* version's groups, which can differ from the draft's. ### moveToFolder (Folder Placement, n8n 2.32+) Move the workflow into a folder as part of a diff: ```javascript n8n_update_partial_workflow({ id: "workflow-id", intent: "Move into the Production folder", operations: [ {type: "moveToFolder", parentFolderId: "abc123"} // or null = project root ] }) ``` n8n treats a workflow's folder as **write-only**: it can be set but never read back (no field in `n8n_get_workflow`, no folder filter on `n8n_list_workflows`). Verify a move indirectly — `n8n_manage_folders({action: "get", folderId: "abc123"})` reports the folder's recursive `totalWorkflows` — or visually in the n8n UI. Two consequences: - If a mixed update fails after the PUT persisted, the rollback restores the graph but **cannot** restore the previous folder (it was never readable); the error says so. - Combined with `transferWorkflow` in one request, the folder move applies in the *source* project before the transfer — move in a separate call after transferring. On n8n < 2.32 the operation fails with a 400 naming `parentFolderId` plus upgrade guidance. Find or create folders with `n8n_manage_folders` (see below). ### Cleanup & Recovery **cleanStaleConnections** - Remove broken connections: ```javascript {type: "cleanStaleConnections"} ``` **rewireConnection** - Change target atomically: ```javascript { type: "rewireConnection", source: "Webhook", from: "Old Handler", to: "New Handler" } ``` **Best-effort mode** - Apply what works: ```javascript n8n_update_partial_workflow({ id: "workflow-id", operations: [...], continueOnError: true // Don't fail if some operations fail }) ``` **Validate before applying**: ```javascript n8n_update_partial_workflow({ id: "workflow-id", operations: [...], validateOnly: true // Preview without applying }) ``` --- ## n8n_deploy_template (QUICK START!) **Speed**: 200-500ms **Use when**: Deploying a template directly to n8n instance ```javascript n8n_deploy_template({ templateId: 2947, // Required: from n8n.io name: "My Weather to Slack", // Optional: custom name autoFix: true, // Default: auto-fix common issues autoUpgradeVersions: true, // Default: upgrade node versions stripCredentials: true // Default: remove credential refs }) ``` **Returns**: - Workflow ID - Required credentials - Fixes applied **Example**: ```javascript // Deploy a webhook to Slack template const result = n8n_deploy_template({ templateId: 2947, name: "Production Slack Notifier" }); // Result includes: // - id: "new-workflow-id" // - requiredCredentials: ["slack"] // - fixesApplied: ["typeVersion upgraded", "expression format fixed"] ``` --- ## n8n_workflow_versions (VERSION CONTROL) **Use when**: Managing workflow history, rollback, cleanup, comparing versions Two independent histories, selected with `source`: - `source: "local"` (default) — snapshots n8n-mcp takes before it changes a workflow. Any n8n version, no token; ids are numbers. Blind to edits made in the n8n UI. The only source with `delete` and `prune`. - `source: "native"` — n8n's own workflow history, the same list the UI shows. Needs `N8N_MCP_ACCESS_TOKEN` (n8n 2.34+; native `diff` needs 2.36) and the workflow's "Available in MCP" setting; ids are opaque strings; `list` is capped at 50 with `offset`; `delete`/`prune` return `MODE_NOT_SUPPORTED_FOR_SOURCE`; native `rollback` runs without local validation and says so in `validation`. Native modes are gated on the workflow's "Available in MCP" setting: when it is off the call answers `WORKFLOW_NOT_EXPOSED`, and re-running with `exposeToMcp: true` turns it on and retries once (`exposedToMcp: true` comes back in the response). That setting is visible and persistent in the n8n UI — confirm with the user before enabling it. Nothing ever disables it implicitly. `timeoutMs` (5000-600000) is the client deadline for a native call. Every response states `source` and `backend` (`n8n-mcp` for local, `official-mcp` for native). The examples below use `source: "local"` unless noted. ### List Versions ```javascript n8n_workflow_versions({ mode: "list", workflowId: "workflow-id", limit: 10 }) // n8n's own history (edits made in the UI included) n8n_workflow_versions({ mode: "list", source: "native", workflowId: "workflow-id", limit: 20, offset: 0 }) ``` ### Get Specific Version ```javascript n8n_workflow_versions({ mode: "get", workflowId: "workflow-id", versionId: 123 // local ids are numbers }) n8n_workflow_versions({ mode: "get", source: "native", workflowId: "workflow-id", versionId: "8f3c…" // native ids are strings, from the native list }) ``` ### Diff Two Versions ```javascript // Local: added/removed/modified nodes reported as node IDs (data.format: "n8n-mcp") n8n_workflow_versions({ mode: "diff", workflowId: "workflow-id", versionId: 122, toVersionId: 123 }) // Native: n8n's own payload with field-level before/after values (data.format: "n8n", n8n 2.36+) n8n_workflow_versions({ mode: "diff", source: "native", workflowId: "workflow-id", versionId: "8f3c…", toVersionId: "a91d…" }) ``` Both versions must come from the same source and the same workflow; a mismatch is refused, not silently compared. ### Rollback to Previous Version ```javascript n8n_workflow_versions({ mode: "rollback", workflowId: "workflow-id", versionId: 123, // Optional: specific version validateBefore: true // Default: validate before rollback (local only) }) // Native rollback restores n8n's own version; no local validation runs n8n_workflow_versions({ mode: "rollback", source: "native", workflowId: "workflow-id", versionId: "8f3c…" }) ``` ### Delete Versions Local snapshots only (`source: "native"` returns `MODE_NOT_SUPPORTED_FOR_SOURCE`). ```javascript // Delete specific version n8n_workflow_versions({ mode: "delete", workflowId: "workflow-id", versionId: 123 }) // Delete all versions for workflow n8n_workflow_versions({ mode: "delete", workflowId: "workflow-id", deleteAll: true }) ``` ### Prune Old Versions Local snapshots only. ```javascript n8n_workflow_versions({ mode: "prune", workflowId: "workflow-id", maxVersions: 10 // Keep 10 most recent }) ``` --- ## n8n_test_workflow (RUNNING WORKFLOWS) **Use when**: Running a workflow to test it `method` picks the path. `auto` (default) and `trigger` fire a webhook/form/chat trigger over HTTP through the Public API — the workflow must be active. `prepare`, `pinned` and `direct` go through n8n's MCP server (`N8N_MCP_ACCESS_TOKEN`, n8n 2.34+, workflow "Available in MCP") and also work for inactive workflows and workflows without an HTTP trigger. `auto` never runs anything through n8n's MCP server: without an HTTP trigger it reports that the workflow cannot be triggered and names the other methods. **Both routed run methods execute real nodes.** `direct` runs every node; `pinned` substitutes pinned data only for trigger nodes, nodes with credentials and HTTP Request nodes, so Code, Set, If and credential-free I/O (Execute Command, file read/write) still run for real. Confirm with the user before running a workflow that writes anywhere. `executionMode: "production"` (on `direct`) changes the execution context and how the run is recorded, not whether it has side effects — never pass it unasked. SKILL.md → "Running Workflows" has the same methods as a side-by-side table. ```javascript // HTTP trigger path (method auto/trigger): the older fields still apply here n8n_test_workflow({ workflowId: "workflow-id", triggerType: "webhook", // Optional: auto-detected httpMethod: "POST", data: {message: "Hello!"}, waitForResponse: true, timeout: 120000 // HTTP trigger path only }) n8n_test_workflow({ workflowId: "workflow-id", triggerType: "chat", message: "Hello, AI agent!", sessionId: "session-123" // For conversation continuity }) // Which nodes need pinned data (read-only) n8n_test_workflow({ workflowId: "workflow-id", method: "prepare" }) // Run with pinned data standing in for trigger, credentialed and HTTP Request nodes n8n_test_workflow({ workflowId: "workflow-id", method: "pinned", pinData: {"Webhook": [{"json": {"id": "123"}}]}, // keyed by node name; items wrapped as {json: ...} timeoutMs: 300000 // client deadline for the official call (5000-600000) }) // Start a run without a webhook; returns once the run has started n8n_test_workflow({ workflowId: "workflow-id", method: "direct", data: {message: "Hello!"}, triggerNodeName: "Manual Trigger" // optional; required by n8n when inputs are given }) // then poll: n8n_executions({action: "get", id: executionId}) ``` `timeout` applies to the HTTP trigger path only; the routed methods use `timeoutMs`. A workflow whose "Available in MCP" setting is off comes back as `WORKFLOW_NOT_EXPOSED`; re-running with `exposeToMcp: true` enables that setting on the workflow (a visible, persistent change — confirm with the user first). --- ## n8n_manage_credentials (CREDENTIAL MANAGEMENT) **Speed**: 50-500ms **Use when**: Creating, updating, listing, or deleting credentials; discovering credential schemas ### 6 Actions 1. `list` - List all credentials (id, name, type, timestamps) 2. `get` - Get credential by ID (data field stripped) 3. `create` - Create credential (requires name, type, data) 4. `update` - Update credential by ID (name, data, and/or type) 5. `delete` - Permanently delete credential by ID 6. `getSchema` - Discover required fields for a credential type `list` and `get` also accept an optional `includeUsage: true` flag that attaches workflow-usage info to each credential (see "Find Which Workflows Use a Credential" below). ### List Credentials ```javascript n8n_manage_credentials({action: "list"}) // → [{id, name, type, createdAt, updatedAt}, ...] ``` ### Get Credential ```javascript n8n_manage_credentials({action: "get", id: "123"}) // → {id, name, type, ...} (data field stripped for security) // Falls back to list+filter if GET returns 403/405 ``` ### Find Which Workflows Use a Credential n8n's public API has no native "which workflows use credential X" endpoint, so n8n-mcp builds the reverse index for you by scanning workflows client-side. Pass `includeUsage: true` to either `list` or `get`. ```javascript // Every credential, with the workflows that reference it n8n_manage_credentials({action: "list", includeUsage: true}) // → { // credentials: [ // { // id: "123", // name: "Production Slack", // type: "slackApi", // createdAt: "...", updatedAt: "...", // usedIn: [ // {id: "wf_abc", name: "Daily digest", active: true}, // {id: "wf_xyz", name: "Alert fan-out", active: false} // ], // usageCount: 2 // }, // ... // ], // count: N, // // usageScanError: "..." // present only if the workflow scan failed // } // One credential, with its workflow references n8n_manage_credentials({action: "get", id: "123", includeUsage: true}) // → Same shape as `get` plus usedIn and usageCount. // On scan failure: response sets usageScanError and omits usedIn/usageCount. ``` **When to use it:** - Before `delete`: confirm nothing references the credential - Before rotating a secret with `update`: see exactly which workflows you'll affect - After `n8n_audit_instance` flags a credential: locate the workflows that need remediation **Behavior and limits:** - The reverse index is built client-side, deduplicated per workflow, and capped at 5000 workflows (same ceiling as `n8n_audit_instance`) - Archived workflows are excluded by n8n's API — a "no usages" result does **not** prove a credential is unused; verify before destructive actions - Triggers one full workflow scan per call. On large instances expect slower responses than the base ~50–500ms — budget accordingly when calling repeatedly - If the scan fails, the response degrades to base credentials with a `usageScanError` field rather than failing the whole call - Default behavior unchanged: omit the flag and no extra API calls happen ### Discover Schema ```javascript n8n_manage_credentials({ action: "getSchema", type: "httpHeaderAuth" }) // → Required fields, types, descriptions for this credential type ``` ### Create Credential ```javascript n8n_manage_credentials({ action: "create", name: "My Slack Token", type: "slackApi", data: {accessToken: "xoxb-your-token"} }) // → Created credential (data field stripped from response) ``` ### Update Credential ```javascript n8n_manage_credentials({ action: "update", id: "123", name: "Updated Slack Token", data: {accessToken: "xoxb-new-token"}, type: "slackApi" // Optional, some n8n versions require it }) // → Updated credential (data field stripped from response) ``` ### Delete Credential ```javascript n8n_manage_credentials({action: "delete", id: "123"}) ``` ### Typical Workflow: Set Up Credentials for a New Integration ```javascript // 1. Discover what fields are needed n8n_manage_credentials({ action: "getSchema", type: "slackApi" }) // 2. Create the credential n8n_manage_credentials({ action: "create", name: "Production Slack", type: "slackApi", data: {accessToken: "xoxb-..."} }) // 3. Verify it was created n8n_manage_credentials({action: "list"}) ``` ### Typical Workflow: Safely Delete or Rotate a Credential ```javascript // 1. Check what would break n8n_manage_credentials({action: "get", id: "123", includeUsage: true}) // → Inspect usedIn — the {id, name, active} of every workflow that references it // 2a. If nothing depends on it, delete n8n_manage_credentials({action: "delete", id: "123"}) // 2b. If something does, rotate the secret instead and notify owners n8n_manage_credentials({ action: "update", id: "123", data: {accessToken: "xoxb-new-..."} }) ``` ### Security Notes - **Response stripping**: `get`, `create`, and `update` all strip the `data` field from responses (defense-in-depth — secrets are never returned) - **Log redaction**: Credential request bodies are redacted from debug logs - **Fallback resilience**: `get` falls back to list+filter when `GET /credentials/:id` returns 403/405 (endpoint not in all n8n versions) - **Usage scan resilience**: when `includeUsage: true` triggers a workflow scan that fails, the response includes `usageScanError` and still returns the base credentials rather than erroring out --- ## n8n_audit_instance (SECURITY AUDIT) **Speed**: 500-5000ms (scans all workflows) **Use when**: Auditing instance security, finding hardcoded secrets, checking for unauthenticated webhooks, verifying error handling ### Two Scanning Approaches **1. Built-in Audit** (via n8n's `POST /audit` API): - 5 risk categories: `credentials`, `database`, `nodes`, `instance`, `filesystem` - Wraps n8n's native audit endpoint; gracefully degrades if unavailable **2. Custom Deep Scan** (workflow analysis): - `hardcoded_secrets` — 50+ regex patterns for API keys/tokens/passwords plus PII detection - `unauthenticated_webhooks` — Webhook/form triggers without authentication - `error_handling` — Workflows with 3+ nodes and no error handling - `data_retention` — Workflows saving all execution data ### Examples ```javascript // Full audit (default) n8n_audit_instance() // Built-in audit only n8n_audit_instance({ categories: ["credentials", "nodes", "instance"], includeCustomScan: false }) // Custom scan only — specific checks n8n_audit_instance({ customChecks: ["hardcoded_secrets", "unauthenticated_webhooks"] }) // Custom abandoned workflow threshold n8n_audit_instance({ daysAbandonedWorkflow: 90 }) ``` ### Output Returns an actionable markdown report with: - **Summary table**: Critical/high/medium/low finding counts - **Findings by workflow**: Per-workflow tables of issues - **Built-in audit results**: n8n's native audit findings - **Remediation Playbook**: - Auto-fixable items (with tool chains to apply) - Items requiring review (human judgment needed) - Items requiring user action (e.g., key rotation) ### Secret Masking Detected secrets are masked in output — shows first 6 + last 4 characters only. Raw values are never stored or returned. ### Remediation Types - `auto_fixable` — Can be fixed with MCP tools (e.g., add webhook auth) - `review_recommended` — Needs human judgment (e.g., PII detection) - `user_input_needed` — Requires user decision (e.g., choose auth method) - `user_action_needed` — Manual action required (e.g., rotate exposed API key) --- ## n8n_validate_workflow (by ID) **Use when**: Validating workflow stored in n8n ```javascript n8n_validate_workflow({ id: "workflow-id", options: { validateNodes: true, validateConnections: true, validateExpressions: true, profile: "runtime" } }) ``` --- ## n8n_get_workflow **Use when**: Retrieving workflow details n8n has a **draft/publish model**: the workflow body holds the draft (your latest edits), while `mode: "active"` returns the published graph that's actually running. Pick the mode by how much you need and how big the workflow is. **Modes**: - `full` (default) - Draft workflow JSON + metadata - `details` - Full + execution stats (success/error counts, last run) - `active` - The published (running) graph; returns `code: "NO_ACTIVE_VERSION"` if the workflow was never activated - `structure` - Nodes + connections only (topology, no `parameters`) - `filtered` - Full config of **only** the nodes named in `nodeNames` (matched by node name *or* node id), plus light metadata. Use it to read one heavy node — e.g. a Code node with long `jsCode`/`pythonCode` — on a large workflow that would otherwise get truncated client-side when fetched whole - `minimal` - ID, name, active, tags (fastest) ```javascript // Full draft workflow n8n_get_workflow({id: "workflow-id"}) // Just the topology (cheap; strips parameters) n8n_get_workflow({id: "workflow-id", mode: "structure"}) // Read one heavy node without the whole workflow (avoids client-side truncation) n8n_get_workflow({id: "workflow-id", mode: "filtered", nodeNames: ["Process Data"]}) // Minimal metadata n8n_get_workflow({id: "workflow-id", mode: "minimal"}) ``` **Recommended flow for big workflows**: `mode: "structure"` to discover node names cheaply → `mode: "filtered"` with those names to pull the specific heavy node's full config. This is the fix for the case where `full`/`active` returns a payload large enough that the client truncates it and you can't read the Code-node source at all. **`filtered` mode returns**: `{ id, name, active, isArchived, nodes[] (full config of matched nodes only), nodeCount (total in workflow), returnedCount, notFound? }`. It omits `connections` and the rest of the graph by design, so the response stays small. **`filtered` pitfalls**: - Requires a non-empty `nodeNames` array. Entries that match nothing come back in a `notFound` list rather than erroring, so a partial request stays transparent — check `notFound` before assuming a node is missing. - `nodeNames` matches each entry against node **name OR id** in one namespace, so `returnedCount` can exceed `nodeNames.length` when a name collides with another node's id, or when the workflow has duplicate node names. Disambiguate by the `id` on each returned node. --- ## n8n_executions (EXECUTION MANAGEMENT) **Use when**: Managing workflow executions ### Get Execution Details ```javascript n8n_executions({ action: "get", id: "execution-id", mode: "summary" // preview, summary, filtered, full, error }) // Error mode for debugging n8n_executions({ action: "get", id: "execution-id", mode: "error", includeStackTrace: true }) ``` ### List Executions ```javascript n8n_executions({ action: "list", workflowId: "workflow-id", status: "error", // success, error, waiting limit: 100 }) ``` ### Delete Execution ```javascript n8n_executions({ action: "delete", id: "execution-id" }) ``` --- ## n8n_evaluations (EVALUATION TEST RUNS) **Use when**: Working with evaluation test runs — starting or cancelling a run, polling one in progress, comparing metrics across runs, pulling per-case results into a report or dashboard. Reads (`list_runs`/`get_run`/`list_cases`) require n8n >= 2.30 and an API key created on 2.30+; `run`/`cancel` require n8n >= 2.32 and a key created on 2.32+ — older keys silently lack the `testRun` scopes. A 403 can mean: a key created before the action's minimum version (re-create it), evaluations not licensed on the plan, or the key's owner lacking access to the workflow — for `run`/`cancel`, specifically the `workflow:execute` scope. Runs exist only for workflows with an evaluation trigger. ### List Test Runs ```javascript n8n_evaluations({ action: "list_runs", workflowId: "workflow-id", status: "completed" // new, running, completed, error, cancelled }) ``` ### Get a Run (aggregated metrics) ```javascript n8n_evaluations({ action: "get_run", workflowId: "workflow-id", runId: "run-id" }) // → status, finalResult (success/error/warning), metrics, testCaseCount // metrics is a flat name → number/boolean map: your custom metrics plus // n8n's automatic ones (promptTokens, completionTokens, totalTokens, executionTime) ``` ### Per-Case Results ```javascript n8n_evaluations({ action: "list_cases", workflowId: "workflow-id", runId: "run-id" }) // Default limit 20 — per-case inputs/outputs can be large; paginate with // cursor rather than raising the limit. // Each case carries an executionId — drill into the underlying execution // with n8n_executions({action: "get", id: executionId, mode: "error"}) ``` ### Start a Run ```javascript n8n_evaluations({ action: "run", workflowId: "workflow-id" }) // → {id, status: "new", createdAt} — poll with get_run until completed ``` `run` executes the workflow once per dataset row — real nodes fire (HTTP calls, DB writes, sends) and LLM metric calls cost money. Ask the user before starting a run, especially against a large dataset. ### Cancel a Run ```javascript n8n_evaluations({ action: "cancel", workflowId: "workflow-id", runId: "run-id" }) // → {id, status: "cancelled"} plus a note: in-flight cases stop // asynchronously — confirm with get_run that the run reached "cancelled" ``` **Gotchas**: - A 404 can mean three things: the instance predates the action's minimum version, the workflowId is wrong, or the runId belongs to a different workflow (the tool's error message disambiguates using the instance version when it can read it) - Pre-2.32 instances answer `run` with 405 (the route exists, GET-only) and `cancel` with 404 (the route does not exist); the tool folds both into its upgrade guidance - 409 on `run` = the workflow has no evaluation trigger; 409 on `cancel` = the run already finished - 402 on `run` = the license's evaluation-run quota is exhausted - Evaluations are license/quota-gated in n8n — an unlicensed instance answers `run`/`cancel` with 403, and its reads have no runs to return - Compare `metrics` across runs of the same workflow to catch prompt/model regressions --- ## n8n_manage_folders (FOLDER MANAGEMENT) **Use when**: Organizing workflows into folders — creating structure before deploying a batch, restructuring a grown instance, or finding where to place a new workflow. Folder CRUD needs n8n >= 2.19 and a licensed instance (folders unlock on the **registered free Community tier** — Settings → Usage and plan → register — and up) plus `folder:*` API key scopes. Placing *workflows* into folders needs n8n >= 2.32 and happens in the workflow tools (`parentFolderId` on `n8n_create_workflow`, `moveToFolder` op), not here. ### 6 Actions `create`, `list`, `get`, `rename`, `move`, `delete`. `projectId` defaults to `'personal'` (the calling user's personal project) on every action — pass a real project ID on multi-project enterprise instances. ### Create a Folder ```javascript n8n_manage_folders({action: "create", name: "Production"}) // Nested: add parentFolderId: "abc123" // → {id, name, parentFolderId} ``` ### List Folders (with contents counts) ```javascript n8n_manage_folders({action: "list"}) // Optional: nameFilter (contains match), parentFolderId (direct children only), // sortBy ("name:asc" ... default "updatedAt:desc"), skip, take (max 100) // → folders: [{id, name, parentFolder, workflowCount, subFolderCount, path}], count // count = total matching the query, not the page size; path = names from root ``` ### Folder Details (recursive totals) ```javascript n8n_manage_folders({action: "get", folderId: "abc123"}) // → {..., totalSubFolders, totalWorkflows} // recursive — this is how you verify // a workflow placement, since n8n never reports a workflow's folder directly ``` ### Rename / Move ```javascript n8n_manage_folders({action: "rename", folderId: "abc123", name: "Staging"}) n8n_manage_folders({action: "move", folderId: "abc123", parentFolderId: "def456"}) n8n_manage_folders({action: "move", folderId: "abc123", parentFolderId: null}) // → project root ``` ### Delete (read this one) ```javascript n8n_manage_folders({action: "delete", folderId: "abc123", transferToFolderId: "0"}) ``` Without `transferToFolderId`, the folder's workflows are moved to the project root **and ARCHIVED** (deactivated), and sub-folders are deleted. Pass `transferToFolderId` to move contents somewhere first — `"0"` means the project root and keeps workflows active. Prefer the transfer form unless archiving is intended. **Gotchas**: - Folder names are not unique — `list` before `create` to avoid duplicates - A workflow's folder cannot be read back through the API; don't build logic that queries folder membership. Folder contents are visible only as counts - The `'personal'` default resolves through the projects API when licensed; on Community (where that API is 403) it infers the project from an existing workflow — a brand-new instance with zero workflows needs an explicit `projectId`, or create any workflow first - 403 = missing `folder:*` scopes or an unregistered/unlicensed instance; 404 = wrong project/folder ID, or n8n < 2.19 (no folders API at all) - On n8n 2.19–2.31 folder CRUD works but workflow placement doesn't (that needs 2.32+) --- ## Workflow Lifecycle **Standard pattern**: ``` 1. CREATE n8n_create_workflow({...}) → Returns workflow ID 2. VALIDATE n8n_validate_workflow({id}) → Check for errors 3. EDIT (iterative! 56s avg between edits) n8n_update_partial_workflow({id, intent: "...", operations: [...]}) → Make changes 4. VALIDATE AGAIN n8n_validate_workflow({id}) → Verify changes 5. ACTIVATE n8n_update_partial_workflow({ id, intent: "Activate workflow", operations: [{type: "activateWorkflow"}] }) → Workflow now runs on triggers! 6. MONITOR n8n_executions({action: "list", workflowId: id}) n8n_executions({action: "get", id: execution_id}) ``` --- ## Common Patterns from Telemetry ### Pattern 1: Edit → Validate (7,841 occurrences) ```javascript n8n_update_partial_workflow({...}) // ↓ 23s (thinking about what to validate) n8n_validate_workflow({id}) ``` ### Pattern 2: Validate → Fix (7,266 occurrences) ```javascript n8n_validate_workflow({id}) // ↓ 58s (fixing errors) n8n_update_partial_workflow({...}) ``` ### Pattern 3: Iterative Building (31,464 occurrences) ```javascript update → update → update → ... (56s avg between edits) ``` **This shows**: Workflows are built **iteratively**, not in one shot! --- ## Best Practices ### Do - Build workflows **iteratively** (avg 56s between edits) - Include **intent** parameter for better responses - Use **smart parameters** (branch, case) for clarity - Validate **after** significant changes - Use **atomic mode** (default) for critical updates - Specify **sourceOutput** for AI connections - Clean stale connections after node renames/deletions - Use `n8n_deploy_template` for quick starts - Activate workflows via API when ready ### Don't - Try to build workflows in one shot - Skip the intent parameter - Use sourceIndex when branch/case available - Skip validation before activation - Forget to test workflows after creation - Ignore auto-sanitization behavior --- ## Summary **Most Important**: 1. **n8n_update_partial_workflow** is most-used tool (38,287 uses, 19 operation types) 2. Include **intent** parameter for better responses 3. Workflows built **iteratively** (56s avg between edits) 4. Use **smart parameters** (branch="true", case=0) for clarity 5. **patchNodeField** for surgical string edits (strict find/replace with regex support) 6. **AI connections** supported (8 types with sourceOutput) 7. **Workflow activation** supported via API (`activateWorkflow` operation) 8. **Auto-sanitization** runs on all operations 9. Use **n8n_deploy_template** for quick starts **Additional Tools**: - `n8n_deploy_template` - Deploy templates directly - `n8n_workflow_versions` - Version control & rollback - `n8n_test_workflow` - Trigger execution - `n8n_executions` - Manage executions - `n8n_evaluations` - Evaluation test runs: reads (n8n 2.30+), run/cancel (n8n 2.32+) - `n8n_manage_datatable` - Data table and row management - `n8n_manage_credentials` - Credential CRUD + schema discovery - `n8n_audit_instance` - Security audit (built-in + custom scan) - `n8n_delete_workflow` - Permanently delete workflows - `n8n_list_workflows` - List workflows with filtering - `n8n_update_full_workflow` - Full workflow replacement **Related**: - [SEARCH_GUIDE.md](SEARCH_GUIDE.md) - Find nodes to add - [VALIDATION_GUIDE.md](VALIDATION_GUIDE.md) - Validate workflows