@thecodesaiyan/tcs-n8n-mcp 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -6
- package/build/config.js +71 -0
- package/build/index.js +88 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ An [MCP](https://modelcontextprotocol.io) server that gives AI assistants full c
|
|
|
23
23
|
- **22 tools** covering the full n8n REST API
|
|
24
24
|
- **Works everywhere** — Claude Code, Claude Desktop, VS Code, Cursor, Windsurf, Cline
|
|
25
25
|
- **Secure by default** — ID validation, sanitised errors, masked secrets, request timeouts
|
|
26
|
+
- **Flexible authentication** — API key (default), Bearer token, or Basic Auth
|
|
26
27
|
- **Zero config** — just provide your n8n API key and go
|
|
27
28
|
- **Lightweight** — under 30KB, no runtime dependencies beyond the MCP SDK and Zod
|
|
28
29
|
|
|
@@ -44,12 +45,31 @@ An [MCP](https://modelcontextprotocol.io) server that gives AI assistants full c
|
|
|
44
45
|
npx @thecodesaiyan/tcs-n8n-mcp
|
|
45
46
|
```
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
### Interactive Setup Wizard
|
|
49
|
+
|
|
50
|
+
The setup wizard walks you through configuration and tests your connection:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @thecodesaiyan/tcs-n8n-mcp --setup
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
It will prompt for your n8n URL, auth type, and credentials, then output ready-to-paste config for your MCP client.
|
|
57
|
+
|
|
58
|
+
### Environment Variables
|
|
48
59
|
|
|
49
60
|
| Variable | Required | Default | Description |
|
|
50
61
|
|----------|:--------:|---------|-------------|
|
|
51
|
-
| `N8N_API_KEY` | Yes | — | Your n8n API key |
|
|
62
|
+
| `N8N_API_KEY` | Yes | — | Your n8n API key (or password for basic auth) |
|
|
52
63
|
| `N8N_API_URL` | No | `http://localhost:5678` | Base URL of your n8n instance |
|
|
64
|
+
| `N8N_AUTH_TYPE` | No | `apikey` | Authentication method: `apikey`, `bearer`, or `basic` |
|
|
65
|
+
| `N8N_API_USER` | Only for `basic` | — | Username for basic authentication |
|
|
66
|
+
| `N8N_TIMEOUT_MS` | No | `30000` | Request timeout in milliseconds |
|
|
67
|
+
|
|
68
|
+
#### Authentication Types
|
|
69
|
+
|
|
70
|
+
- **`apikey`** (default) — Sends `X-N8N-API-KEY` header. This is n8n's standard API authentication.
|
|
71
|
+
- **`bearer`** — Sends `Authorization: Bearer <token>` header.
|
|
72
|
+
- **`basic`** — Sends `Authorization: Basic <base64>` header. Requires `N8N_API_USER` to be set.
|
|
53
73
|
|
|
54
74
|
---
|
|
55
75
|
|
|
@@ -58,13 +78,19 @@ Set the required environment variables:
|
|
|
58
78
|
<details>
|
|
59
79
|
<summary><strong>Claude Code</strong></summary>
|
|
60
80
|
|
|
61
|
-
#### Option A:
|
|
81
|
+
#### Option A: Interactive wizard
|
|
62
82
|
|
|
63
83
|
```bash
|
|
64
|
-
|
|
84
|
+
npx @thecodesaiyan/tcs-n8n-mcp --setup
|
|
65
85
|
```
|
|
66
86
|
|
|
67
|
-
#### Option B:
|
|
87
|
+
#### Option B: CLI command
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
claude mcp add n8n -e N8N_API_URL=http://localhost:5678 -e N8N_API_KEY=your-api-key -- npx -y @thecodesaiyan/tcs-n8n-mcp
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Option C: Manual config
|
|
68
94
|
|
|
69
95
|
Add to `~/.claude.json`:
|
|
70
96
|
|
|
@@ -275,9 +301,11 @@ Open **MCP Servers** > **Configure** > **Advanced MCP Settings** and add:
|
|
|
275
301
|
| **ID Validation** | All resource IDs are validated as numeric strings to prevent path traversal attacks |
|
|
276
302
|
| **Error Sanitisation** | API error responses only return HTTP status codes, never raw response bodies |
|
|
277
303
|
| **Secret Masking** | Variable values are hidden in list output to prevent accidental secret exposure |
|
|
278
|
-
| **Request Timeout** | All API calls have a
|
|
304
|
+
| **Request Timeout** | All API calls have a configurable timeout (default 30s) to prevent hanging connections |
|
|
279
305
|
| **No Key Logging** | API keys are never logged, echoed, or included in tool output |
|
|
280
306
|
| **Credential Safety** | The credentials tool only returns metadata — secrets are never exposed |
|
|
307
|
+
| **Startup Validation** | Credentials and connectivity are verified on startup before accepting any requests |
|
|
308
|
+
| **Header Injection Prevention** | Basic auth credentials are validated to reject control characters |
|
|
281
309
|
|
|
282
310
|
---
|
|
283
311
|
|
|
@@ -301,7 +329,14 @@ npm install
|
|
|
301
329
|
### Running locally
|
|
302
330
|
|
|
303
331
|
```bash
|
|
332
|
+
# API key auth (default)
|
|
304
333
|
N8N_API_KEY=your-key N8N_API_URL=http://localhost:5678 npm start
|
|
334
|
+
|
|
335
|
+
# Bearer token auth
|
|
336
|
+
N8N_AUTH_TYPE=bearer N8N_API_KEY=your-token N8N_API_URL=http://localhost:5678 npm start
|
|
337
|
+
|
|
338
|
+
# Basic auth
|
|
339
|
+
N8N_AUTH_TYPE=basic N8N_API_USER=admin N8N_API_KEY=password N8N_API_URL=http://localhost:5678 npm start
|
|
305
340
|
```
|
|
306
341
|
|
|
307
342
|
### Project Structure
|
|
@@ -309,6 +344,7 @@ N8N_API_KEY=your-key N8N_API_URL=http://localhost:5678 npm start
|
|
|
309
344
|
```
|
|
310
345
|
src/
|
|
311
346
|
index.ts Entry point, fetch wrapper, server setup
|
|
347
|
+
config.ts Auth type parsing, header building, timeout, connection check
|
|
312
348
|
types.ts Shared types, interfaces, response helpers
|
|
313
349
|
validation.ts Zod schemas for input validation
|
|
314
350
|
tools/
|
package/build/config.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const VALID_AUTH_TYPES = ["apikey", "bearer", "basic"];
|
|
2
|
+
const CONTROL_CHAR_RE = /[\r\n\0]/;
|
|
3
|
+
/**
|
|
4
|
+
* Parse and validate N8N_AUTH_TYPE. Defaults to "apikey" when unset.
|
|
5
|
+
* @throws if the value is not one of the supported types
|
|
6
|
+
*/
|
|
7
|
+
export function parseAuthType(raw) {
|
|
8
|
+
const value = (raw || "apikey").toLowerCase();
|
|
9
|
+
if (!VALID_AUTH_TYPES.includes(value)) {
|
|
10
|
+
throw new Error(`Invalid N8N_AUTH_TYPE "${raw}". Must be one of: ${VALID_AUTH_TYPES.join(", ")}`);
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Build the authentication headers for n8n API requests.
|
|
16
|
+
* @throws if authType is "basic" and apiUser is missing or contains control characters
|
|
17
|
+
*/
|
|
18
|
+
export function buildAuthHeaders(authType, apiKey, apiUser) {
|
|
19
|
+
switch (authType) {
|
|
20
|
+
case "apikey":
|
|
21
|
+
return { "X-N8N-API-KEY": apiKey };
|
|
22
|
+
case "bearer":
|
|
23
|
+
return { Authorization: `Bearer ${apiKey}` };
|
|
24
|
+
case "basic": {
|
|
25
|
+
if (!apiUser) {
|
|
26
|
+
throw new Error("N8N_API_USER is required when N8N_AUTH_TYPE is 'basic'");
|
|
27
|
+
}
|
|
28
|
+
if (CONTROL_CHAR_RE.test(apiUser) || CONTROL_CHAR_RE.test(apiKey)) {
|
|
29
|
+
throw new Error("Invalid credentials: username and password must not contain control characters");
|
|
30
|
+
}
|
|
31
|
+
const encoded = Buffer.from(`${apiUser}:${apiKey}`).toString("base64");
|
|
32
|
+
return { Authorization: `Basic ${encoded}` };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse N8N_TIMEOUT_MS env var into a positive integer.
|
|
38
|
+
* Returns 30 000 ms for any invalid / missing value.
|
|
39
|
+
*/
|
|
40
|
+
export function parseTimeoutMs(raw) {
|
|
41
|
+
const value = parseInt(raw || "30000", 10);
|
|
42
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
43
|
+
return 30_000;
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Probe the n8n API to verify credentials and connectivity.
|
|
49
|
+
* Returns an object indicating success or failure with an error message.
|
|
50
|
+
*/
|
|
51
|
+
export async function checkConnection(fetchFn) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await fetchFn("/workflows?limit=1");
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false,
|
|
57
|
+
error: `HTTP ${res.status} ${res.statusText || ""}. ` +
|
|
58
|
+
"Verify N8N_API_URL and credentials are correct.",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return { ok: true };
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: `${e instanceof Error ? e.message : String(e)}. ` +
|
|
67
|
+
"Verify N8N_API_URL is reachable.",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=config.js.map
|
package/build/index.js
CHANGED
|
@@ -8,68 +8,131 @@ import { registerTagTools } from "./tools/tags.js";
|
|
|
8
8
|
import { registerVariableTools } from "./tools/variables.js";
|
|
9
9
|
import { registerCredentialTools } from "./tools/credentials.js";
|
|
10
10
|
import { registerUserTools } from "./tools/users.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
import { parseAuthType, buildAuthHeaders, parseTimeoutMs, checkConnection } from "./config.js";
|
|
12
|
+
// --- Interactive setup: --setup flag OR missing config in a TTY ---
|
|
13
|
+
if (process.argv.includes("--setup") || (!process.env.N8N_API_KEY && process.stdin.isTTY)) {
|
|
14
|
+
await runSetup().catch((e) => {
|
|
14
15
|
console.error("Setup failed:", e);
|
|
15
16
|
process.exit(1);
|
|
16
17
|
});
|
|
18
|
+
process.exit(0);
|
|
17
19
|
}
|
|
18
20
|
async function runSetup() {
|
|
19
21
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
20
22
|
console.log("\n @thecodesaiyan/tcs-n8n-mcp - Setup Wizard\n");
|
|
21
23
|
const url = (await rl.question(" n8n URL [http://localhost:5678]: ")).trim() || "http://localhost:5678";
|
|
22
|
-
const
|
|
24
|
+
const authTypeRaw = (await rl.question(" Auth type (apikey/bearer/basic) [apikey]: ")).trim() || "apikey";
|
|
25
|
+
let authType;
|
|
26
|
+
try {
|
|
27
|
+
authType = parseAuthType(authTypeRaw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
console.error(`\n Invalid auth type "${authTypeRaw}". Must be: apikey, bearer, or basic.\n`);
|
|
31
|
+
rl.close();
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
let apiUser = "";
|
|
35
|
+
if (authType === "basic") {
|
|
36
|
+
apiUser = (await rl.question(" n8n Username: ")).trim();
|
|
37
|
+
if (!apiUser) {
|
|
38
|
+
console.error("\n Username is required for basic auth.\n");
|
|
39
|
+
rl.close();
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const credentialLabel = authType === "basic" ? "Password" : "API Key";
|
|
44
|
+
const apiKey = (await rl.question(` n8n ${credentialLabel}: `)).trim();
|
|
23
45
|
rl.close();
|
|
24
46
|
if (!apiKey) {
|
|
25
|
-
console.error(
|
|
47
|
+
console.error(authType === "basic"
|
|
48
|
+
? "\n Password is required.\n"
|
|
49
|
+
: "\n API key is required. Generate one in n8n: Settings > API > Create API Key\n");
|
|
26
50
|
process.exit(1);
|
|
27
51
|
}
|
|
28
52
|
// Test connection
|
|
29
53
|
console.log("\n Testing connection...");
|
|
30
54
|
const testUrl = `${url.replace(/\/$/, "")}/api/v1/workflows?limit=1`;
|
|
55
|
+
const testHeaders = buildAuthHeaders(authType, apiKey, apiUser || undefined);
|
|
31
56
|
try {
|
|
32
57
|
const res = await fetch(testUrl, {
|
|
33
|
-
headers: {
|
|
58
|
+
headers: { ...testHeaders, "Content-Type": "application/json" },
|
|
34
59
|
signal: AbortSignal.timeout(10_000),
|
|
35
60
|
});
|
|
36
61
|
if (!res.ok) {
|
|
37
62
|
console.error(` Connection failed: HTTP ${res.status}`);
|
|
38
|
-
console.error(" Check your URL and
|
|
63
|
+
console.error(" Check your URL and credentials, then try again.\n");
|
|
39
64
|
process.exit(1);
|
|
40
65
|
}
|
|
41
66
|
console.log(" Connected successfully!\n");
|
|
42
67
|
}
|
|
43
68
|
catch (e) {
|
|
44
69
|
console.error(` Connection failed: ${e instanceof Error ? e.message : e}`);
|
|
45
|
-
console.error(" Check your URL and
|
|
70
|
+
console.error(" Check your URL and credentials, then try again.\n");
|
|
46
71
|
process.exit(1);
|
|
47
72
|
}
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
args: ["-y", pkg],
|
|
53
|
-
env: { N8N_API_URL: url, N8N_API_KEY: apiKey },
|
|
73
|
+
// Build env config — only include non-default values
|
|
74
|
+
const env = {
|
|
75
|
+
N8N_API_URL: url,
|
|
76
|
+
N8N_API_KEY: apiKey,
|
|
54
77
|
};
|
|
78
|
+
if (authType !== "apikey") {
|
|
79
|
+
env.N8N_AUTH_TYPE = authType;
|
|
80
|
+
}
|
|
81
|
+
if (authType === "basic") {
|
|
82
|
+
env.N8N_API_USER = apiUser;
|
|
83
|
+
}
|
|
84
|
+
const pkg = "@thecodesaiyan/tcs-n8n-mcp";
|
|
85
|
+
const isWindows = process.platform === "win32";
|
|
86
|
+
const stdioCfg = isWindows
|
|
87
|
+
? { command: "tcs-n8n-mcp", args: [], env }
|
|
88
|
+
: { command: "npx", args: ["-y", pkg], env };
|
|
89
|
+
const envFlags = Object.entries(env)
|
|
90
|
+
.map(([k, v]) => `-e ${k}=${v}`)
|
|
91
|
+
.join(" ");
|
|
92
|
+
if (isWindows) {
|
|
93
|
+
console.log(" ── Prerequisites (Windows) ──");
|
|
94
|
+
console.log(` npm install -g ${pkg}\n`);
|
|
95
|
+
}
|
|
55
96
|
console.log(" ── Claude Code ──");
|
|
56
|
-
|
|
97
|
+
if (isWindows) {
|
|
98
|
+
console.log(` claude mcp add tcs-n8n-mcp ${envFlags} -- tcs-n8n-mcp\n`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log(` claude mcp add tcs-n8n-mcp ${envFlags} -- npx -y ${pkg}\n`);
|
|
102
|
+
}
|
|
57
103
|
console.log(" ── Claude Desktop / Windsurf ──");
|
|
58
104
|
console.log(" Add to your config JSON under \"mcpServers\":\n");
|
|
59
105
|
console.log(` "tcs-n8n-mcp": ${JSON.stringify(stdioCfg, null, 4)}\n`);
|
|
60
106
|
console.log(" ── Cursor ──");
|
|
61
107
|
console.log(" Add to .cursor/mcp.json under \"mcpServers\":\n");
|
|
62
108
|
console.log(` "tcs-n8n-mcp": ${JSON.stringify(stdioCfg, null, 4)}\n`);
|
|
109
|
+
console.log(" Note: The above snippets contain your credentials.");
|
|
110
|
+
console.log(" Avoid sharing them in public channels or screenshots.\n");
|
|
63
111
|
}
|
|
64
112
|
// --- Normal MCP server mode ---
|
|
65
113
|
const N8N_API_URL = process.env.N8N_API_URL || "http://localhost:5678";
|
|
66
114
|
const N8N_API_KEY = process.env.N8N_API_KEY || "";
|
|
115
|
+
const N8N_API_USER = process.env.N8N_API_USER || "";
|
|
67
116
|
if (!N8N_API_KEY) {
|
|
68
|
-
console.error("N8N_API_KEY environment variable is required");
|
|
117
|
+
console.error("N8N_API_KEY environment variable is required.");
|
|
118
|
+
console.error("Tip: run `npx @thecodesaiyan/tcs-n8n-mcp --setup` for interactive configuration.");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
let authType;
|
|
122
|
+
try {
|
|
123
|
+
authType = parseAuthType(process.env.N8N_AUTH_TYPE);
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
console.error(e instanceof Error ? e.message : String(e));
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
if (authType === "basic" && !N8N_API_USER) {
|
|
130
|
+
console.error("N8N_API_USER environment variable is required when N8N_AUTH_TYPE is 'basic'");
|
|
69
131
|
process.exit(1);
|
|
70
132
|
}
|
|
133
|
+
const authHeaders = buildAuthHeaders(authType, N8N_API_KEY, N8N_API_USER || undefined);
|
|
71
134
|
const apiBase = `${N8N_API_URL.replace(/\/$/, "")}/api/v1`;
|
|
72
|
-
const DEFAULT_TIMEOUT_MS =
|
|
135
|
+
const DEFAULT_TIMEOUT_MS = parseTimeoutMs(process.env.N8N_TIMEOUT_MS);
|
|
73
136
|
const n8nFetch = (path, options = {}) => {
|
|
74
137
|
const controller = new AbortController();
|
|
75
138
|
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
@@ -77,7 +140,7 @@ const n8nFetch = (path, options = {}) => {
|
|
|
77
140
|
...options,
|
|
78
141
|
signal: options.signal ?? controller.signal,
|
|
79
142
|
headers: {
|
|
80
|
-
|
|
143
|
+
...authHeaders,
|
|
81
144
|
"Content-Type": "application/json",
|
|
82
145
|
...options.headers,
|
|
83
146
|
},
|
|
@@ -85,7 +148,7 @@ const n8nFetch = (path, options = {}) => {
|
|
|
85
148
|
};
|
|
86
149
|
const server = new McpServer({
|
|
87
150
|
name: "@thecodesaiyan/tcs-n8n-mcp",
|
|
88
|
-
version: "1.
|
|
151
|
+
version: "1.2.0",
|
|
89
152
|
});
|
|
90
153
|
// Register all tool modules
|
|
91
154
|
registerWorkflowTools(server, n8nFetch);
|
|
@@ -95,9 +158,14 @@ registerVariableTools(server, n8nFetch);
|
|
|
95
158
|
registerCredentialTools(server, n8nFetch);
|
|
96
159
|
registerUserTools(server, n8nFetch);
|
|
97
160
|
async function main() {
|
|
161
|
+
const result = await checkConnection(n8nFetch);
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
console.error(`n8n connection check failed: ${result.error}`);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
98
166
|
const transport = new StdioServerTransport();
|
|
99
167
|
await server.connect(transport);
|
|
100
|
-
console.error("@thecodesaiyan/tcs-n8n-mcp v1.
|
|
168
|
+
console.error("@thecodesaiyan/tcs-n8n-mcp v1.2.0 running on stdio (22 tools)");
|
|
101
169
|
}
|
|
102
170
|
main().catch((error) => {
|
|
103
171
|
console.error("Fatal error:", error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thecodesaiyan/tcs-n8n-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP server for n8n workflow automation. Manage workflows, executions, tags, variables, credentials and users via the Model Context Protocol.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|