# n8n MCP Agent Workflow in Production: Where the Agent Stops

- Author: Abdullah Chaudary
- Category: Self-Hosted AI
- Published: 2026-10-12T19:50:38.546Z
- Updated: 2026-10-12T19:50:38.658Z
- Reading time: 11 min
- Tags: n8n, MCP, AI Agents, Workflow Automation, Self-Hosted AI

> In an n8n MCP agent workflow, the agent drafts and n8n holds publishing. Postiz's schedulePostTool takes a type of draft, schedule or now, so the limit is a parameter n8n must pin.

One field decides whether an agent drafts a post or publishes it. Postiz's MCP server has a single tool for creating posts, `schedulePostTool`, and each post it takes has a `type` of `draft`, `schedule` or `now`. In an n8n MCP agent workflow in production, where the agent stops is therefore a parameter on one tool, not a choice between tools, and a line in a prompt is the weakest place to hold it.

I build content-creation and automated-posting workflows for 10+ clients and 4+ of my own projects: agents draft, n8n routes, Postiz schedules and posts, and both n8n and Postiz are self-hosted and exposed over MCP. My n8n is a single instance behind nginx and Cloudflare, and nothing has broken in production. So this is the boundary set up step by step, plus what a demo showed when I broke the parts that can fail.

Every figure comes from that demo, run on 12 October 2026 against n8n 2.42.7 and the MCP TypeScript SDK 1.32.1. No AI Agent node or LLM was involved: an SDK client called the tools directly and stood in for the agent. The Postiz and human-review parts come from documentation, and I did not run them.

**TL;DR:** Give the agent the judgement calls and keep publishing in n8n. Select only the tools the agent needs, gate `schedulePostTool` with n8n's tool-level human review (since 2.6.0), and pin `type: draft` in a fixed step. In the demo, Retry On Fail was capped at 5 tries and 5 s, and a tool's own rejection text came back as a success.

## Where Does the Agent Stop in an n8n MCP Agent Workflow in Production?

The workflow ends wherever a correct output can be written down in advance. Triggers, schedules, branching, retries, length checks and the final call to Postiz are n8n nodes. The agent gets two decisions that need judgement: drafting copy for one channel, and choosing which brand rules apply to it. The line I would draw is that the agent proposes a post and only n8n lets one leave.

The test for each step is whether its correct output can be stated as a function of its input. A length check can be stated and a tone call cannot. The first goes in a node, and the second goes to the agent, which in n8n's words "decides which tools to call to complete a task."

Postiz makes the split harder than a tool list suggests. Its docs describe `schedulePostTool` as "Schedule, draft, or immediately publish posts to social media platforms," so there is no publish tool to leave out. Three controls hold the line instead, and none is the prompt:

- **Tools to Include: Selected** on the MCP Client Tool node, so the agent only sees the tools you name.
- **Tool-level human review** on `schedulePostTool`, so a call cannot run until a person approves it.
- **A fixed step that forces ****type: draft**, so the workflow sets the field and the model does not.

A system prompt that says "only draft" is still worth writing, but the model can ignore it: the same gap as [rules an AI tool reads but nothing enforces](/blog/agents-md-rulebook-ai-coding-tools).

## How Do You Expose and Consume Tools Over MCP in n8n?

Use the MCP Server Trigger to expose your own tools and the MCP Client Tool to consume Postiz's. The trigger connects only to tool nodes, serves SSE and streamable HTTP, does not support stdio, and offers None, Bearer or Header authentication. The client tool filters a server's tools with Tools to Include: All, Selected or All Except.

Postiz's setup page gives the endpoint as `https://<host>/mcp/<api-key>`, or `/mcp` with a Bearer header, over Streamable HTTP. I would use the header, since a key in the path ends up in access logs. n8n's Client Tool page names an "SSE Endpoint" parameter, so check that your node version speaks streamable HTTP first. I did not run that pairing.

For the demo I built the other direction, a trigger exposing two tools, so a real client could call them. Trimmed from the workflow JSON:

