Skip to main content
Glama
NestorPVsf

n8n-workflows-mcp

by NestorPVsf
README.md
# n8n-workflows-mcp

**Turn your deployed n8n workflows into tools your AI agents can call — without rewriting anything.**

[![CI](https://github.com/NestorPVsf/n8n-workflows-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/NestorPVsf/n8n-workflows-mcp/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/n8n-workflows-mcp)](https://www.npmjs.com/package/n8n-workflows-mcp)

An MCP (Model Context Protocol) server that exposes the n8n workflows you already
have running in production as tools an AI agent can discover and execute. No SDK,
no rewriting integrations, no duplicating credentials inside the agent.

> This is **not** the official n8n MCP server. That one lets an agent *build*
> workflows through the n8n Workflow SDK. This one runs the workflows you
> already built — it is the operational complement, not a competitor.

![Demo: an agent listing and inspecting tagged n8n workflows](docs/demo.gif)

## Why

Thousands of teams have n8n automations deployed with real business logic:
tested integrations, configured credentials, flows already running in
production. AI agents can't use them today. The usual options are to
reimplement the integration inside the agent (duplicating both work and
secrets) or to manually bridge the chat and the automation by hand.

This server closes that gap: any workflow tagged for exposure becomes callable
through four generic tools (`list_workflows`, `get_workflow_schema`,
`execute_workflow`, `get_execution_status`) — it does not register a separate
MCP tool per workflow. That gets you three things:

- **Reuse** — an automation you already validated becomes an agent capability
  without rewriting a line of it.
- **Control and security** — the workflow runs on *your* infrastructure with
  *your* credentials; the agent never sees third-party API keys. The `mcp` tag
  is an explicit governance boundary: you expose what you decide, nothing else.
- **A standard, not a lock-in** — because it's MCP, it works with Claude, custom
  agents, and any current or future MCP client.

Useful if you're an automation consultant adding agents on top of existing n8n
stacks, a team that already invested in n8n and wants agentic AI without
migrating anything, or a maker wiring personal automations into an agent.

## How it works

```
 AI agent (MCP client)
        │  stdio (JSON-RPC)
        ▼
 n8n-workflows-mcp  ───────────────►  n8n REST API   (discovery, schema, execution status)
        │                              GET /workflows, /workflows/:id, /executions/:id
        └────────────────────────────► n8n webhook    (execution)
                                        POST/GET /webhook/:path
                                             │
                                             ▼
                                     your n8n instance
```

Discovery and execution are opt-in by design: only workflows tagged with a
configured tag (`mcp` by default) are ever listed, described, or run. Anything
without that tag is invisible to the agent, even if the API key used could
technically reach it.

## Quickstart

**Requirements**

- Node.js >= 22
- An n8n instance reachable over HTTP(S), with the REST API enabled
- An n8n API key (**Settings → n8n API → Create an API key**)

**1. Tag a workflow in n8n**

Open the workflow → Tags → add a tag named `mcp` (or whatever you set
`N8N_MCP_TAG` to). Only tagged workflows become visible to the agent.

**2. Point your MCP client at it**

Claude Desktop / Claude Code config (`claude_desktop_config.json` or
`.mcp.json`) — `npx` fetches the package on first run, no install step:

```json
{
  "mcpServers": {
    "n8n-workflows": {
      "command": "npx",
      "args": ["-y", "n8n-workflows-mcp"],
      "env": {
        "N8N_BASE_URL": "https://your-n8n-instance.example.com",
        "N8N_API_KEY": "your-api-key",
        "N8N_MCP_TAG": "mcp"
      }
    }
  }
}
```

Restart the client. The agent now has four tools scoped to whatever you've
tagged `mcp`.

**From source** (for development or to run a local build):

```bash
git clone https://github.com/NestorPVsf/n8n-workflows-mcp.git
cd n8n-workflows-mcp
npm install
npm run build
```

Then point the MCP client at the local build instead of `npx`, keeping the
same `env` block as above:

```json
{
  "mcpServers": {
    "n8n-workflows": {
      "command": "node",
      "args": ["/absolute/path/to/n8n-workflows-mcp/dist/index.js"],
      "env": {
        "N8N_BASE_URL": "https://your-n8n-instance.example.com",
        "N8N_API_KEY": "your-api-key",
        "N8N_MCP_TAG": "mcp"
      }
    }
  }
}
```

## Configuration

All configuration is via environment variables, read once at startup
(`src/config.ts`). Missing or invalid required values make the server fail
fast with a clear error instead of starting in a broken state.

| Variable | Required | Default | Notes |
|---|---|---|---|
| `N8N_BASE_URL` | Yes | — | Base URL of your n8n instance, no trailing slash. Must be a valid URL. |
| `N8N_API_KEY` | Yes* | — | REST API key, sent as `X-N8N-API-KEY` on every discovery request. Mutually exclusive with `N8N_API_KEY_FILE` — exactly one is required. |
| `N8N_API_KEY_FILE` | Yes* | — | Path to a file whose (trimmed) contents are the API key. Mutually exclusive with `N8N_API_KEY`. |
| `N8N_MCP_TAG` | No | `mcp` | Tag used as the discovery/execution boundary. |
| `N8N_WEBHOOK_AUTH_HEADER_NAME` | No | — | Name of a header sent on every webhook call. Requires a value (below) if set. |
| `N8N_WEBHOOK_AUTH_HEADER_VALUE` | No | — | Value of that header. Requires the name above if set. Mutually exclusive with `N8N_WEBHOOK_AUTH_HEADER_VALUE_FILE`. Leave unset (along with the file variant) to disable. |
| `N8N_WEBHOOK_AUTH_HEADER_VALUE_FILE` | No | — | Path to a file whose (trimmed) contents are the webhook auth header value. Mutually exclusive with `N8N_WEBHOOK_AUTH_HEADER_VALUE`. |

\* Exactly one of `N8N_API_KEY` / `N8N_API_KEY_FILE` must be set.

The `*_FILE` variants follow the Docker/Kubernetes secrets convention
(read the secret from a mounted file instead of an env var) and are the
**recommended** way to configure this server: they keep the actual secret out
of the MCP client's JSON config, which is often stored in plaintext, synced,
or checked into a dotfiles repo. The file is read once at startup, trimmed of
surrounding whitespace, and treated exactly like the direct env var from then
on. On POSIX systems, the server warns on stderr (without failing) if the
file is readable by group or other users, since that undermines the point of
keeping the secret out of the config file.

## Tools

| Tool | Arguments | Returns |
|---|---|---|
| `list_workflows` | none | Array of `{ id, name, description? }` — only workflows tagged with the configured tag. |
| `get_workflow_schema` | `workflowId: string` | `{ name, httpMethod, webhookPath, inputSchema, schemaSource: "sticky-note" \| "generic", note? }` — the webhook trigger details and a best-effort input schema. |
| `execute_workflow` | `workflowId: string`, `input?: Record<string, unknown>` | `{ status, body }` — the raw HTTP response from the workflow's webhook. |
| `get_execution_status` | `executionId: string` | `{ id, status, startedAt?, stoppedAt? }` |

Every call to `get_workflow_schema` and `execute_workflow` re-fetches the
workflow and re-checks the tag, so a workflow untagged mid-session stops being
usable immediately — the agent doesn't get to act on a stale, cached
authorization. This isn't perfectly atomic: an in-flight call that already
passed the tag check completes even if the workflow is untagged a moment
later (TOCTOU) — only the *next* call is guaranteed to see the change.

See [Execution semantics](#execution-semantics) below for what
`execute_workflow` actually guarantees (and doesn't) when a call fails.

## Security model

- **The tag is the boundary.** `list_workflows`, `get_workflow_schema`, and
  `execute_workflow` all filter on `N8N_MCP_TAG`. This is default-deny: a
  workflow is invisible to the agent until you explicitly opt it in.
- **Two separate auth mechanisms, not one.** `N8N_API_KEY` authenticates
  against the n8n REST API for discovery (listing workflows, reading schema,
  checking execution status). `N8N_WEBHOOK_AUTH_HEADER_NAME` /
  `N8N_WEBHOOK_AUTH_HEADER_VALUE` is a separate shared secret sent only on
  webhook execution calls, verified inside the workflow itself if you choose
  to check it. Don't conflate the two — a leaked webhook secret does not grant
  REST API access, and vice versa.
- **Use a least-privilege API key.** Where your n8n instance supports scoped
  keys or a dedicated service user, prefer that over reusing an
  administrator's personal API key.
- **Prefer the `*_FILE` env vars for secrets.** `N8N_API_KEY_FILE` and
  `N8N_WEBHOOK_AUTH_HEADER_VALUE_FILE` let the actual secret live in a file
  (owned and permissioned like any other credential file) instead of the MCP
  client's JSON config, which many clients store in plaintext on disk and
  which is easy to accidentally commit, sync, or screenshot. The server reads
  the file once at startup and never re-reads it.
- **The credentials this server manages never pass through the agent.**
  `N8N_API_KEY` and the webhook auth header are never included in tool
  results. This does **not** extend to whatever a workflow itself returns —
  if a workflow's webhook response echoes back a secret (by design or by
  mistake), the agent sees it, because `execute_workflow` returns the
  workflow's raw HTTP response body. Keeping secrets out of webhook responses
  is the workflow author's responsibility, not this server's.

## Execution semantics

`execute_workflow` delivery is **at-least-once, not exactly-once**. A
`Webhook request timeout after 15 seconds.` or `Failed to reach webhook: ...`
error means the MCP server didn't get a confirmed response — it does **not**
mean the workflow didn't run. The HTTP request may have reached n8n and
triggered execution before the connection dropped or the client gave up.

There is no idempotency key in v1: retrying a timed-out `execute_workflow`
call can run the workflow twice. If a workflow has side effects that aren't
safe to duplicate (charging a card, sending an email, creating a record),
either make the workflow idempotent on its own terms (e.g. dedupe on a
request id you pass in `input`), or don't retry blindly — use
`get_execution_status` / your own out-of-band signal to check whether the
first attempt actually completed before deciding to retry. This is why
`execute_workflow` is registered with MCP annotations
`destructiveHint: true, idempotentHint: false` — a well-behaved client should
already treat it with the same caution as any other non-idempotent write.

## Known limitations

- **Only *active* workflows are listed.** `list_workflows` asks n8n for active
  workflows only. Tagging a workflow that is switched off is not enough: n8n
  only serves the production `/webhook/<path>` URL for active workflows
  (inactive ones answer on the test URL, and only while the editor is
  listening), so an inactive workflow could be listed but never executed. If a
  workflow you tagged does not show up, check the **Active** toggle first.
- **Only webhook-triggered workflows are executable.** `get_workflow_schema`
  and `execute_workflow` require a `n8n-nodes-base.webhook` trigger node;
  workflows started by other triggers (cron, manual, form) will list fine but
  fail schema/execution with a clear error. This mirrors n8n itself — there is
  no generic "run any workflow synchronously" API. Disabled webhook nodes are
  skipped when looking for the trigger; if a workflow somehow has more than
  one *active* webhook node, the first one found is used — there's no way to
  select between them.
- **Webhook responses must be JSON, text, or empty.** `execute_workflow`
  reads the workflow's webhook response according to its `Content-Type`:
  JSON is parsed, `text/*` is returned as a string, and an empty/204 body
  becomes `null`. Any other `Content-Type` (binary, multipart, etc.) fails
  with an explicit "unsupported Content-Type" error rather than mangling the
  bytes.
- **Webhook responses are capped at 2MB.** A larger response — whether
  declared via `Content-Length` or discovered while reading the body — fails
  with an explicit error instead of silently truncating or exhausting memory.
- **Redirects are never followed.** Both the n8n REST API calls and webhook
  execution calls use `redirect: "manual"`; a 3xx response fails with an
  explicit error instead of resending `N8N_API_KEY` or the webhook auth
  header to whatever origin the `Location` header points at.
- **Schema inference is best-effort.** If a workflow has a Sticky Note node
  whose content is JSON containing `type` or `properties`, that's used as the
  input schema. Otherwise the server falls back to a generic
  `{ type: "object", additionalProperties: true }` schema and says so in the
  response. It never promises strict validation of a workflow's actual inputs.
- **stdio transport only in v1.** No HTTP/SSE transport, no remote MCP client
  support yet.
- **Read + execute only.** This server does not create, edit, activate, or
  deactivate workflows — that's the official n8n MCP server's job.

## Development

```bash
npm install
npm run typecheck   # tsc --noEmit
npm run lint         # biome check .
npm test             # vitest run
npm run build         # tsdown
npm run dev           # tsdown --watch
```

Built with a TDD workflow — tests in `test/` are written against each module
before the implementation (`src/n8n-client.ts`, `src/tools/*.ts`,
`src/server.ts`) and mock the n8n HTTP layer rather than hitting a real
instance. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup details and
conventions, and [DECISIONS.md](DECISIONS.md) for the reasoning behind the
main design choices.

## Work with me

I build AI systems that run in production — RAG pipelines, agent
orchestration, workflow automation. This repo is open so you can see how I
work. If you want to adapt it to your stack, go deeper than the code, or
build something of your own, get in touch:

- Portfolio: [nestormartinez.dev](https://www.nestormartinez.dev)
- Email: info@podervsfuerza.com
- LinkedIn: [Nestor Martinez](https://www.linkedin.com/in/nestor-martinez-batista-pvsf/)

## License

MIT — see [LICENSE](LICENSE).

TDQS

A4.2/5.0

Scored across 4 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: listing workflows, retrieving schema, executing a workflow, and checking execution status. No overlap or ambiguity exists between them.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (list_workflows, get_workflow_schema, execute_workflow, get_execution_status), making the naming predictable and uniform.

Tool Count5/5

With only 4 tools, the server is well-scoped for its purpose. Each tool is essential and earns its place without unnecessary bloat or missing core functionality.

Completeness5/5

The tool set covers the full execution lifecycle: discover workflows, understand their input schema, execute them, and check execution status. No obvious dead ends or missing operations for the stated purpose.

Maintenance

ActivitySlowing
ResponsivenessUnresponsive