@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 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
- Set the required environment variables:
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: CLI command
81
+ #### Option A: Interactive wizard
62
82
 
63
83
  ```bash
64
- claude mcp add n8n -- npx -y @thecodesaiyan/tcs-n8n-mcp
84
+ npx @thecodesaiyan/tcs-n8n-mcp --setup
65
85
  ```
66
86
 
67
- #### Option B: Manual config
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 30-second timeout to prevent hanging connections |
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/
@@ -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
- // --- Interactive setup mode ---
12
- if (process.argv.includes("--setup")) {
13
- runSetup().then(() => process.exit(0)).catch((e) => {
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 apiKey = (await rl.question(" n8n API Key: ")).trim();
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("\n API key is required. Generate one in n8n: Settings > API > Create API Key\n");
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: { "X-N8N-API-KEY": apiKey, "Content-Type": "application/json" },
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 API key, then try again.\n");
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 API key, then try again.\n");
70
+ console.error(" Check your URL and credentials, then try again.\n");
46
71
  process.exit(1);
47
72
  }
48
- const pkg = "@thecodesaiyan/tcs-n8n-mcp";
49
- const envJson = JSON.stringify({ N8N_API_URL: url, N8N_API_KEY: apiKey });
50
- const stdioCfg = {
51
- command: "npx",
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
- console.log(` claude mcp add tcs-n8n-mcp -e N8N_API_URL=${url} -e N8N_API_KEY=${apiKey} -- npx -y ${pkg}\n`);
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 = 30_000;
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
- "X-N8N-API-KEY": N8N_API_KEY,
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.1.0",
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.1.0 running on stdio (22 tools)");
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.1.0",
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",