```json
{
  "name": "MCP Server Trigger",
  "type": "@n8n/n8n-nodes-langchain.mcpTrigger",
  "parameters": { "authentication": "bearerAuth", "path": "content-pipeline" }
},
{
  "name": "queue_post_for_review",
  "type": "@n8n/n8n-nodes-langchain.toolCode",
  "parameters": {
    "name": "queue_post_for_review",
    "description": "Queues a drafted post for HUMAN REVIEW. It does not publish or schedule anything. Returns a review id, or REJECTED with the reason.",
    "specifyInputSchema": true,
    "schemaType": "manual"
  }
}
```

The tool takes `channel` (an enum of `linkedin` and `x`), `text` and an optional `article_url`. The client is the SDK's own:

```js
const transport = new StreamableHTTPClientTransport(new URL(process.env.MCP_URL), {
  requestInit: { headers: { Authorization: `Bearer ${process.env.MCP_TOKEN}` } },
});
const client = new Client({ name: 'demo-client', version: '1.0.0' });
await client.connect(transport);
const res = await client.callTool({
  name: 'queue_post_for_review',
  arguments: { channel: 'x', text: 'x'.repeat(300) },
});
```

The capture showed three things:

- **The node is the contract.** The tool name and description the client received were the node's fields, verbatim, and a tool with no input schema was advertised as `{"input": string}`. Whatever the agent knows about a tool is what those fields say, so "It does not publish or schedule anything" belongs in the description.
- **A bad or missing token returns 403, not 401.** The SDK reported `Authorization data is wrong!`, `curl` showed `HTTP/1.1 403 Forbidden`, and there was no `WWW-Authenticate` header.
- **Version negotiation steps down.** A client asking for `2025-11-25` got it. A client asking for `2026-07-28`, the revision that removes the `initialize` handshake, got `2025-11-25` back, handshake and `mcp-session-id` header included.

n8n's docs say to disable proxy buffering on the MCP endpoint. Behind nginx 1.31.6 with `proxy_buffering` on and then off, I saw no difference: the SSE endpoint event arrived in about 0.2 s either way and every call completed. The nginx test was a short session with no gzip, Cloudflare or long calls, so it does not show that disabling proxy buffering on the MCP endpoint is unnecessary.

## Which Tool Calls Need a Human to Approve Them?

Any call that changes something outside n8n needs a human, and for Postiz that means `schedulePostTool` whenever `type` could be `schedule` or `now`. n8n 2.6.0 (released 2026-01-26) added tool-level review: a gated tool "cannot execute unless a human explicitly approves the action," including the MCP Client Tool and sub-workflows.

On approval the tool runs with the AI's input. On denial the action is cancelled and the AI is told. The docs list nine review channels, from Chat and Slack to Gmail, and expose `$tool.name` and `$tool.parameters` for the reviewer's message. The setup, in order:

1. Set Tools to Include to **Selected** on the Postiz MCP Client Tool node and pick tools by name. The agent never sees the rest of the 13 the docs list.
2. Add human review on `schedulePostTool`. Build the message from `$tool.parameters` so `type` is the first line read, since a reviewer who sees only the post text can approve a `now` without noticing.
3. Tell the system prompt what a denial means, so the agent revises or stops instead of retrying the same call.
4. Where the agent can hand back its draft as output, do not give it `schedulePostTool` at all. A regular node after the agent posts to Postiz with `type` fixed to `draft`.

Withholding `schedulePostTool` from the agent, so a regular node posts with `type` fixed to `draft`, is the option I would pick first, because a call the agent never makes needs no approval. I wrote about [where human review belongs in an LLM pipeline](/blog/claude-api-ocr-financial-aid-letters) for a Claude API pipeline: confidence has to come from checks. Tool gating applies that rule, confidence from checks, to tool calls as well.

## What Fails in an n8n Agent Workflow, and Which Setting Catches It?

