Audit logging for n8n workflows that call MCP tools

Audit logging for n8n workflows that call MCP tools

If you run an n8n workflow that calls MCP tools, here’s something you’ve probably hit. The execution log shows the MCP Client Tool node ran. It doesn’t show what the agent asked the tool to do, or what it got back.

That’s a gap. If the agent loops, or hits a tool you didn’t expect, or sends arguments you wouldn’t have approved, the execution log just says “MCP Client Tool: success.”

I built Cordon for this gap.

What Cordon is

Cordon is an MCP gateway that sits between your MCP client and any upstream MCP server, logs every tool call, and lets you write policies that block or pause calls before they fire. Open source, MIT, free.

n8n’s MCP Client Tool node takes a URL and a bearer token. That’s also exactly what Cordon’s HTTP transport exposes. So the swap is a one-field change. Point the node at Cordon, point Cordon at the upstream server, and you get an audit trail of every call your agent makes through n8n.

Setup

Five minutes if you already have n8n running and at least one MCP server you want to put behind it.

1. Install Cordon

npm install -g @getcordon/cli

2. Write a config

Cordon reads a cordon.config.ts in the directory you run it from. Point it at whatever MCP server your n8n workflow currently uses.

import { defineConfig } from '@getcordon/policy';

export default defineConfig({
  servers: [
    {
      name: 'filesystem',
      transport: 'stdio',
      command: 'npx',
      args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir'],
      policy: 'allow',
    },
  ],
  gateway: {
    transport: 'http',
    port: 7777,
    authToken: process.env.CORDON_GATEWAY_TOKEN,
  },
  audit: { enabled: true, output: 'stdout' },
});

3. Start the gateway

export CORDON_GATEWAY_TOKEN=$(openssl rand -hex 16)
cordon start --http

Cordon spawns the upstream MCP server as a child process and listens on http://127.0.0.1:7777/mcp. The stderr line you’re waiting for looks like this.

[cordon] HTTP gateway listening on http://127.0.0.1:7777/mcp

4. Point n8n at Cordon

In your n8n workflow, open the MCP Client Tool node. Set Server Transport to HTTP Streamable. The SSE option is deprecated, and Cordon serves Streamable HTTP at /mcp. Set the endpoint URL to:

http://127.0.0.1:7777/mcp

Set Authentication to Bearer and paste in your CORDON_GATEWAY_TOKEN. Save.

If you’re running n8n in Docker and Cordon outside it, swap 127.0.0.1 for your host’s IP, or run Cordon in the same Docker network and use the container name. If you’re hosting Cordon behind a reverse proxy, the URL becomes https://<your-domain>/mcp and everything else is the same.

What you see

Run your workflow. Cordon prints a line of audit JSON to stderr for every tool call, with the tool name, the arguments, the response timing.

{"event":"tool_call_received","timestamp":1748000000000,"callId":"call_001","toolName":"read_file","args":{"path":"/data/notes.md"}}
{"event":"tool_call_completed","timestamp":1748000000350,"callId":"call_001","toolName":"read_file","durationMs":350}

That’s the visibility n8n’s execution log doesn’t give you. Now you can see what the agent actually asked the tool to do.

If you want a friendlier surface, run cordon login to authenticate against the hosted dashboard and switch your config’s audit output to 'hosted'. Same events, browser table, sortable, exportable.

Gating dangerous calls

You can also tag specific tools with policies. Change a tool’s action to 'approve' and Cordon pauses for a human before the call fires. Slack or terminal prompt. Useful when an agent has access to a tool you don’t fully trust the agent to use unsupervised.

tools: {
  delete_file: { action: 'block', reason: 'Delete is for manual ops only.' },
  write_file:  { action: 'approve', reason: 'Writes need human review.' },
}

Try it

If you’re running n8n workflows with MCP tools, adding Cordon is a config change and a node-URL swap and boom! You’re seeing what’s happening.

→ getcordon.com

If you want a worked example with a real production MCP server in front, here’s how I run Cordon in front of Agent Toolbelt (27 tools my agents call for stock research, schema gen, regex and the small stuff). Same swap, more concrete.

→ Putting my own MCP server behind my own MCP gateway — Marco Arras

1 Like

The gap you’re describing is real - n8n’s execution log just records “MCP Client Tool: success” with no payload detail. Cordon is a clean solution for teams who need that full audit trail. One lightweight alternative for simpler cases: wrap your MCP Client Tool node in a sub-workflow and add a logging step (write to Google Sheets, Postgres, or even a simple HTTP Request to your log sink) before and after the call - you can capture the tool name, input arguments, and response manually that way without the extra infrastructure.

We’ve also just added support for Open Telemetry, which will give you this data too.

1 Like

Good call, I hadn’t dug into n8n’s OpenTelemetry support yet. Curious about the granularity, since that’s the whole question for this use case. Do the spans for the MCP Client Tool node carry the actual tool name and the arguments the agent sent and the response it got back, or is it node level execution data like “did it run” and duration?

If the spans carry the tool call payload, then for n8n originated traffic that closes the gap natively and is a clean answer, no proxy needed. If it’s node level, the args and response gap is still there, which is what sent me to a gateway in the first place.

Either way oTel is the right primitive for the n8n side. The place a gateway still adds something is calls to the same mcp server from outside n8n, claude desktop or a script, landing in one trail, plus blocking a call before it fires rather than recording it after. Would love a pointer to what the spans actually capture if you have one.

Appreciate that. And yeah, the subworkflow wrapper is a great call for the simpler case. Sheets or Postgres before and after the node gets you the tool name, args, and response with zero extra infrastructure, which is exactly the right move when you just want a record for one workflow.

The one spot I’d reach for the gateway over the wrapper is when you want to stop a call rather than log it after the fact. The wrapper sees the call once it has already returned. Cordon can pause or block before it fires, which matters when a tool can do something you wouldn’t want to undo. For pure audit though, your approach is lighter and totally legit.

Marco’s OpenTelemetry question is the right acceptance test: for one run, can you reconstruct the actual MCP tool call, not just node success?

I would test the same harmless MCP call three ways:

1. n8n execution / OpenTelemetry span

2. before-and-after wrapper workflow log row

3. gateway audit event, if one is in the path

The fields that decide it for me are tool name, input args or payload hash, response or response hash, policy verdict before execution for mutating tools, and correlation id back to the n8n execution.

If n8n spans carry those, native tracing is the cleanest answer for n8n-only traffic. If they do not, a wrapper is enough for one workflow and a gateway earns its keep when multiple clients hit the same MCP server or when you need to block before the tool fires.