data/skills/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md
Complete guide for creating, updating, and managing n8n workflows.
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.
Speed: 100-500ms
Use when: Creating new workflows from scratch
Syntax:
n8n_create_workflow({
name: "Webhook to Slack", // Required
nodes: [...], // Required: array of nodes
connections: {...}, // Required: connections object
settings: {...} // Optional: workflow settings
})
Returns: Created workflow with ID
Example:
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:
activateWorkflow operation)Speed: 50-200ms | Uses: 38,287 (most used tool!)
Use when: Making incremental changes to workflows
Common pattern: 56s average between edits (iterative building!)
Node Operations (7 types):
addNode - Add new noderemoveNode - Remove node by ID or nameupdateNode - Update node properties (use dot notation)patchNodeField - Surgical string edits via strict find/replace (see below)moveNode - Change positionenableNode - Enable disabled nodedisableNode - Disable active nodeConnection 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 (1 type):
20. transferWorkflow - Transfer workflow to a different project (enterprise/cloud)
Always include intent for better responses:
n8n_update_partial_workflow({
id: "workflow-id",
intent: "Add error handling for API failures", // Describe what you're doing
operations: [...]
})
IF nodes - Use semantic branch names:
{
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:
{
type: "addConnection",
source: "Switch",
target: "Handler A",
case: 0
}
{
type: "addConnection",
source: "Switch",
target: "Handler B",
case: 1
}
Full support for AI workflows:
// 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
Remove properties by setting them to null:
// 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
}
}
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:
{
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:
// 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: "<p>Unsubscribe</p>", replace: ""}
]
}]
})
Error behavior (this is what makes it strict):
replaceAll → operation fails (ambiguity detected)Security limits:
(a+)+ and overlapping alternations like (\w|\d)+// 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"}]
})
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!
}
]
})
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:
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:
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
]
}]
})
nodeGroups: [] ungroups everything.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:
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.
cleanStaleConnections - Remove broken connections:
{type: "cleanStaleConnections"}
rewireConnection - Change target atomically:
{
type: "rewireConnection",
source: "Webhook",
from: "Old Handler",
to: "New Handler"
}
Best-effort mode - Apply what works:
n8n_update_partial_workflow({
id: "workflow-id",
operations: [...],
continueOnError: true // Don't fail if some operations fail
})
Validate before applying:
n8n_update_partial_workflow({
id: "workflow-id",
operations: [...],
validateOnly: true // Preview without applying
})
Speed: 200-500ms
Use when: Deploying a template directly to n8n instance
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:
Example:
// 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"]
Use when: Managing workflow history, rollback, cleanup
n8n_workflow_versions({
mode: "list",
workflowId: "workflow-id",
limit: 10
})
n8n_workflow_versions({
mode: "get",
versionId: 123
})
n8n_workflow_versions({
mode: "rollback",
workflowId: "workflow-id",
versionId: 123, // Optional: specific version
validateBefore: true // Default: validate before rollback
})
// 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
})
n8n_workflow_versions({
mode: "prune",
workflowId: "workflow-id",
maxVersions: 10 // Keep 10 most recent
})
Use when: Testing workflow execution
Auto-detects trigger type (webhook, form, chat)
// Test webhook workflow
n8n_test_workflow({
workflowId: "workflow-id",
triggerType: "webhook", // Optional: auto-detected
httpMethod: "POST",
data: {message: "Hello!"},
waitForResponse: true,
timeout: 120000
})
// Test chat workflow
n8n_test_workflow({
workflowId: "workflow-id",
triggerType: "chat",
message: "Hello, AI agent!",
sessionId: "session-123" // For conversation continuity
})
Speed: 50-500ms
Use when: Creating, updating, listing, or deleting credentials; discovering credential schemas
list - List all credentials (id, name, type, timestamps)get - Get credential by ID (data field stripped)create - Create credential (requires name, type, data)update - Update credential by ID (name, data, and/or type)delete - Permanently delete credential by IDgetSchema - Discover required fields for a credential typelist 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).
n8n_manage_credentials({action: "list"})
// → [{id, name, type, createdAt, updatedAt}, ...]
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
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.
// 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:
delete: confirm nothing references the credentialupdate: see exactly which workflows you'll affectn8n_audit_instance flags a credential: locate the workflows that need remediationBehavior and limits:
n8n_audit_instance)usageScanError field rather than failing the whole calln8n_manage_credentials({
action: "getSchema",
type: "httpHeaderAuth"
})
// → Required fields, types, descriptions for this credential type
n8n_manage_credentials({
action: "create",
name: "My Slack Token",
type: "slackApi",
data: {accessToken: "xoxb-your-token"}
})
// → Created credential (data field stripped from response)
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)
n8n_manage_credentials({action: "delete", id: "123"})
// 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"})
// 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-..."}
})
get, create, and update all strip the data field from responses (defense-in-depth — secrets are never returned)get falls back to list+filter when GET /credentials/:id returns 403/405 (endpoint not in all n8n versions)includeUsage: true triggers a workflow scan that fails, the response includes usageScanError and still returns the base credentials rather than erroring outSpeed: 500-5000ms (scans all workflows)
Use when: Auditing instance security, finding hardcoded secrets, checking for unauthenticated webhooks, verifying error handling
1. Built-in Audit (via n8n's POST /audit API):
credentials, database, nodes, instance, filesystem2. Custom Deep Scan (workflow analysis):
hardcoded_secrets — 50+ regex patterns for API keys/tokens/passwords plus PII detectionunauthenticated_webhooks — Webhook/form triggers without authenticationerror_handling — Workflows with 3+ nodes and no error handlingdata_retention — Workflows saving all execution data// 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
})
Returns an actionable markdown report with:
Detected secrets are masked in output — shows first 6 + last 4 characters only. Raw values are never stored or returned.
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)Use when: Validating workflow stored in n8n
n8n_validate_workflow({
id: "workflow-id",
options: {
validateNodes: true,
validateConnections: true,
validateExpressions: true,
profile: "runtime"
}
})
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 + metadatadetails - Full + execution stats (success/error counts, last run)active - The published (running) graph; returns code: "NO_ACTIVE_VERSION" if the workflow was never activatedstructure - 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 wholeminimal - ID, name, active, tags (fastest)// 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:
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.Use when: Managing workflow executions
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
})
n8n_executions({
action: "list",
workflowId: "workflow-id",
status: "error", // success, error, waiting
limit: 100
})
n8n_executions({
action: "delete",
id: "execution-id"
})
Use when: Reading evaluation test runs — polling a run started in the editor, comparing metrics across runs, pulling per-case results into a report or dashboard.
Read-only. Requires n8n >= 2.30 and an API key created on 2.30+ — keys created earlier silently lack the testRun scopes, so a 403 means "re-create the API key", not a bug. Runs exist only for workflows with an evaluation trigger that have been run from the n8n editor; triggering runs via the public API is not yet supported by n8n (planned upstream, will arrive as run/cancel actions).
n8n_evaluations({
action: "list_runs",
workflowId: "workflow-id",
status: "completed" // new, running, completed, error, cancelled
})
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)
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"})
Gotchas:
metrics across runs of the same workflow to catch prompt/model regressionsStandard 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})
n8n_update_partial_workflow({...})
// ↓ 23s (thinking about what to validate)
n8n_validate_workflow({id})
n8n_validate_workflow({id})
// ↓ 58s (fixing errors)
n8n_update_partial_workflow({...})
update → update → update → ... (56s avg between edits)
This shows: Workflows are built iteratively, not in one shot!
n8n_deploy_template for quick startsMost Important:
activateWorkflow operation)Additional Tools:
n8n_deploy_template - Deploy templates directlyn8n_workflow_versions - Version control & rollbackn8n_test_workflow - Trigger executionn8n_executions - Manage executionsn8n_evaluations - Read evaluation test runs (n8n 2.30+, read-only)n8n_manage_datatable - Data table and row managementn8n_manage_credentials - Credential CRUD + schema discoveryn8n_audit_instance - Security audit (built-in + custom scan)n8n_delete_workflow - Permanently delete workflowsn8n_list_workflows - List workflows with filteringn8n_update_full_workflow - Full workflow replacementRelated: