# n8n-flow-auditor — Full reference for LLMs > Deterministic validator for n8n workflows. Ports `@n8n/workflow-sdk` to plain JavaScript and runs on Cloudflare Workers. Exposed as a remote MCP server (Model Context Protocol). Same input always produces the same output. No LLM tokens consumed on the service side. Service URL: https://n8n-auditor.automators.work OpenAPI spec: https://n8n-auditor.automators.work/openapi.yaml Interactive docs: https://n8n-auditor.automators.work/docs This file is a single-document reference suitable for ingestion by an LLM that needs to call this service or generate n8n workflows that pass validation. Read it once and you have everything needed to (a) call the API, (b) interpret responses, (c) understand which validators fire and why. --- ## What it does Given a workflow JSON exported from n8n, the service runs 21 deterministic validators and a deep node-schema check, then returns errors and warnings with structured codes, severities, and exact parameter paths. There is no LLM call on the server side — the result is a pure function of the input. The same logic is exposed two ways: 1. **REST**: `POST /validate` with `{ workflow, options? }` returns a `ValidationReport` JSON. 2. **MCP**: `POST /mcp` (JSON-RPC 2.0, MCP protocol `2025-06-18`) exposes 5 tools. Use REST for one-off validations. Use MCP when an agent needs to call multiple tools (validate, schema introspection, sticky-note inspection) in a session. --- ## Authentication Three auth paths, in priority order: 1. **`API_KEY` bearer** — admin/dev bypass. `Authorization: Bearer ` authenticates as `sub: 'admin'`. 2. **OAuth 2.1 self-hosted** — primary path for public MCP clients (Claude Desktop, Cursor). The worker is the Authorization Server; GitHub is the upstream IdP. Full RFC 8414 metadata + RFC 7591 Dynamic Client Registration + PKCE. 3. **CF Access JWT** (legacy) — `Cf-Access-Jwt-Assertion` header, kept for backwards compatibility. Endpoints: - `GET /.well-known/oauth-authorization-server` — RFC 8414 metadata - `GET /.well-known/oauth-protected-resource` — MCP-spec metadata - `POST /oauth/register` — Dynamic Client Registration - `GET /oauth/authorize` — starts the flow (browser-based, redirects to GitHub) - `GET /oauth/callback` — GitHub redirect URI - `POST /oauth/token` — exchanges authorization code for access token (requires PKCE verifier) The public REST endpoint `/validate` does NOT require authentication; rate-limited at the edge by Cloudflare WAF (10 req per 10s per IP, free-tier limit). Authenticated MCP calls get a per-user 60 req/min budget. `/admin/*` endpoints require the API_KEY bearer. They are not part of the public surface. --- ## REST API ### POST /validate Validate an inline workflow. Request body: ```json { "workflow": { "name": "string (optional)", "nodes": [/* n8n node objects */], "connections": {/* n8n connections object */} }, "options": { "allowNoTrigger": false, "allowDisconnectedNodes": false }, "format": "json" } ``` Response 200: ```json { "workflowName": "string", "generatedAt": "ISO-8601 timestamp", "valid": true, "errors": [ { "code": "MISSING_PARAMETER", "severity": "error", "message": "Required parameter 'schema' (string) missing in 'Save to DB'.", "nodeName": "Save to DB", "parameterPath": "schema" } ], "warnings": [] } ``` `valid` is `true` when `errors.length === 0` regardless of warnings. Other status codes: - 400 — invalid body (malformed JSON or missing fields) - 413 — body exceeds 1 MB or workflow exceeds 50 nodes - 429 — rate-limited at the edge (retry after the `Retry-After` header value) Cached by input hash (10-minute TTL). The `x-cache: hit|miss` response header indicates which. ### POST /validate/by-id Fetches a workflow from a configured n8n instance and validates it. Requires auth. Body: ```json { "workflowId": "abc123", "format": "json", "options": { /* same as /validate */ } } ``` Status codes specific to this endpoint: - 404 — workflow does not exist on the configured n8n - 502 — n8n is unreachable - 503 — n8n is not configured on this worker (env vars `N8N_BASE_URL` / `N8N_API_KEY` missing) - 504 — timeout fetching from the user's n8n (>10 s) ### GET /n8n/workflows?limit=50 Lists workflows from the configured n8n. Requires auth. Returns `{ workflows: [{id, name, active, tags, updatedAt}, ...] }`. ### GET /health Worker status, version, runtime configuration. Public, no auth. ### GET /openapi.yaml OpenAPI 3.1 spec of every endpoint above. Cached 1 hour. ### GET /docs Swagger UI rendering of `/openapi.yaml`. Public. ### GET /llms.txt This file's shorter cousin. Concise machine-readable summary for LLM context windows. ### GET /llms-full.txt This file. Full reference. ### GET /robots.txt, /sitemap.xml Standard SEO crawl files. --- ## MCP server Endpoint: `POST /mcp` Transport: Streamable HTTP Protocol: `2025-06-18` JSON-RPC: 2.0 State: stateless (the worker doesn't keep session state between requests) ### Methods - `initialize` — handshake; returns `protocolVersion`, `capabilities.tools`, `serverInfo`, `instructions` - `notifications/initialized` — no-op (returns 202) - `notifications/cancelled` — no-op (returns 202) - `tools/list` — lists the 5 tools - `tools/call` — invokes a tool - `ping` — `{}` ### Tools #### validate_workflow Same logic as `POST /validate`. Input/output identical. #### validate_workflow_by_id Fetches a workflow from the user's n8n and validates it. Credentials passed per-call, never stored. Input: ```json { "workflow_id": "abc123", "n8n_base_url": "https://n8n.example.com", "n8n_api_key": "..." } ``` Output: same as `validate_workflow`, plus `workflowId`. #### list_known_node_types Returns the 66 types in the catalog with their available versions. Output: ```json { "count": 66, "types": [ { "type": "n8n-nodes-base.postgres", "versions": [1, 2, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6] }, { "type": "@n8n/n8n-nodes-langchain.agent", "versions": [1, 1.1, "...", 3, 3.1] } ] } ``` #### get_node_schema Returns the canonical shape for a specific type+version. Input: `{ "type": "n8n-nodes-base.postgres", "version": 1 }` Output: ```json { "type": "n8n-nodes-base.postgres", "version": 1, "shape": { "v": [1], "i": ["main"], "o": ["main"], "bh": null, "p": [ { "n": "operation", "t": "options", "ov": ["executeQuery", "insert", "update"] }, { "n": "schema", "t": "string", "r": 1, "df": "public", "d": { "show": { "operation": ["insert"] } } } ] } } ``` Shape format (compressed key names to fit large payloads): - `v` — array of versions sharing this shape - `i` — inputs: array of strings (`["main"]`), or `"__expr__"` (computed dynamically), or `"__function__"` - `o` — outputs: same shape - `bh` — `builderHint`: SDK metadata; lists required AI sub-nodes when present - `p` — properties array, each with short keys: - `n` — name - `t` — type (string/number/boolean/options/multiOptions/fixedCollection/collection/...) - `r` — required (1 if true) - `df` — default value - `d` — displayOptions: `{show:{...}, hide:{...}}` with literal values or `{_cnd: {gte/lte/gt/lt/eq/not/...}}` - `ov` — optionsValues: array of valid values for options/multiOptions - `o` — sub-options for fixedCollection/collection: `[{n, v: subprops[]}]` - `mv` — multipleValues (1 if true) #### analyze_sticky_notes Heuristic prompt-injection detection in sticky notes. 12 regex patterns. Useful for AI-generated workflows that may contain instructions hidden in notes. Input: `{ "workflow": { ... } }` Output: ```json { "summary": { "totalStickyNotes": 3, "suspiciousCount": 1, "maxSuspicionScore": 0.75, "recommendation": "manual_review" }, "notes": [ { "nodeName": "Note", "suspicionScore": 0.75, "flags": ["instruction_override", "system_role_claim"], "contentTruncated": "Ignore all previous instructions..." } ] } ``` `recommendation` is one of `safe`, `manual_review`, `block`. --- ## Validators (full reference) | # | Code | Severity | Detects | |---|------|----------|---------| | 1 | `NO_NODES` | error | Empty workflow | | 2 | `MISSING_TRIGGER` | warning | No trigger node present | | 3 | `DISCONNECTED_NODE` | warning | Orphan node (no incoming edge) | | 4 | `INVALID_CONNECTION` | error | Connection points to a non-existent node | | 5 | `MERGE_SINGLE_INPUT` | warning | Merge node has fewer than 2 inputs | | 6 | `SUBNODE_NOT_CONNECTED` | error | Sub-node `lm*`/`memory*`/`embedding*` not wired to a parent | | 7 | `SET_CREDENTIAL_FIELD` | warning | Set node with `apiKey`/`password`-shaped fields | | 8 | `FILTER_MISSING_OPTIONS` / `FILTER_MISSING_CONDITIONS` / `FILTER_MISSING_COMBINATOR` | error | Filter / IF / Switch with malformed structure | | 9 | `SWITCH_WRONG_RULES_KEY` | error | Switch with `rules.rules` instead of `rules.values` | | 10 | `HARDCODED_CREDENTIALS` | warning | Bearer / sk- / xox / AKIA / AIza secrets in plaintext | | 11 | `AGENT_STATIC_PROMPT` | warning | Agent with a non-dynamic prompt | | 12 | `AGENT_NO_SYSTEM_MESSAGE` | warning | Agent without `systemMessage` | | 13 | `INVALID_DATE_METHOD` | warning | `.toISOString()` on a Luxon value (should be `.toISO()`) | | 14 | `MISSING_EXPRESSION_PREFIX` | warning | `{{ $... }}` missing the `=` prefix | | 15 | `INVALID_EXPRESSION_PATH` | warning | `$json.foo` when no predecessor exposes `foo` | | 16 | `PARTIAL_EXPRESSION_PATH` | warning | Field exposed by only some predecessors | | 17 | `FROM_AI_IN_NON_TOOL` | warning | `$fromAI()` in a non-tool node | | 18 | `TOOL_NO_PARAMETERS` | warning | Tool node without configured parameters | | 19 | `INVALID_INPUT_INDEX` | warning | Connection to an out-of-range input index | | 20 | `MISSING_REQUIRED_INPUT` | error | Required AI input missing (e.g. agent without `ai_languageModel`) | | 21 | `UNSUPPORTED_SUBNODE_INPUT` | warning | Sub-node connected but parent doesn't support it in its config | | — | `MISSING_PARAMETER` | error | Required parameter missing on any catalog node | | — | `INVALID_PARAMETER` | warning | Type mismatch or option enum violation | | — | `MAX_NODES_EXCEEDED` | warning | Workflow > 50 nodes | Validators 1–21 are direct ports of `@n8n/workflow-sdk` validators (3 of which are SDK opt-in tiers we always run). The bottom three are catalog-driven and apply to every type+version combination in the catalog. --- ## Connect from Claude Desktop / Cursor / any MCP client Add to your client's MCP server config: ```json { "mcpServers": { "n8n-flow-auditor": { "url": "https://n8n-auditor.automators.work/mcp" } } } ``` On first call, the client receives a 401 with `WWW-Authenticate: Bearer realm="...", resource_metadata="..."`. Spec-compliant MCP clients then: 1. Read `/.well-known/oauth-protected-resource` to find the Authorization Server. 2. Read `/.well-known/oauth-authorization-server` for the endpoint URLs. 3. POST `/oauth/register` to get a `client_id` + `client_secret` (Dynamic Client Registration). 4. Open a browser to `/oauth/authorize?...` with PKCE challenge. 5. The worker redirects to GitHub. User signs in. 6. GitHub redirects back to `/oauth/callback`. Worker emits an authorization code, redirects to the client's redirect URI. 7. Client POSTs `/oauth/token` with the PKCE verifier, gets an `access_token`. 8. Subsequent `/mcp` requests include `Authorization: Bearer `. Tokens are valid for 30 days. --- ## Limits and operational notes - Workflows up to **50 nodes** per request (HTTP 413 beyond) - Request body up to **1 MB** (HTTP 413) - n8n REST timeout: **10 s** for `validate_workflow_by_id` (HTTP 504 beyond) - Edge rate limit: **10 req per 10 s window per IP** on public endpoints (Cloudflare WAF, free-tier ceiling) - MCP rate limit: **60 req/min per authenticated user** (in-memory per-isolate fallback) - Sub-node detection (`embedding`, `memory`, `lm`, etc.): by type prefix — fails on CUSTOM nodes that don't follow the n8n naming convention - Cache: `/validate` responses cached 10 min by input hash; `/openapi.yaml` and `/docs` cached 1 h --- ## What the service is NOT - It does **not** execute workflows. Only validates structure + parameters. - It does **not** call any LLM on the server side. Pure deterministic logic. - It does **not** support n8n custom nodes (community packages outside the embedded catalog). - It does **not** persist workflows. Every request is stateless. - It is **not** a workflow editor. Use n8n itself for editing. --- ## License and operator Proprietary. Hosted by the project author at `https://n8n-auditor.automators.work`. Free tier is intended for individual developers and one-off validations. Heavy production use should self-host (the worker is portable to any Cloudflare account; see DEPLOY.md in the source repo, available to authorized collaborators). For usage questions, contact via the [Swagger UI](https://n8n-auditor.automators.work/docs).