Three different things fail, each with a different catcher. A tool call that fails once is caught by Retry On Fail. A failed execution is caught by an error workflow. A tool that returns a wrong answer as ordinary content is caught by nothing in n8n, and needs a check you write.

### Layer 1: Retry On Fail Covers a Blip, Not an Outage

On n8n 2.42.7, Retry On Fail is capped at runtime at 2 to 5 tries with 0 to 5,000 ms between them, whatever the workflow JSON says. The [docs on nodes](https://docs.n8n.io/build/understand-workflows/workflow-components/work-with-nodes/) state no limits; the editor source, `ndv.utils.ts`, sets `maxValue` 5 and 5000. I checked the runtime with a counter server that answered every request with 503 and logged each hit:

- **maxTries**** 10:** the server received 5 requests.
- **waitBetweenTries**** 10000:** the gaps were about 5.0 s.
- **waitBetweenTries**** 0:** the gaps were about 1.0 s, because the engine reads `node.waitBetweenTries || 1000` and 0 is falsy.

The public API stored every out-of-range value without complaint. Five tries with four 5 s waits is about 20 seconds, so retries absorb a blip and nothing longer. The execution data added two details:

- **The try count is not recorded.** The node shows `runs=1` whatever the number of tries, so I counted at the receiving end. Log it on the server you call if you need it.
- **On Error: Continue still retried.** The node tried 3 times, then the execution finished with `status=success` and the error object passed downstream as normal output. [n8n issue #9236](https://github.com/n8n-io/n8n/issues/9236), opened in April 2024 and now closed, reported that Max Tries and Wait Between Tries are ignored when On Error is set to Continue on the HTTP Request node. The claim in issue #9236 did not reproduce on n8n 2.42.7. A Continue node turns a persistent failure into a success downstream.

### Layer 2: An Error Workflow Catches a Failed Execution

An error workflow runs when an execution fails, and it must start with the Error Trigger node. Its default payload carries `execution.id`, `execution.url`, `execution.error` and `execution.lastNodeExecuted`. The trigger only fires on automatic executions, so a manual run cannot test it. The Stop And Error node sends it custom messages, so a check can turn a wrong result into a failure.

### Layer 3: A Wrong Answer That Is Not an Error

Neither retry nor an error workflow fires when a tool returns a failure as ordinary content. In the demo, `queue_post_for_review` answered a 300-character X post with `REJECTED: 300 characters, x limit is 280. Nothing queued.` and no `isError` flag, and the SDK call resolved. To the client that looks like success, and the Error Trigger runs only for failed executions.

Bad input behaves differently. A `channel` of `instagram` and a missing `channel` were both refused before the tool ran, with `isError: true` and a message such as `Invalid enum value. Expected 'linkedin' | 'x', received 'instagram'`. The input schema is a free first gate, and an enum on the channel is worth the few lines.

The check I would add sits where n8n owns it. A node after the agent tests the structured result (here, `status` equals `queued_for_review` and `published` is `false`) and routes anything else to Stop And Error, which feeds layer 2. I did not run that wiring.

## What Should an n8n MCP Workflow Log, and Where Does It Go?

On the n8n side, execution data and the error workflow's payload are the log. For a stdio MCP server, `stdout` carries protocol messages only and logs go to `stderr`. The stdout-only rule does not apply to n8n's MCP Server Trigger, which serves HTTP, but it applies to any stdio server an agent connects to.

The MCP spec says the server "MUST NOT write anything to its `stdout` that is not a valid MCP message" and "MAY write UTF-8 strings to `stderr` for any logging purposes," in both the 2025-11-25 and 2026-07-28 revisions. The MCP spec does not require a stdio server to write its logs to stderr.

Stdio still turns up around n8n: its docs route Claude Desktop through `mcp-remote`, a gateway to stdio-based servers. I reproduced the failure with one tool, where the only difference is where `log` writes:

```js
const log = process.env.LOG_TO === 'stdout' ? console.log : console.error;
// inside the tool handler:
log(`debug: counting ${text.length} characters`);
```

With SDK 1.32.1:

- **console.log****, with newline:** each call raised `SyntaxError: Unexpected token 'd', "debug: cou"... is not valid JSON` on the client's `onerror` hook. The line was dropped and the call still returned in 12 ms and 3 ms. A client that does not watch `onerror` sees nothing wrong.
- **The same text without a trailing newline:** the log fragment and the JSON-RPC response landed on one line, the line failed to parse, and the call timed out after 5,007 ms with `McpError -32001`. My client set a 5 s timeout. The SDK default is 60,000 ms, so an unconfigured client would hang for a minute.
- **console.error****:** no client errors, and the lines arrived on stderr.

## Limitations: What Does This Not Cover?

None of the figures here come from my production pipelines. Each comes from the 12 October 2026 demo, and several claims come from documentation I did not run.

- **Versions.** n8n 2.42.7 (image `n8nio/n8n:latest`, pulled that day), SDK 1.32.1, Node.js v24.14.1, nginx 1.31.6. n8n ships weekly, and the retry caps are runtime behaviour of this version.
- **No agent.** How a model reacts to a denial, chooses among tools or reads a `REJECTED` string was not tested.
- **Not run: Postiz, the 2.6.0 review and the fixed step.** Postiz's behaviour comes from its docs, the review from n8n's docs, and the `type: draft` step is my own design.
- **Retry scope.** I tested the HTTP Request node. Tools attached to an agent pass through a second copy of the clamp, in `get-input-connection-data.js`, which uses `??` and would keep a 0 wait. I read that path and did not run it.
- **Proxy scope.** The nginx test excluded gzip, Cloudflare, long calls and queue mode. With multiple webhook replicas, n8n's docs say to route all `/mcp*` requests to one replica. I run a single instance and did not test that.

[More Self-Hosted AI field notes](/blog/category/self-hosted-ai) are in the category.

A prompt can ask an agent to only draft, but only a parameter that n8n sets can make it so.

If you run Postiz, or any one-tool publisher, behind n8n's MCP Client Tool, what does your reviewer see when `type` is `now`, and how did you build that message so it is the first thing read?

## References

- Postiz MCP docs: [introduction](https://docs.postiz.com/mcp/introduction.md) (13 tools), [tools](https://docs.postiz.com/mcp/tools.md) (`schedulePostTool`, `type`), [setup](https://docs.postiz.com/mcp/setup.md) (endpoint, Bearer, Streamable HTTP).
- n8n docs: [MCP Server Trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-langchain.mcptrigger/), [MCP Client Tool](https://docs.n8n.io/integrations/builtin/cluster-nodes/sub-nodes/n8n-nodes-langchain.toolmcp/), [AI Agent](https://docs.n8n.io/integrations/builtin/cluster-nodes/root-nodes/n8n-nodes-langchain.agent/), [Human-in-the-loop for tools](https://docs.n8n.io/build/integrate-ai/ai-examples/human-in-the-loop-for-tools/), [Handle errors gracefully](https://docs.n8n.io/build/flow-logic/handle-errors-gracefully/), [Error Trigger](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.errortrigger/), [Work with nodes](https://docs.n8n.io/build/understand-workflows/workflow-components/work-with-nodes/).
- [n8n issue #9236](https://github.com/n8n-io/n8n/issues/9236): retry settings and On Error: Continue, read 2026-10-12.
- [n8n 2.x release notes](https://docs.n8n.io/changelog/release-notes-2.x/): entry `n8n@2.6.0`, 2026-01-26.
- n8n source: [`ndv.utils.ts`](https://github.com/n8n-io/n8n/blob/master/packages/frontend/editor-ui/src/features/ndv/shared/ndv.utils.ts) (retry limits, `master`, read 2026-10-12), and `workflow-execute.js` and `get-input-connection-data.js` from the `n8n-core` package inside the 2.42.7 image.
- MCP specification, stdio transport: [2026-07-28](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio) and [2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports).
- The demo workflow, SDK client, stdio server and captures from 12 October 2026: the source for every measured figure above.
