@thecodesaiyan/tcs-n8n-mcp 1.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nigel Tatschner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # @thecodesaiyan/tcs-n8n-mcp
2
+
3
+ By [@TheCodeSaiyan](https://github.com/ntatschner)
4
+
5
+ An [MCP](https://modelcontextprotocol.io) (Model Context Protocol) server for managing [n8n](https://n8n.io) workflow automation instances. Provides 22 tools for workflows, executions, tags, variables, credentials, and users.
6
+
7
+ ## Quick Start
8
+
9
+ Add to your MCP client configuration:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "n8n": {
15
+ "command": "npx",
16
+ "args": ["-y", "@thecodesaiyan/tcs-n8n-mcp"],
17
+ "env": {
18
+ "N8N_API_URL": "http://localhost:5678",
19
+ "N8N_API_KEY": "your-api-key-here"
20
+ }
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ ### Claude Code
27
+
28
+ Add to `~/.claude.json` under `mcpServers`, or run:
29
+
30
+ ```bash
31
+ claude mcp add n8n -- npx -y @thecodesaiyan/tcs-n8n-mcp
32
+ ```
33
+
34
+ Then set the environment variables `N8N_API_URL` and `N8N_API_KEY`.
35
+
36
+ ## Environment Variables
37
+
38
+ | Variable | Required | Default | Description |
39
+ |----------|----------|---------|-------------|
40
+ | `N8N_API_KEY` | Yes | — | n8n API key ([how to create](https://docs.n8n.io/api/authentication/)) |
41
+ | `N8N_API_URL` | No | `http://localhost:5678` | Base URL of your n8n instance |
42
+
43
+ ## Available Tools (22)
44
+
45
+ ### Workflows (8)
46
+
47
+ | Tool | Description |
48
+ |------|-------------|
49
+ | `list_workflows` | List all workflows with pagination |
50
+ | `get_workflow` | Get full workflow details by ID |
51
+ | `create_workflow` | Create a new workflow |
52
+ | `update_workflow` | Update an existing workflow |
53
+ | `delete_workflow` | Delete a workflow (irreversible) |
54
+ | `activate_workflow` | Activate a workflow's triggers |
55
+ | `deactivate_workflow` | Deactivate a workflow |
56
+ | `execute_workflow` | Execute a workflow immediately |
57
+
58
+ ### Executions (3)
59
+
60
+ | Tool | Description |
61
+ |------|-------------|
62
+ | `list_executions` | List executions with filters |
63
+ | `get_execution` | Get execution details and results |
64
+ | `delete_execution` | Delete an execution record |
65
+
66
+ ### Tags (4)
67
+
68
+ | Tool | Description |
69
+ |------|-------------|
70
+ | `list_tags` | List all tags |
71
+ | `create_tag` | Create a new tag |
72
+ | `update_tag` | Rename a tag |
73
+ | `delete_tag` | Delete a tag |
74
+
75
+ ### Variables (4)
76
+
77
+ | Tool | Description |
78
+ |------|-------------|
79
+ | `list_variables` | List all variables (values masked) |
80
+ | `create_variable` | Create a new variable |
81
+ | `update_variable` | Update a variable |
82
+ | `delete_variable` | Delete a variable |
83
+
84
+ ### Credentials (1)
85
+
86
+ | Tool | Description |
87
+ |------|-------------|
88
+ | `list_credentials` | List credentials (metadata only) |
89
+
90
+ ### Users (2)
91
+
92
+ | Tool | Description |
93
+ |------|-------------|
94
+ | `list_users` | List all users |
95
+ | `get_user` | Get user details |
96
+
97
+ ## Security
98
+
99
+ - All resource IDs are validated as numeric strings to prevent path traversal
100
+ - Error responses are sanitised to avoid leaking n8n instance internals
101
+ - Variable values are masked in list output to prevent secret exposure
102
+ - All API calls have a 30-second timeout
103
+ - API keys are never logged or exposed in tool output
104
+ - Credentials tool only returns metadata (secrets are never exposed)
105
+
106
+ ## Development
107
+
108
+ ```bash
109
+ git clone https://github.com/ntatschner/tcs-n8n-mcp.git
110
+ cd tcs-n8n-mcp
111
+ npm install
112
+ npm run build
113
+ npm test
114
+ ```
115
+
116
+ ### Running locally
117
+
118
+ ```bash
119
+ N8N_API_KEY=your-key N8N_API_URL=http://localhost:5678 npm start
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
package/build/index.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { registerWorkflowTools } from "./tools/workflows.js";
5
+ import { registerExecutionTools } from "./tools/executions.js";
6
+ import { registerTagTools } from "./tools/tags.js";
7
+ import { registerVariableTools } from "./tools/variables.js";
8
+ import { registerCredentialTools } from "./tools/credentials.js";
9
+ import { registerUserTools } from "./tools/users.js";
10
+ const N8N_API_URL = process.env.N8N_API_URL || "http://localhost:5678";
11
+ const N8N_API_KEY = process.env.N8N_API_KEY || "";
12
+ if (!N8N_API_KEY) {
13
+ console.error("N8N_API_KEY environment variable is required");
14
+ process.exit(1);
15
+ }
16
+ const apiBase = `${N8N_API_URL.replace(/\/$/, "")}/api/v1`;
17
+ const DEFAULT_TIMEOUT_MS = 30_000;
18
+ const n8nFetch = (path, options = {}) => {
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
21
+ return fetch(`${apiBase}${path}`, {
22
+ ...options,
23
+ signal: options.signal ?? controller.signal,
24
+ headers: {
25
+ "X-N8N-API-KEY": N8N_API_KEY,
26
+ "Content-Type": "application/json",
27
+ ...options.headers,
28
+ },
29
+ }).finally(() => clearTimeout(timeoutId));
30
+ };
31
+ const server = new McpServer({
32
+ name: "@thecodesaiyan/tcs-n8n-mcp",
33
+ version: "1.0.2",
34
+ });
35
+ // Register all tool modules
36
+ registerWorkflowTools(server, n8nFetch);
37
+ registerExecutionTools(server, n8nFetch);
38
+ registerTagTools(server, n8nFetch);
39
+ registerVariableTools(server, n8nFetch);
40
+ registerCredentialTools(server, n8nFetch);
41
+ registerUserTools(server, n8nFetch);
42
+ async function main() {
43
+ const transport = new StdioServerTransport();
44
+ await server.connect(transport);
45
+ console.error("@thecodesaiyan/tcs-n8n-mcp v1.0.2 running on stdio (22 tools)");
46
+ }
47
+ main().catch((error) => {
48
+ console.error("Fatal error:", error);
49
+ process.exit(1);
50
+ });
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,26 @@
1
+ import { z } from "zod";
2
+ import { ok, err, handleError, safeJson, } from "../types.js";
3
+ import { paginationCursor } from "../validation.js";
4
+ export function registerCredentialTools(server, n8nFetch) {
5
+ // --- list_credentials ---
6
+ server.tool("list_credentials", "List all n8n credentials (metadata only — secrets are not exposed). Shows name, type, and creation date.", {
7
+ limit: z.number().int().positive().max(200).optional().default(50).describe("Max results"),
8
+ cursor: paginationCursor,
9
+ }, async ({ limit, cursor }) => {
10
+ const params = new URLSearchParams();
11
+ if (limit)
12
+ params.set("limit", String(limit));
13
+ if (cursor)
14
+ params.set("cursor", cursor);
15
+ const res = await n8nFetch(`/credentials?${params}`);
16
+ if (!res.ok)
17
+ return handleError(res, "listing credentials");
18
+ const json = await safeJson(res);
19
+ if (!json)
20
+ return err("Failed to parse credential list response");
21
+ const creds = json.data ?? [];
22
+ const lines = (Array.isArray(creds) ? creds : []).map((c) => `- ${c.name} (ID: ${c.id}, Type: ${c.type}, Created: ${c.createdAt ?? "N/A"})`);
23
+ return ok(`Found ${lines.length} credentials:\n${lines.join("\n")}`);
24
+ });
25
+ }
26
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1,52 @@
1
+ import { z } from "zod";
2
+ import { ok, err, handleError, safeJson, } from "../types.js";
3
+ import { n8nId, paginationCursor } from "../validation.js";
4
+ export function registerExecutionTools(server, n8nFetch) {
5
+ // --- list_executions ---
6
+ server.tool("list_executions", "List n8n workflow executions with optional filters for workflow ID and status.", {
7
+ workflowId: n8nId.optional().describe("Filter by workflow ID"),
8
+ status: z.enum(["error", "success", "waiting"]).optional().describe("Filter by execution status"),
9
+ limit: z.number().int().positive().max(200).optional().default(20).describe("Max results (default 20)"),
10
+ cursor: paginationCursor,
11
+ }, async ({ workflowId, status, limit, cursor }) => {
12
+ const params = new URLSearchParams();
13
+ if (workflowId)
14
+ params.set("workflowId", workflowId);
15
+ if (status)
16
+ params.set("status", status);
17
+ if (limit)
18
+ params.set("limit", String(limit));
19
+ if (cursor)
20
+ params.set("cursor", cursor);
21
+ const res = await n8nFetch(`/executions?${params}`);
22
+ if (!res.ok)
23
+ return handleError(res, "listing executions");
24
+ const json = await safeJson(res);
25
+ if (!json)
26
+ return err("Failed to parse execution list response");
27
+ const execs = json.data ?? [];
28
+ const lines = (Array.isArray(execs) ? execs : []).map((e) => `- #${e.id} | Workflow: ${e.workflowId} | Status: ${e.status} | Started: ${e.startedAt ?? "N/A"} | Finished: ${e.stoppedAt ?? "running"}`);
29
+ const next = json.nextCursor ? `\nNext cursor: ${json.nextCursor}` : "";
30
+ return ok(`Found ${lines.length} executions:\n${lines.join("\n")}${next}`);
31
+ });
32
+ // --- get_execution ---
33
+ server.tool("get_execution", "Get full details of an n8n execution including node results and error data.", {
34
+ executionId: n8nId.describe("ID of the execution"),
35
+ }, async ({ executionId }) => {
36
+ const res = await n8nFetch(`/executions/${executionId}`);
37
+ if (!res.ok)
38
+ return handleError(res, "fetching execution");
39
+ const exec = await safeJson(res);
40
+ if (!exec)
41
+ return err("Failed to parse execution response");
42
+ return ok(JSON.stringify(exec, null, 2));
43
+ });
44
+ // --- delete_execution ---
45
+ server.tool("delete_execution", "Delete an n8n execution record by ID.", { executionId: n8nId.describe("ID of the execution to delete") }, async ({ executionId }) => {
46
+ const res = await n8nFetch(`/executions/${executionId}`, { method: "DELETE" });
47
+ if (!res.ok)
48
+ return handleError(res, "deleting execution");
49
+ return ok(`Deleted execution ${executionId}`);
50
+ });
51
+ }
52
+ //# sourceMappingURL=executions.js.map
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import { ok, err, handleError, safeJson, } from "../types.js";
3
+ import { n8nId, paginationCursor } from "../validation.js";
4
+ export function registerTagTools(server, n8nFetch) {
5
+ // --- list_tags ---
6
+ server.tool("list_tags", "List all n8n tags used for organizing workflows.", {
7
+ limit: z.number().int().positive().max(200).optional().default(50).describe("Max results"),
8
+ cursor: paginationCursor,
9
+ }, async ({ limit, cursor }) => {
10
+ const params = new URLSearchParams();
11
+ if (limit)
12
+ params.set("limit", String(limit));
13
+ if (cursor)
14
+ params.set("cursor", cursor);
15
+ const res = await n8nFetch(`/tags?${params}`);
16
+ if (!res.ok)
17
+ return handleError(res, "listing tags");
18
+ const json = await safeJson(res);
19
+ if (!json)
20
+ return err("Failed to parse tag list response");
21
+ const tags = json.data ?? [];
22
+ const lines = (Array.isArray(tags) ? tags : []).map((t) => `- ${t.name} (ID: ${t.id})`);
23
+ return ok(`Found ${lines.length} tags:\n${lines.join("\n")}`);
24
+ });
25
+ // --- create_tag ---
26
+ server.tool("create_tag", "Create a new tag for organizing n8n workflows.", { name: z.string().describe("Tag name") }, async ({ name }) => {
27
+ const res = await n8nFetch("/tags", { method: "POST", body: JSON.stringify({ name }) });
28
+ if (!res.ok)
29
+ return handleError(res, "creating tag");
30
+ const tag = await safeJson(res);
31
+ if (!tag)
32
+ return err("Failed to parse create tag response");
33
+ return ok(`Created tag "${tag.name}" (ID: ${tag.id})`);
34
+ });
35
+ // --- update_tag ---
36
+ server.tool("update_tag", "Rename an existing n8n tag.", {
37
+ tagId: n8nId.describe("ID of the tag to update"),
38
+ name: z.string().describe("New tag name"),
39
+ }, async ({ tagId, name }) => {
40
+ const res = await n8nFetch(`/tags/${tagId}`, { method: "PUT", body: JSON.stringify({ name }) });
41
+ if (!res.ok)
42
+ return handleError(res, "updating tag");
43
+ const tag = await safeJson(res);
44
+ if (!tag)
45
+ return err("Failed to parse update tag response");
46
+ return ok(`Updated tag to "${tag.name}" (ID: ${tag.id})`);
47
+ });
48
+ // --- delete_tag ---
49
+ server.tool("delete_tag", "Delete an n8n tag by ID.", { tagId: n8nId.describe("ID of the tag to delete") }, async ({ tagId }) => {
50
+ const res = await n8nFetch(`/tags/${tagId}`, { method: "DELETE" });
51
+ if (!res.ok)
52
+ return handleError(res, "deleting tag");
53
+ return ok(`Deleted tag ${tagId}`);
54
+ });
55
+ }
56
+ //# sourceMappingURL=tags.js.map
@@ -0,0 +1,38 @@
1
+ import { z } from "zod";
2
+ import { ok, err, handleError, safeJson, } from "../types.js";
3
+ import { n8nId, paginationCursor } from "../validation.js";
4
+ export function registerUserTools(server, n8nFetch) {
5
+ // --- list_users ---
6
+ server.tool("list_users", "List all n8n users with their roles and status.", {
7
+ limit: z.number().int().positive().max(200).optional().default(50).describe("Max results"),
8
+ cursor: paginationCursor,
9
+ }, async ({ limit, cursor }) => {
10
+ const params = new URLSearchParams();
11
+ if (limit)
12
+ params.set("limit", String(limit));
13
+ if (cursor)
14
+ params.set("cursor", cursor);
15
+ const res = await n8nFetch(`/users?${params}`);
16
+ if (!res.ok)
17
+ return handleError(res, "listing users");
18
+ const json = await safeJson(res);
19
+ if (!json)
20
+ return err("Failed to parse user list response");
21
+ const users = json.data ?? [];
22
+ const lines = (Array.isArray(users) ? users : []).map((u) => `- ${u.firstName ?? ""} ${u.lastName ?? ""} (ID: ${u.id}, Email: ${u.email}, Role: ${u.role ?? "N/A"})`);
23
+ return ok(`Found ${lines.length} users:\n${lines.join("\n")}`);
24
+ });
25
+ // --- get_user ---
26
+ server.tool("get_user", "Get full details of an n8n user by ID.", {
27
+ userId: n8nId.describe("ID of the user"),
28
+ }, async ({ userId }) => {
29
+ const res = await n8nFetch(`/users/${userId}`);
30
+ if (!res.ok)
31
+ return handleError(res, "fetching user");
32
+ const user = await safeJson(res);
33
+ if (!user)
34
+ return err("Failed to parse user response");
35
+ return ok(JSON.stringify(user, null, 2));
36
+ });
37
+ }
38
+ //# sourceMappingURL=users.js.map
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { ok, err, handleError, safeJson, } from "../types.js";
3
+ import { n8nId, paginationCursor } from "../validation.js";
4
+ export function registerVariableTools(server, n8nFetch) {
5
+ // --- list_variables ---
6
+ server.tool("list_variables", "List all n8n environment variables (values are masked for security).", {
7
+ limit: z.number().int().positive().max(200).optional().default(50).describe("Max results"),
8
+ cursor: paginationCursor,
9
+ }, async ({ limit, cursor }) => {
10
+ const params = new URLSearchParams();
11
+ if (limit)
12
+ params.set("limit", String(limit));
13
+ if (cursor)
14
+ params.set("cursor", cursor);
15
+ const res = await n8nFetch(`/variables?${params}`);
16
+ if (!res.ok)
17
+ return handleError(res, "listing variables");
18
+ const json = await safeJson(res);
19
+ if (!json)
20
+ return err("Failed to parse variable list response");
21
+ const vars = json.data ?? [];
22
+ const lines = (Array.isArray(vars) ? vars : []).map((v) => `- ${v.key} (ID: ${v.id})`);
23
+ return ok(`Found ${lines.length} variables:\n${lines.join("\n")}`);
24
+ });
25
+ // --- create_variable ---
26
+ server.tool("create_variable", "Create a new n8n environment variable (key-value pair).", {
27
+ key: z.string().describe("Variable key/name"),
28
+ value: z.string().describe("Variable value"),
29
+ }, async ({ key, value }) => {
30
+ const res = await n8nFetch("/variables", { method: "POST", body: JSON.stringify({ key, value }) });
31
+ if (!res.ok)
32
+ return handleError(res, "creating variable");
33
+ const v = await safeJson(res);
34
+ if (!v)
35
+ return err("Failed to parse create variable response");
36
+ return ok(`Created variable "${v.key}" (ID: ${v.id})`);
37
+ });
38
+ // --- update_variable ---
39
+ server.tool("update_variable", "Update an existing n8n environment variable.", {
40
+ variableId: n8nId.describe("ID of the variable to update"),
41
+ key: z.string().optional().describe("New key name"),
42
+ value: z.string().optional().describe("New value"),
43
+ }, async ({ variableId, key, value }) => {
44
+ const body = {};
45
+ if (key !== undefined)
46
+ body.key = key;
47
+ if (value !== undefined)
48
+ body.value = value;
49
+ const res = await n8nFetch(`/variables/${variableId}`, { method: "PUT", body: JSON.stringify(body) });
50
+ if (!res.ok)
51
+ return handleError(res, "updating variable");
52
+ const v = await safeJson(res);
53
+ if (!v)
54
+ return err("Failed to parse update variable response");
55
+ return ok(`Updated variable "${v.key}" (ID: ${v.id})`);
56
+ });
57
+ // --- delete_variable ---
58
+ server.tool("delete_variable", "Delete an n8n environment variable by ID.", { variableId: n8nId.describe("ID of the variable to delete") }, async ({ variableId }) => {
59
+ const res = await n8nFetch(`/variables/${variableId}`, { method: "DELETE" });
60
+ if (!res.ok)
61
+ return handleError(res, "deleting variable");
62
+ return ok(`Deleted variable ${variableId}`);
63
+ });
64
+ }
65
+ //# sourceMappingURL=variables.js.map
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ import { ok, err, handleError, safeJson, } from "../types.js";
3
+ import { n8nId, paginationCursor } from "../validation.js";
4
+ const nodeSchema = z.object({
5
+ id: z.string(),
6
+ name: z.string(),
7
+ type: z.string(),
8
+ typeVersion: z.number(),
9
+ position: z.tuple([z.number(), z.number()]),
10
+ parameters: z.record(z.unknown()).optional().default({}),
11
+ onError: z.string().optional().describe("Error handling: 'continueRegularOutput' | 'continueErrorOutput' | 'stopWorkflow'"),
12
+ credentials: z.record(z.unknown()).optional().describe("Credential references for this node"),
13
+ webhookId: z.string().optional().describe("Webhook ID for trigger nodes"),
14
+ });
15
+ export function registerWorkflowTools(server, n8nFetch) {
16
+ // --- list_workflows ---
17
+ server.tool("list_workflows", "List all n8n workflows with optional name filter and pagination.", {
18
+ limit: z.number().int().positive().max(200).optional().default(50).describe("Max results (default 50)"),
19
+ cursor: paginationCursor,
20
+ }, async ({ limit, cursor }) => {
21
+ const params = new URLSearchParams();
22
+ if (limit)
23
+ params.set("limit", String(limit));
24
+ if (cursor)
25
+ params.set("cursor", cursor);
26
+ const res = await n8nFetch(`/workflows?${params}`);
27
+ if (!res.ok)
28
+ return handleError(res, "listing workflows");
29
+ const json = await safeJson(res);
30
+ if (!json)
31
+ return err("Failed to parse workflow list response");
32
+ const workflows = json.data ?? [];
33
+ const lines = (Array.isArray(workflows) ? workflows : []).map((w) => `- ${w.name} (ID: ${w.id}, Active: ${w.active})`);
34
+ const next = json.nextCursor ? `\nNext cursor: ${json.nextCursor}` : "";
35
+ return ok(`Found ${lines.length} workflows:\n${lines.join("\n")}${next}`);
36
+ });
37
+ // --- get_workflow ---
38
+ server.tool("get_workflow", "Get full details of an n8n workflow by ID, including nodes, connections, and settings.", {
39
+ workflowId: n8nId.describe("ID of the workflow"),
40
+ }, async ({ workflowId }) => {
41
+ const res = await n8nFetch(`/workflows/${workflowId}`);
42
+ if (!res.ok)
43
+ return handleError(res, "fetching workflow");
44
+ const w = await safeJson(res);
45
+ if (!w)
46
+ return err("Failed to parse workflow response");
47
+ return ok(JSON.stringify(w, null, 2));
48
+ });
49
+ // --- create_workflow ---
50
+ server.tool("create_workflow", "Create a new n8n workflow. Provide a name and optionally nodes/connections. Defaults to a Manual Trigger node if no nodes given.", {
51
+ name: z.string().describe("Workflow name"),
52
+ nodes: z.array(nodeSchema).optional().describe("Array of workflow nodes (defaults to Manual Trigger)"),
53
+ connections: z.record(z.unknown()).optional().default({}).describe("Node connections mapping"),
54
+ settings: z.record(z.unknown()).optional().default({}).describe("Workflow settings"),
55
+ }, async ({ name, nodes, connections, settings }) => {
56
+ const defaultNodes = [
57
+ {
58
+ id: "trigger-1",
59
+ name: "Manual Trigger",
60
+ type: "n8n-nodes-base.manualTrigger",
61
+ typeVersion: 1,
62
+ position: [0, 0],
63
+ parameters: {},
64
+ },
65
+ ];
66
+ const body = { name, nodes: nodes ?? defaultNodes, connections: connections ?? {}, settings: settings ?? {} };
67
+ const res = await n8nFetch("/workflows", { method: "POST", body: JSON.stringify(body) });
68
+ if (!res.ok)
69
+ return handleError(res, "creating workflow");
70
+ const w = await safeJson(res);
71
+ if (!w)
72
+ return err("Failed to parse create workflow response");
73
+ return ok(`Created workflow "${w.name}" (ID: ${w.id}, Active: ${w.active})`);
74
+ });
75
+ // --- update_workflow ---
76
+ server.tool("update_workflow", "Update an existing n8n workflow. Provide the workflow ID and any fields to change.", {
77
+ workflowId: n8nId.describe("ID of the workflow to update"),
78
+ name: z.string().optional().describe("New workflow name"),
79
+ nodes: z.array(nodeSchema).optional().describe("Updated nodes array"),
80
+ connections: z.record(z.unknown()).optional().describe("Updated connections"),
81
+ settings: z.record(z.unknown()).optional().describe("Updated settings"),
82
+ }, async ({ workflowId, name, nodes, connections, settings }) => {
83
+ const getRes = await n8nFetch(`/workflows/${workflowId}`);
84
+ if (!getRes.ok)
85
+ return handleError(getRes, "fetching workflow for update");
86
+ const current = await safeJson(getRes);
87
+ if (!current)
88
+ return err("Failed to parse current workflow for update");
89
+ const body = {
90
+ name: name ?? current.name,
91
+ nodes: nodes ?? current.nodes,
92
+ connections: connections ?? current.connections,
93
+ settings: settings ?? current.settings,
94
+ };
95
+ const res = await n8nFetch(`/workflows/${workflowId}`, { method: "PUT", body: JSON.stringify(body) });
96
+ if (!res.ok)
97
+ return handleError(res, "updating workflow");
98
+ const w = await safeJson(res);
99
+ if (!w)
100
+ return err("Failed to parse update workflow response");
101
+ return ok(`Updated workflow "${w.name}" (ID: ${w.id})`);
102
+ });
103
+ // --- delete_workflow ---
104
+ server.tool("delete_workflow", "Delete an n8n workflow by ID. This is irreversible.", { workflowId: n8nId.describe("ID of the workflow to delete") }, async ({ workflowId }) => {
105
+ const res = await n8nFetch(`/workflows/${workflowId}`, { method: "DELETE" });
106
+ if (!res.ok)
107
+ return handleError(res, "deleting workflow");
108
+ return ok(`Deleted workflow ${workflowId}`);
109
+ });
110
+ // --- activate_workflow ---
111
+ server.tool("activate_workflow", "Activate an n8n workflow so it runs on its configured triggers.", { workflowId: n8nId.describe("ID of the workflow to activate") }, async ({ workflowId }) => {
112
+ const res = await n8nFetch(`/workflows/${workflowId}/activate`, { method: "POST" });
113
+ if (!res.ok)
114
+ return handleError(res, "activating workflow");
115
+ return ok(`Activated workflow ${workflowId}`);
116
+ });
117
+ // --- deactivate_workflow ---
118
+ server.tool("deactivate_workflow", "Deactivate an n8n workflow so it stops running on triggers.", { workflowId: n8nId.describe("ID of the workflow to deactivate") }, async ({ workflowId }) => {
119
+ const res = await n8nFetch(`/workflows/${workflowId}/deactivate`, { method: "POST" });
120
+ if (!res.ok)
121
+ return handleError(res, "deactivating workflow");
122
+ return ok(`Deactivated workflow ${workflowId}`);
123
+ });
124
+ // --- execute_workflow ---
125
+ server.tool("execute_workflow", "Execute an n8n workflow immediately and return the execution ID. The workflow runs asynchronously.", { workflowId: n8nId.describe("ID of the workflow to execute") }, async ({ workflowId }) => {
126
+ const res = await n8nFetch(`/workflows/${workflowId}/execute`, {
127
+ method: "POST",
128
+ body: JSON.stringify({}),
129
+ });
130
+ if (!res.ok)
131
+ return handleError(res, "executing workflow");
132
+ const result = await safeJson(res);
133
+ if (!result)
134
+ return err("Failed to parse execution response");
135
+ return ok(`Execution started for workflow ${workflowId} (Execution ID: ${result.executionId})`);
136
+ });
137
+ }
138
+ //# sourceMappingURL=workflows.js.map
package/build/types.js ADDED
@@ -0,0 +1,21 @@
1
+ // --- MCP response helpers ---
2
+ export function ok(msg) {
3
+ return { content: [{ type: "text", text: msg }] };
4
+ }
5
+ export function err(msg) {
6
+ return { content: [{ type: "text", text: msg }], isError: true };
7
+ }
8
+ export async function handleError(res, action) {
9
+ const status = res.status;
10
+ const statusText = res.statusText || "Unknown error";
11
+ return err(`Error ${action}: HTTP ${status} ${statusText}`);
12
+ }
13
+ export async function safeJson(res) {
14
+ try {
15
+ return (await res.json());
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Schema for n8n resource IDs.
4
+ * n8n uses numeric IDs (integers as strings) for all resources.
5
+ * Prevents path traversal attacks (e.g. "../admin").
6
+ */
7
+ export const n8nId = z
8
+ .string()
9
+ .regex(/^\d+$/, "ID must be a numeric string (e.g. '123')")
10
+ .describe("Numeric resource ID");
11
+ /**
12
+ * Schema for pagination cursors.
13
+ * Cursors are alphanumeric base64-like strings from the n8n API.
14
+ */
15
+ export const paginationCursor = z
16
+ .string()
17
+ .regex(/^[a-zA-Z0-9+/=_-]+$/, "Invalid cursor format")
18
+ .optional()
19
+ .describe("Pagination cursor from previous response");
20
+ //# sourceMappingURL=validation.js.map
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@thecodesaiyan/tcs-n8n-mcp",
3
+ "version": "1.0.2",
4
+ "description": "MCP server for n8n workflow automation. Manage workflows, executions, tags, variables, credentials and users via the Model Context Protocol.",
5
+ "type": "module",
6
+ "main": "build/index.js",
7
+ "bin": {
8
+ "tcs-n8n-mcp": "build/index.js"
9
+ },
10
+ "files": [
11
+ "build/**/*.js",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "start": "node build/index.js",
21
+ "prepare": "npm run build",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:coverage": "vitest run --coverage"
25
+ },
26
+ "keywords": [
27
+ "mcp",
28
+ "model-context-protocol",
29
+ "n8n",
30
+ "workflow",
31
+ "automation",
32
+ "ai",
33
+ "llm",
34
+ "claude",
35
+ "mcp-server"
36
+ ],
37
+ "author": "ntatschner",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/ntatschner/tcs-n8n-mcp.git"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/ntatschner/tcs-n8n-mcp/issues"
45
+ },
46
+ "homepage": "https://github.com/ntatschner/tcs-n8n-mcp#readme",
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.26.0",
49
+ "zod": "^3.23.0"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.0.0",
53
+ "@vitest/coverage-v8": "^4.0.18",
54
+ "typescript": "^5.0.0",
55
+ "vitest": "^4.0.18"
56
+ }
57
+ }