n8n and AI Agents: Workflows Where Every Step Is Visible
Most companies will meet AI agents not in a coding tool but in a business workflow: chasing invoices, triaging email, answering staff questions. n8n is where a lot of that gets built. What n8n 2.x offers for agents (the AI Agent node, MCP in both directions, human review of tool calls, the new Agents preview), a self-hosted setup that will survive production, two tested workflows you can import, and the licence question every services company has to answer.

Bản tiếng Việt: n8n và AI agent: quy trình mà bước nào cũng nhìn thấy được. Part 12 of the series. Previous: Paseo. The series starts at The Agent Loop.
The request came from a client's finance team, not their engineers: "We want an AI that chases overdue invoices." What they meant, once we sat with them for an afternoon, was this. Every morning, find invoices past due. For each one, look up the customer and the contract terms, draft a reminder in Vietnamese or English depending on the customer, and send it, but only after someone in finance has looked at it. Log what was sent.
Two parts of that need judgment: deciding what the reminder should say, and handling the replies. Everything else is plumbing between an accounting system, a mailbox and a chat tool. And the person who would own it after we left was an operations analyst who does not write Python but reads flowcharts all day.
That combination, a little judgment wrapped in a lot of integration, owned by someone who is not a developer, is where most companies will meet agents first. n8n is the tool we reach for in that situation, and this post is about using it well.
What n8n is in 2026
n8n is a workflow automation platform: a canvas where you connect triggers (a webhook, a schedule, a new email, a chat message) to nodes that do things (call an API, query a database, transform data, branch), with hundreds of integrations built in. It runs as n8n Cloud or self-hosted, and the self-hosted version is free for internal use under n8n's fair-code licence, which I come back to at the end because it matters for agencies.
The 2.0 release in December 2025 was a hardening release, and if you last looked at n8n on 1.x some defaults have changed under you. Code nodes now run in separate task runners. Code nodes can no longer read environment variables unless you allow it (N8N_BLOCK_ENV_ACCESS_IN_NODE defaults to true). The Execute Command and Local File Trigger nodes are disabled by default. File nodes are restricted to one directory. OAuth callbacks require authentication. The Activate toggle became Publish and Unpublish, MySQL and MariaDB are gone as storage backends, and the old Pyodide-based Python support was removed in favour of native Python in external task runners. All of that is good for anyone running agents, which are, after all, programs that decide at runtime what to do.
For agents specifically, n8n now gives you four things:
- the AI Agent node, which puts a tool-calling loop inside a workflow, with a chat model, memory and tools attached as sub-nodes;
- MCP in both directions: an MCP Server Trigger that exposes workflows as tools to any MCP host, and an MCP Client Tool that lets an n8n agent use any MCP server;
- human review of tool calls, so a person approves specific actions in Slack, Teams, email or chat before they run;
- and since 2.32, Agents (in preview): standalone assistants built in an Agent Builder, reachable from Slack, Telegram, Linear, schedules and workflows.
The way I think about it is the graph-engineering idea from part 9 with a canvas on top. The deterministic steps are ordinary nodes, visible and testable. The judgment is one AI Agent node with a small, explicit set of tools. Anyone who can read a flowchart can see what the system does and where the model is allowed to decide.
A self-hosted setup that will survive production
The one-line docker run in the docs is fine for trying n8n. For anything you will depend on, run Postgres instead of the default SQLite, set an encryption key you control, and run task runners as a separate container. This is the compose file we start from:
# compose.yaml — n8n 2.x with Postgres and external task runners
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set in .env}
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 5s
retries: 10
n8n:
image: n8nio/n8n:2.42.7
restart: unless-stopped
ports:
- "127.0.0.1:5678:5678" # behind a reverse proxy, never straight to the internet
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY:?set in .env} # encrypts stored credentials; back it up
WEBHOOK_URL: https://n8n.example.com/
GENERIC_TIMEZONE: Asia/Ho_Chi_Minh
TZ: Asia/Ho_Chi_Minh
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: "true"
N8N_DIAGNOSTICS_ENABLED: "false"
N8N_RUNNERS_MODE: external
N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN:?set in .env}
volumes:
- n8n_data:/home/node/.n8n
depends_on:
postgres:
condition: service_healthy
runners:
image: n8nio/runners:2.42.7 # must match the n8n version exactly
restart: unless-stopped
environment:
N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
depends_on:
- n8n
volumes:
pg_data:
n8n_data:
Four details are worth explaining.
Pin the version, and keep the runners image in lockstep. The docs say the n8nio/runners tag must match n8nio/n8n exactly. Upgrade both in one commit.
N8N_ENCRYPTION_KEY is the key to every credential you store. n8n generates one on first start if you do not set it, and keeps it in the data volume. Set it yourself, keep it in your secret manager, and back it up separately from the database. A database backup without the key is a box of encrypted API tokens nobody can open.
External task runners are where Code nodes run. Internal mode still works, and n8n 2.42 prints a deprecation warning for it on startup: external mode is the future, for isolation and for scaling. Code written by people, and increasingly by AI workflow builders, should not run inside the main process that holds your credentials.
Bind to localhost and put a proxy in front. n8n serves the editor, webhooks and MCP endpoints on the same port. Terminate TLS at a reverse proxy, and if you expose MCP endpoints through it, follow the docs and turn off proxy buffering, gzip and chunked transfer encoding for /mcp paths, or streaming responses will stall. If you run n8n in queue mode with several webhook replicas, route all /mcp* traffic to one dedicated replica; MCP connections break if their requests land on different replicas.
Example 1: company tools as an MCP server
The first thing I build for a client is usually not an agent at all. It is a small set of read-only tools over their systems, exposed through MCP, so every agent they use (Claude, an IDE agent, an n8n agent) can reach the same data through one controlled door. In n8n that door is the MCP Server Trigger.
This workflow exposes one tool, lookup_invoice. In production the tool body is a Postgres or HTTP Request node against the accounting system; here a Code Tool with a small table keeps it self-contained. Paste it onto the editor canvas, or import it with n8n import:workflow --input=invoice-tools.json (the CLI import needs the top-level id):
{
"id": "invoiceToolsMcp",
"name": "Invoice tools (MCP)",
"nodes": [
{
"name": "MCP Server Trigger",
"type": "@n8n/n8n-nodes-langchain.mcpTrigger",
"typeVersion": 2.1,
"position": [0, 0],
"webhookId": "5f0c7d3e-9a52-4f3b-9d0e-3b1f7a2c6e11",
"parameters": {
"path": "invoices",
"authentication": "bearerAuth",
"instructions": "Read-only lookups in the accounting system. Use lookup_invoice with an id like INV-2026-0412."
},
"credentials": { "httpBearerAuth": { "id": "mcpBearer01", "name": "MCP bearer (invoices)" } }
},
{
"name": "lookup_invoice",
"type": "@n8n/n8n-nodes-langchain.toolCode",
"typeVersion": 1.3,
"position": [0, 220],
"parameters": {
"description": "Look up one invoice by id (e.g. INV-2026-0412). Returns customer, amount in VND, due date and status. Unknown ids return an error listing valid ids.",
"language": "javaScript",
"specifyInputSchema": true,
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"invoice_id\": { \"type\": \"string\", \"description\": \"Invoice id, e.g. INV-2026-0412\" }\n },\n \"required\": [\"invoice_id\"]\n}",
"jsCode": "// In production this is an HTTP Request or Postgres node; a table keeps the demo self-contained.\nconst invoices = {\n 'INV-2026-0412': { customer: 'Công ty Xây dựng An Phát', amount_vnd: 485000000, due: '2026-10-31', status: 'unpaid' },\n 'INV-2026-0398': { customer: 'Acme Pty Ltd', amount_vnd: 120000000, due: '2026-10-15', status: 'paid' },\n};\nconst inv = invoices[query.invoice_id];\nif (!inv) {\n return `unknown invoice ${query.invoice_id}; known ids: ${Object.keys(invoices).join(', ')}`;\n}\nreturn JSON.stringify({ id: query.invoice_id, ...inv });"
}
}
],
"connections": {
"lookup_invoice": { "ai_tool": [[{ "node": "MCP Server Trigger", "type": "ai_tool", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
After importing, create a Bearer Auth credential with a long random token, select it on the trigger (the credential id in the JSON will not exist on your instance), and publish the workflow. The trigger has a test URL, live only while you are listening in the editor, and a production URL, live once the workflow is published. Agents should only ever see the production one.
Everything from part 7 applies to how the tool is written. The description says what the tool returns and what to pass. The input has a schema. An unknown id returns a message that lists valid ids instead of an empty result, so the model can correct itself.
Because this is plain MCP, the Python client from part 7 can call it. With the token in a header:
# call_n8n_mcp_auth.py — the MCP client from part 7, pointed at an n8n workflow
import asyncio
import os
from mcp import Client
from mcp.client.streamable_http import create_mcp_http_client, streamable_http_client
URL = "http://127.0.0.1:5678/mcp/invoices" # production URL of the MCP Server Trigger
http = create_mcp_http_client(headers={"Authorization": f"Bearer {os.environ['N8N_MCP_TOKEN']}"})
async def main():
async with Client(streamable_http_client(URL, http_client=http)) as client:
print("protocol:", client.protocol_version)
print("tools:", [t.name for t in (await client.list_tools()).tools])
for invoice_id in ["INV-2026-0412", "INV-9999"]:
r = await client.call_tool("lookup_invoice", {"invoice_id": invoice_id})
print(invoice_id, "->", r.content[0].text)
asyncio.run(main())
protocol: 2025-11-25
tools: ['lookup_invoice']
INV-2026-0412 -> {"id":"INV-2026-0412","customer":"Công ty Xây dựng An Phát","amount_vnd":485000000,"due":"2026-10-31","status":"unpaid"}
INV-9999 -> unknown invoice INV-9999; known ids: INV-2026-0412, INV-2026-0398
Without the token, the same tool call gets 403 Authorization data is wrong!. Note the protocol line: n8n 2.42 speaks the 2025-11-25 revision with sessions, not the stateless 2026-07-28 one, and the 2.x Python SDK falls back to it automatically. It will matter if you plan to scale n8n's MCP endpoints horizontally; until n8n adopts the new revision, the one-replica routing rule above stands.
For Claude Code it is the same command as for any remote MCP server:
claude mcp add --transport http --scope project invoices https://n8n.example.com/mcp/invoices \
--header "Authorization: Bearer ${N8N_MCP_TOKEN}"
One quirk from the docs to save you an hour: claude.ai asks users to sign in to n8n when connecting to an MCP Server Trigger even if authentication is set to none. It is the only client known to do that.
Example 2: an agent inside a workflow
Now the agent itself. The finance team talks to it in n8n's chat; it looks invoices up through the MCP server above and drafts reminders. The structure is the AI Agent node with three sub-nodes: a chat model, memory, and the MCP Client Tool pointed at our invoice server.
{
"id": "collectionsAgent",
"name": "Collections assistant",
"nodes": [
{
"name": "When chat message received",
"type": "@n8n/n8n-nodes-langchain.chatTrigger",
"typeVersion": 1.5,
"position": [0, 0],
"webhookId": "0b6f3f8e-3c5e-4d7a-9a51-6e2f4c1d8b22",
"parameters": {}
},
{
"name": "AI Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [260, 0],
"parameters": {
"options": {
"systemMessage": "You help the finance team chase overdue invoices.\nAlways look invoices up with lookup_invoice; never guess amounts or dates.\nAnswer in the language the user writes in. Amounts are in VND.\nYou cannot send anything yourself. If a reminder is needed, draft it and say who should approve it.",
"maxIterations": 6
}
}
},
{
"name": "Anthropic Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"typeVersion": 1.6,
"position": [120, 220],
"parameters": {
"model": { "__rl": true, "mode": "list", "value": "claude-sonnet-5", "cachedResultName": "Claude Sonnet 5" },
"options": {}
}
},
{
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.4,
"position": [280, 220],
"parameters": { "contextWindowLength": 10 }
},
{
"name": "Invoice tools",
"type": "@n8n/n8n-nodes-langchain.mcpClientTool",
"typeVersion": 1.4,
"position": [440, 220],
"parameters": {
"endpointUrl": "http://localhost:5678/mcp/invoices",
"serverTransport": "httpStreamable",
"authentication": "bearerAuth",
"include": "selected",
"includeTools": ["lookup_invoice"],
"options": {}
},
"credentials": { "httpBearerAuth": { "id": "mcpBearer01", "name": "MCP bearer (invoices)" } }
}
],
"connections": {
"When chat message received": { "main": [[{ "node": "AI Agent", "type": "main", "index": 0 }]] },
"Anthropic Chat Model": { "ai_languageModel": [[{ "node": "AI Agent", "type": "ai_languageModel", "index": 0 }]] },
"Simple Memory": { "ai_memory": [[{ "node": "AI Agent", "type": "ai_memory", "index": 0 }]] },
"Invoice tools": { "ai_tool": [[{ "node": "AI Agent", "type": "ai_tool", "index": 0 }]] }
},
"settings": { "executionOrder": "v1" }
}
You will need to attach an Anthropic credential (or swap the model node for any other chat model) before it runs. I tested the MCP side of this on n8n 2.42.7 with the MCP Client node, which calls the same endpoint with the same credential without needing a model; the agent itself depends on your model key.
Three settings carry most of the safety.
maxIterations: 6. The agent loop from part 1, with a step limit chosen for the task. A lookup-and-draft assistant needs two or three tool calls. Six is generous. The default of ten is too many for most business tasks.
include: selected. The MCP Client Tool exposes only lookup_invoice, even if the server later grows a write tool. This is part 7's rug-pull defence applied at the client: the agent gets the tools you chose, not whatever the server offers today.
The system prompt says what the agent cannot do. It cannot send. That sentence is not the guardrail (the guardrail is that it has no sending tool), but it makes the agent's behaviour match its permissions, so it drafts and hands over instead of apologising that it failed.
Human review for the actions that matter
Eventually finance wanted the agent to send the reminders itself, with approval. This is where n8n's human review for tool calls is the right tool, and it is configured in the UI rather than the JSON.
Add a Gmail tool (or Outlook, or Slack) to the agent and let the model fill the recipient, subject and body with $fromAI(), for example {{ $fromAI('to', 'customer billing email') }}. Then open the agent's Tools panel, add a Human review step, choose the approval channel (Slack, Microsoft Teams, Gmail, Telegram, Discord, WhatsApp Business Cloud, Google Chat, Outlook or n8n Chat), and put the send tool behind it. The approval message can show exactly what the model wants to do:
The assistant wants to use {{ $tool.name }}:
{{ JSON.stringify($tool.parameters, null, 2) }}
On approve, the tool runs with the model's parameters. On deny, it does not run and the agent is told the request was rejected. The approval channel does not have to be the chat channel: finance chats with the agent in n8n Chat while approvals land in a Slack channel their manager watches. Then update the system prompt to say which tools need approval and what to do after a denial (tell the user, offer to revise the draft), or the agent will retry the same call.
This is part 4's risk tiers in a form an operations team can see and change. Reads run freely. Writes that can be undone run with logging. Writes that leave the company, like an email to a customer, wait for a person.
Agents (preview): the other way to build
Since 2.32, n8n also has Agents as a separate feature from the AI Agent node, currently in preview. You create one in a project's Agents tab, give it a model, instructions and tools (integrations, workflows, MCP servers, with approval for sensitive tools), optionally a knowledge base, memory and sub-agents, test it, and publish it. It can be reached from Slack, Telegram or Linear, run on a schedule, or be called from a workflow with the Message an Agent node.
It is a sensible direction: an agent as a first-class object with draft and published versions, instead of a node buried in a workflow. I would still build production processes with the AI Agent node for now, because a preview feature's behaviour can change, and because the workflow version makes the deterministic steps around the agent explicit. I use Agents for internal assistants where a change in behaviour costs little.
The licence question
n8n is not open source in the OSI sense. It is fair-code under the Sustainable Use License, with some enterprise features under a separate commercial licence. For a company using n8n for its own processes, this rarely matters. For a services company like ours, which builds automations for clients, it decides how projects are structured. The licence FAQ is clear on the main cases:
- Building, running and maintaining workflows on your own n8n instance on behalf of clients is permitted, including charging for that work, provided clients do not access your instance directly.
- Installing n8n on the client's own servers for them is fine, as long as you do not also sell them hosting, which would compete with n8n Cloud.
- Using n8n behind the scenes in your own product, where users trigger automations you built, is allowed. White-labelling n8n, or letting your customers build their own workflows inside an n8n you host, is not.
I am not a lawyer, and the FAQ is not a contract. When a deal falls between those cases, we ask n8n before we sign, not after.
When n8n is the wrong tool
n8n is excellent when the work is mostly integration with a little judgment, when the owner is not a developer, and when approvals and audit matter. It is the wrong tool when:
- the agent logic itself is the product: complex planning, many tools, custom context management. That belongs in code with a harness (part 2) and evals (part 5);
- you need serious testing and code review of the workflow logic. Workflows live as JSON in a database. Export them to git and review the diffs, but it is not the same as code;
- latency matters at the millisecond level, or volume is high enough that per-execution overhead adds up;
- the workflow has grown past what a person can read on one canvas. At that point it is a program, and it should be written as one.
A pattern that works well for us is to keep both: the hard agent in code, exposed as an MCP server or behind A2A (parts 7 and 8), and n8n for the triggers, integrations and approvals around it. Each side does what it is good at, and the operations team can still see every step.
That closes this run of the series. Twelve parts, from a forty-line loop to protocols, graphs, fleets of coding agents and business workflows. The tools will keep changing every few months. The habits underneath them have not changed since part 1: small explicit tools, bounded loops, verification that does not depend on the model agreeing with itself, and a human at the points where mistakes are expensive.
References
- n8n docs, MCP Server Trigger, Human-in-the-loop for tools, Build and manage agents
- n8n docs, 2.0 breaking changes and task runners
- n8n, Sustainable Use License FAQ
- n8n on GitHub