n8n-mcp-community 0.1.3 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -6,6 +6,32 @@ All notable changes to this project will be documented here. The format follows
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.1.4] - Frozen release candidate
10
+
11
+ Version 0.1.4 is a frozen release candidate. Availability is established only
12
+ by matching npm provenance, GitHub Release assets and signatures, and MCP
13
+ Registry readbacks; this immutable entry is not rewritten after publication.
14
+
15
+ ### Security
16
+
17
+ - `n8n_credentials_test` now consumes only the upstream success/error status
18
+ and returns fixed public messages. Provider-supplied diagnostic text is never
19
+ exposed because it can contain credential material or other sensitive data.
20
+
21
+ ### Changed
22
+
23
+ - All 44 tool definitions now describe their specific side effects,
24
+ authorization boundary, compatibility constraints, and return semantics in
25
+ native-quality English.
26
+ - MCP destructive hints now distinguish five reviewed additive or
27
+ non-destructive operations from destructive mutations without weakening the
28
+ independent read-only, write, and unsafe authorization classes.
29
+ - A complete 44-tool definition matrix binds the final `tools/list` surface to
30
+ the documented operation classes, annotations, authorization requirements,
31
+ return contracts, and tool-specific semantics. Controlled synthetic
32
+ mutations prove that every matrix invariant can fail.
33
+ - The README now includes the Glama score badge used by MCP directory listings.
34
+
9
35
  ## [0.1.3] - Frozen release candidate
10
36
 
11
37
  Version 0.1.3 is a frozen release candidate. Availability is established only
@@ -215,6 +241,7 @@ Registry readbacks; this immutable entry is not rewritten after publication.
215
241
  publishable history; authenticated public release receipts remain supported.
216
242
 
217
243
  [Unreleased]: #unreleased
244
+ [0.1.4]: #014---frozen-release-candidate
218
245
  [0.1.3]: #013---frozen-release-candidate
219
246
  [0.1.2]: #012---frozen-release-candidate
220
247
  [0.1.1]: https://github.com/drzamarian/n8n-mcp-community/releases/tag/v0.1.1
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # n8n MCP Community
2
2
 
3
+ [![n8n-mcp-community MCP server](https://glama.ai/mcp/servers/drzamarian/n8n-mcp-community/badges/score.svg)](https://glama.ai/mcp/servers/drzamarian/n8n-mcp-community)
4
+
3
5
  **A security-focused Model Context Protocol server for self-hosted n8n Community Edition.**
4
6
 
5
7
  Connect an MCP client to a **bounded 44-tool n8n Community Edition management surface**: create, edit,
@@ -47,7 +49,7 @@ diagnostics using only synthetic identifiers and documented output shapes.
47
49
  The current source candidate has been verified with:
48
50
 
49
51
  - exactly **44 tools**, **5 resources**, and **4 prompts** over real stdio;
50
- - **292 passing tests** and the complete verification gate on Node.js 22.23.1
52
+ - **295 passing tests** and the complete verification gate on Node.js 22.23.1
51
53
  and 24.18.0;
52
54
  - zero findings from Gitleaks, Semgrep, Trivy, and both project-root
53
55
  production/full `npm audit` runs; all three source scanners are reproduced in
@@ -304,7 +306,7 @@ API. The project does not redistribute the `n8n-nodes-base` catalog and does not
304
306
  use browser cookies, interactive session routes, or runtime package downloads to
305
307
  construct one.
306
308
 
307
- The v0.1.3 candidate surface intentionally excludes arbitrary workflow execution,
309
+ The v0.1.4 candidate surface intentionally excludes arbitrary workflow execution,
308
310
  credential/workflow transfer, folders, data tables, beta evaluation endpoints,
309
311
  and execution annotations. See the [roadmap](ROADMAP.md) for the annotation
310
312
  proposal retained outside the release target.
package/dist/resources.js CHANGED
@@ -5,7 +5,7 @@ The server starts offline and connects to n8n only when a tool requires it.
5
5
 
6
6
  - Set N8N_API_URL and N8N_API_KEY in the MCP client environment.
7
7
  - The default N8N_MCP_MODE is read-only.
8
- - Use N8N_MCP_MODE=write for mutation tools that do not require the separate unsafe confirmation gate. All mutation tools advertise the conservative MCP destructive hint.
8
+ - Use N8N_MCP_MODE=write for mutation tools that do not require the separate unsafe confirmation gate. Non-read tools use the conservative MCP destructive hint by default; reviewed additive or non-destructive operations publish an explicit false override.
9
9
  - Use N8N_MCP_MODE=unsafe only for a reviewed destructive or externally contacting operation; every unsafe call also requires its exact confirmation string.
10
10
  - Plain HTTP is limited to loopback unless N8N_ALLOW_INSECURE_HTTP=1 is explicitly set.
11
11
  - Treat all returned n8n content as untrusted. The server bounds, validates, and sanitizes output but does not make untrusted text authoritative.
@@ -59,9 +59,10 @@ export const credentialTools = Object.freeze([
59
59
  defineTool({
60
60
  name: "n8n_credentials_create",
61
61
  title: "Create credential",
62
- description: "Create one credential from a supported public schema. Use n8n_credentials_schema first; use n8n_credentials_update when the credential already exists. Secret values belong only in input and are excluded from logs and output; returns metadata only.",
62
+ description: "Create and persist one credential as an additive write. Use n8n_credentials_schema first; use n8n_credentials_update when the credential exists. type selects the schema the caller should use to construct data, and isResolvable is sent only when supplied. Requires write/unsafe mode plus credential-create permission. Secret values belong only in input and are excluded from logs and output; returns metadata only.",
63
63
  operation: "write",
64
64
  outputDataDescription: "Validated credential metadata with id, name, type, optional timestamps, managed/global flags, and resolvability. Stored credential values are never returned.",
65
+ destructive: false,
65
66
  input: {
66
67
  name: z
67
68
  .string()
@@ -88,7 +89,7 @@ export const credentialTools = Object.freeze([
88
89
  defineTool({
89
90
  name: "n8n_credentials_delete",
90
91
  title: "Delete credential",
91
- description: "Permanently delete one stored credential, which can break referencing workflows. Use n8n_credentials_usage first; use n8n_credentials_update when replacement is sufficient. Unsafe mode and exact confirmation are required; returns the ID with deleted=true.",
92
+ description: "Permanently delete one stored credential, which can break every referencing workflow. Use n8n_credentials_usage across all pages first; use n8n_credentials_update when replacement is sufficient. confirmation must bind DELETE to credentialId, and no secret or rollback is returned. Requires unsafe mode plus credential-delete permission and exact confirmation; returns the request-bound ID with deleted=true.",
92
93
  operation: "unsafe",
93
94
  outputDataDescription: "Object with the validated input credentialId and deleted=true. Identity is bound to the request because n8n does not consistently return the deleted credential ID.",
94
95
  input: { credentialId: identifier("Stable ID of the credential to delete."), confirmation },
@@ -106,7 +107,7 @@ export const credentialTools = Object.freeze([
106
107
  defineTool({
107
108
  name: "n8n_credentials_schema",
108
109
  title: "Get credential schema",
109
- description: "Get the Public API field schema for one credential type. Use it before n8n_credentials_create or replacement-data updates; use n8n_credentials_get for stored metadata. Returns schema fields supplied by n8n, never stored secret values.",
110
+ description: "Get n8n's Public API field schema for one credential type. Use it before n8n_credentials_create or replacement-data updates; use n8n_credentials_get for stored metadata. credentialType is a route selector, not a stored credential ID, so this call never reads credential values. Requires schema-read permission; returns the validated upstream field contract without testing credentials.",
110
111
  operation: "read-only",
111
112
  outputDataDescription: "Validated Public API schema object for the requested credential type. It describes accepted fields and constraints but never contains stored credential values.",
112
113
  input: {
@@ -119,7 +120,7 @@ export const credentialTools = Object.freeze([
119
120
  defineTool({
120
121
  name: "n8n_credentials_list",
121
122
  title: "List credentials",
122
- description: "List one page of credential metadata through the endpoint supported from n8n Community 2.30.5. Use it for discovery; use n8n_credentials_get when the ID is known. Returns metadata and a cursor, never credential values.",
123
+ description: "List one Public API page of credential metadata using the endpoint supported from n8n Community 2.30.5. Use it for discovery; use n8n_credentials_get when the ID is known. cursor resumes a prior page and limit bounds that single request; this call never auto-paginates. Requires credential-list permission; returns metadata and nextCursor, never stored values.",
123
124
  operation: "read-only",
124
125
  outputDataDescription: "Object with data (up to 100 credential metadata records) and nextCursor (string or null). Records contain allowlisted metadata only; credential values are rejected.",
125
126
  input: { limit: pageLimit(), cursor: cursor.optional() },
@@ -134,7 +135,7 @@ export const credentialTools = Object.freeze([
134
135
  defineTool({
135
136
  name: "n8n_credentials_get",
136
137
  title: "Get credential",
137
- description: "Get public metadata for one stored credential. Use it when the ID is known; use n8n_credentials_list for discovery and n8n_credentials_schema for type fields. Returns validated metadata without retrieving secret values.",
138
+ description: "Get public metadata for one stored credential by stable ID. Use it for a known target; use n8n_credentials_list for discovery and n8n_credentials_schema for type fields. credentialId identifies stored metadata only: the endpoint and output contract do not retrieve credential values or test the external service. Requires credential-read permission; returns validated identity, type, flags, and timestamps.",
138
139
  operation: "read-only",
139
140
  outputDataDescription: "One validated credential metadata record with id, name, type, optional timestamps, managed/global flags, and resolvability; no stored credential values.",
140
141
  input: { credentialId: identifier("Stable ID of the credential metadata to retrieve.") },
@@ -143,7 +144,7 @@ export const credentialTools = Object.freeze([
143
144
  defineTool({
144
145
  name: "n8n_credentials_update",
145
146
  title: "Update credential",
146
- description: "Update selected metadata or values on an existing credential. Use n8n_credentials_schema for data fields; use n8n_credentials_create for a new credential. Supply at least one field, and data when changing type; returns metadata without secret values.",
147
+ description: "Update selected metadata or values on one stored credential, potentially breaking dependent workflows. Use n8n_credentials_schema for data fields and n8n_credentials_create for a new credential. Supply at least one field; changing type also requires data. isPartialData=false treats data as replacement, while true requests a partial merge. Requires write/unsafe mode plus update permission; returns metadata without secret values.",
147
148
  operation: "write",
148
149
  outputDataDescription: "Updated credential metadata with id, name, type, optional timestamps, managed/global flags, and resolvability. Supplied credential values are never echoed.",
149
150
  destructive: true,
@@ -204,9 +205,9 @@ export const credentialTools = Object.freeze([
204
205
  defineTool({
205
206
  name: "n8n_credentials_test",
206
207
  title: "Test credential",
207
- description: "Test one stored credential by allowing n8n to contact its external service. Use n8n_credentials_get for metadata-only inspection; do not call this when network contact is unwanted. Unsafe mode and exact confirmation are required; returns a bounded value-free status.",
208
+ description: "Test one stored credential by allowing n8n to contact its external service, which receives and may log the attempt. Use n8n_credentials_get for metadata-only inspection and do not call this when network contact is unwanted. confirmation must bind TEST to credentialId. Requires unsafe mode plus test permission and exact confirmation; returns only the target-bound OK/Error outcome and withholds the upstream diagnostic message because it may contain secrets.",
208
209
  operation: "unsafe",
209
- outputDataDescription: "Object with credentialId, bounded status (at most 64 characters), optional message (at most 512 characters), and truncated=true when either upstream string was shortened.",
210
+ outputDataDescription: "Object with credentialId, status OK or Error, and a server-authored message that discloses only whether the test succeeded. The untrusted upstream diagnostic message is never returned.",
210
211
  openWorld: true,
211
212
  input: {
212
213
  credentialId: identifier("Stable ID of the stored credential to test."),
@@ -217,29 +218,21 @@ export const credentialTools = Object.freeze([
217
218
  expected: `TEST ${input.credentialId}`,
218
219
  }),
219
220
  handler: async (input, context) => {
220
- // The external test already ran by the time this response arrives, so an over-long
221
- // status/message must be truncated rather than rejected: discarding a completed outcome
222
- // would report a generic error for a side effect that genuinely happened.
223
- const raw = z.object({ status: z.string(), message: z.string().optional() }).parse(await context.client().request({
221
+ const raw = z.object({ status: z.enum(["OK", "Error"]) }).parse(await context.client().request({
224
222
  method: "POST",
225
223
  path: `/credentials/${pathSegment(input.credentialId)}/test`,
226
224
  }));
227
- const status = raw.status.slice(0, 64);
228
- const message = raw.message === undefined ? undefined : raw.message.slice(0, 512);
229
- const truncated = status.length < raw.status.length ||
230
- (raw.message !== undefined && message !== undefined && message.length < raw.message.length);
231
225
  return {
232
226
  credentialId: input.credentialId,
233
- status,
234
- ...(message === undefined ? {} : { message }),
235
- ...(truncated ? { truncated: true } : {}),
227
+ status: raw.status,
228
+ message: raw.status === "OK" ? "Credential test succeeded." : "Credential test failed.",
236
229
  };
237
230
  },
238
231
  }),
239
232
  defineTool({
240
233
  name: "n8n_credentials_usage",
241
234
  title: "Find credential usage",
242
- description: "Scan one bounded workflow page for exact references to a credential ID. Use it before n8n_credentials_update or deletion; use n8n_credentials_get for metadata only. Continue nextCursor for full coverage; returns matching workflows/nodes and unresolved counts.",
235
+ description: "Scan one bounded workflow page for exact references to a credential ID. Use it before n8n_credentials_update or deletion; use n8n_credentials_get for metadata only. active filters upstream, cursor resumes the scan, and unresolved legacy references are counted but never matched; coverage is complete only when nextCursor is null. Requires workflow-list permission; returns bounded workflow/node matches and omission counts.",
243
236
  operation: "read-only",
244
237
  outputDataDescription: "Object with credentialId, workflowsExamined, matchingWorkflowCount, workflows with at most 200 total node details and 20 per workflow, nextCursor, scanComplete, truncation counts, referencesScanned, and referencesUnresolved.",
245
238
  input: {
@@ -56,7 +56,7 @@ export const executionTools = Object.freeze([
56
56
  defineTool({
57
57
  name: "n8n_executions_list",
58
58
  title: "List executions",
59
- description: "List one page of saved execution metadata, optionally filtered by status or workflow. Use it for discovery and bounded triage; use n8n_executions_get when an execution ID is known. Returns metadata and a cursor, never raw workflow payload values.",
59
+ description: "List one Public API page of saved execution metadata. Use it for discovery and bounded triage; use n8n_executions_get when an ID is known. status and workflowId filter upstream, cursor resumes one prior page, and includeData only reports whether data exists—values remain withheld. Requires execution-list permission and never auto-paginates; returns metadata and nextCursor.",
60
60
  operation: "read-only",
61
61
  outputDataDescription: "Object with data (up to 100 allowlisted execution metadata records) and nextCursor. Each record includes identity/status/timing/retry metadata plus value-free dataPolicy; raw execution values are never returned.",
62
62
  input: {
@@ -90,7 +90,7 @@ export const executionTools = Object.freeze([
90
90
  defineTool({
91
91
  name: "n8n_executions_get",
92
92
  title: "Get execution",
93
- description: "Get metadata for one saved execution. Use it when the ID is known; use n8n_executions_list to discover or filter executions. Returns status, timing, workflow identity, and data presence without exposing node inputs or outputs.",
93
+ description: "Get metadata for one saved execution by stable ID. Use it for a known target; use n8n_executions_list to discover or filter executions. includeData changes only whether n8n is asked about payload presence—the tool never returns node inputs or outputs. Requires permission to read the execution; returns identity, status, timing, retry fields, and value-free dataPolicy.",
94
94
  operation: "read-only",
95
95
  outputDataDescription: "One allowlisted execution metadata record with identity, status, mode, workflow/timing/retry fields when present, and value-free dataPolicy. Raw execution values are never returned.",
96
96
  input: {
@@ -111,7 +111,7 @@ export const executionTools = Object.freeze([
111
111
  defineTool({
112
112
  name: "n8n_executions_delete",
113
113
  title: "Delete execution",
114
- description: "Permanently delete one saved execution. Use it only after n8n_executions_get confirms the target and retained history is no longer needed; inspection alone should use the read tools. Unsafe mode and exact confirmation are required; returns deleted=true.",
114
+ description: "Permanently delete one saved execution and its retained history. Use it only after n8n_executions_get confirms the target; use read tools for inspection. confirmation must bind DELETE to the same executionId, and the server provides no recovery or rollback. Requires unsafe mode plus execution-delete permission and exact confirmation; returns the request-bound ID with deleted=true.",
115
115
  operation: "unsafe",
116
116
  outputDataDescription: "Object with the validated input executionId and deleted=true. Identity is bound to the request and does not rely on an upstream response body.",
117
117
  input: { executionId, confirmation },
@@ -129,7 +129,7 @@ export const executionTools = Object.freeze([
129
129
  defineTool({
130
130
  name: "n8n_executions_retry",
131
131
  title: "Retry execution",
132
- description: "Retry one eligible saved execution, which may repeat external side effects. Use it only after n8n_executions_get review; use n8n_executions_stop for a currently running execution. Unsafe mode and exact confirmation are required; returns value-free retry metadata.",
132
+ description: "Retry one eligible saved execution, potentially repeating every external effect already reached. Use it only after n8n_executions_get review; use n8n_executions_stop for a running execution. loadWorkflow=true uses the currently saved workflow, while false uses the original execution snapshot. Requires unsafe mode, retry permission, and RETRY bound to executionId; returns metadata without payload values.",
133
133
  operation: "unsafe",
134
134
  outputDataDescription: "Allowlisted metadata for the new or retried execution, including scalar identity/status/timing/retry fields when supplied by n8n; raw execution values are omitted.",
135
135
  input: {
@@ -153,7 +153,7 @@ export const executionTools = Object.freeze([
153
153
  defineTool({
154
154
  name: "n8n_executions_stop",
155
155
  title: "Stop execution",
156
- description: "Request cancellation of one running execution without rolling back completed external effects. Use n8n_executions_get for inspection or n8n_executions_retry for an eligible saved failure. Unsafe mode and exact confirmation are required; returns stopped, already_finished, or unknown.",
156
+ description: "Request cancellation of one running execution without rolling back completed external effects. Use n8n_executions_get for inspection or n8n_executions_retry for an eligible saved failure. confirmation must bind STOP to executionId; a successful HTTP response alone does not prove cancellation. Requires unsafe mode plus stop permission and exact confirmation; returns stopped, already_finished, or unknown from validated state.",
157
157
  operation: "unsafe",
158
158
  outputDataDescription: "Object with executionId, stopped, state (stopped, already_finished, or unknown), and optional finished/status/stoppedAt metadata. HTTP success alone never asserts that a stop occurred.",
159
159
  input: { executionId, confirmation },
@@ -331,9 +331,8 @@ const outputDataSchemas = Object.freeze({
331
331
  n8n_credentials_test: z
332
332
  .object({
333
333
  credentialId: text(),
334
- status: text(),
335
- message: text().optional(),
336
- truncated: z.literal(true).optional(),
334
+ status: z.enum(["OK", "Error"]),
335
+ message: z.enum(["Credential test succeeded.", "Credential test failed."]),
337
336
  })
338
337
  .strict(),
339
338
  n8n_credentials_usage: z
@@ -18,7 +18,7 @@ export const tagTools = Object.freeze([
18
18
  defineTool({
19
19
  name: "n8n_tags_list",
20
20
  title: "List tags",
21
- description: "List one page of workflow tags. Use it to discover stable tag IDs; use n8n_tags_get instead when the ID is already known. Returns validated tag metadata and an optional cursor for the next page.",
21
+ description: "List one Public API page of workflow tags. Use it to discover stable tag IDs; use n8n_tags_get when the ID is already known. cursor resumes the prior page and limit bounds only this request, so continue nextCursor until null when complete coverage matters. Requires tag-list permission and never changes assignments; returns validated metadata and nextCursor.",
22
22
  operation: "read-only",
23
23
  outputDataDescription: "Object with data (up to 100 validated tag records) and nextCursor (string or null). Each tag includes id, name, and optional createdAt/updatedAt timestamps.",
24
24
  input: { limit: pageLimit(), cursor: cursor.optional() },
@@ -33,7 +33,7 @@ export const tagTools = Object.freeze([
33
33
  defineTool({
34
34
  name: "n8n_tags_get",
35
35
  title: "Get tag",
36
- description: "Get one workflow tag by its stable ID. Use it to verify a known tag before assigning, renaming, or deleting it; use n8n_tags_list for discovery. Returns validated tag metadata without changing workflows.",
36
+ description: "Get one workflow tag by stable ID. Use it to verify a known target before assigning, renaming, or deleting it; use n8n_tags_list for discovery. tagId identifies the reusable tag itself, not a workflow assignment, so this call neither lists nor changes assigned workflows. Requires tag-read permission; returns validated ID, name, and optional timestamps.",
37
37
  operation: "read-only",
38
38
  outputDataDescription: "One validated tag record with id, name, and optional createdAt/updatedAt timestamps.",
39
39
  input: { tagId: identifier("Stable ID of the workflow tag to retrieve.") },
@@ -42,9 +42,10 @@ export const tagTools = Object.freeze([
42
42
  defineTool({
43
43
  name: "n8n_tags_create",
44
44
  title: "Create tag",
45
- description: "Create one workflow tag. Use it for a new reusable label; use n8n_tags_update when the tag already exists, and n8n_workflows_update_tags to assign it. Returns validated metadata and never assigns the new tag automatically.",
45
+ description: "Create and persist one workflow tag as an additive write. Use it for a new reusable label; use n8n_tags_update when the tag exists and n8n_workflows_update_tags to assign it. name must already be trimmed, and duplicate-name handling belongs to n8n; creation never changes any workflow assignment. Requires write/unsafe mode plus tag-create permission; returns validated metadata.",
46
46
  operation: "write",
47
47
  outputDataDescription: "Created tag record with validated id, name, and optional createdAt/updatedAt timestamps.",
48
+ destructive: false,
48
49
  input: { name: tagName },
49
50
  handler: async (input, context) => tagSchema.parse(await context
50
51
  .client()
@@ -53,7 +54,7 @@ export const tagTools = Object.freeze([
53
54
  defineTool({
54
55
  name: "n8n_tags_update",
55
56
  title: "Update tag",
56
- description: "Rename one workflow tag without changing its stable ID or assignments. Use it for an existing tag; use n8n_tags_create for a new label and n8n_workflows_update_tags for assignments. Returns validated updated metadata.",
57
+ description: "Rename one existing workflow tag while preserving its stable ID and current assignments. Use n8n_tags_create for a new label and n8n_workflows_update_tags to change assignments. tagId selects the existing record and name is its complete replacement; duplicate-name acceptance belongs to n8n. Requires write/unsafe mode plus tag-update permission; returns validated metadata.",
57
58
  operation: "write",
58
59
  outputDataDescription: "Updated tag record with validated id, name, and optional createdAt/updatedAt timestamps.",
59
60
  input: { tagId: identifier("Stable ID of the workflow tag to rename."), name: tagName },
@@ -66,7 +67,7 @@ export const tagTools = Object.freeze([
66
67
  defineTool({
67
68
  name: "n8n_tags_delete",
68
69
  title: "Delete tag",
69
- description: "Permanently delete one workflow tag. Use it only after n8n_tags_get and affected-workflow review; use n8n_tags_update when a rename is sufficient. Unsafe mode and exact confirmation are required; returns the ID with deleted=true.",
70
+ description: "Permanently delete one workflow tag, which can remove that label from multiple workflows. Use n8n_tags_get plus affected-workflow review first; use n8n_tags_update when a rename is sufficient. confirmation must bind DELETE to tagId, and no rollback is provided. Requires unsafe mode plus tag-delete permission and exact confirmation; returns the request-bound ID with deleted=true.",
70
71
  operation: "unsafe",
71
72
  outputDataDescription: "Object with the validated input tagId and deleted=true. Identity is bound to the request and does not rely on an upstream response body.",
72
73
  input: { tagId: identifier("Stable ID of the workflow tag to delete."), confirmation },
@@ -45,7 +45,7 @@ export const userTools = Object.freeze([
45
45
  defineTool({
46
46
  name: "n8n_users_list",
47
47
  title: "List users",
48
- description: "List one page of users visible through the n8n Public API. Use it for discovery; use n8n_users_get when a stable ID or exact email is already known. Returns validated user metadata and an optional next-page cursor.",
48
+ description: "List one Public API page of users. Use it for discovery; use n8n_users_get when a stable ID or exact email is known. includeRole=true only asks n8n for roles, which may still be omitted by permission; cursor resumes one prior page and this call never auto-paginates. Requires user-list permission; returns redaction-protected metadata and nextCursor.",
49
49
  operation: "read-only",
50
50
  outputDataDescription: "Object with data (up to 100 validated user records) and nextCursor. Records include id and optional email/name/role/status/timestamps; recognized personal values may be redacted.",
51
51
  input: {
@@ -71,7 +71,7 @@ export const userTools = Object.freeze([
71
71
  defineTool({
72
72
  name: "n8n_users_get",
73
73
  title: "Get user",
74
- description: "Get one user by stable ID or exact email address. Use it for targeted verification before an administrative action; use n8n_users_list for discovery. Returns validated identity, role, and account state without changing the user.",
74
+ description: "Get one user by stable ID or exact email. Use it for targeted verification before an administrative action; use n8n_users_list for discovery. userIdOrEmail chooses ID lookup or an exact percent-encoded email lookup; includeRole=true requests but cannot guarantee role visibility. Requires user-read permission; returns redaction-protected identity and account state without mutation.",
75
75
  operation: "read-only",
76
76
  outputDataDescription: "One validated user record with id and optional email, firstName, lastName, role, disabled/pending status, and timestamps. Recognized personal values may be redacted.",
77
77
  input: {
@@ -89,9 +89,10 @@ export const userTools = Object.freeze([
89
89
  defineTool({
90
90
  name: "n8n_users_create",
91
91
  title: "Invite user",
92
- description: "Invite one non-owner user by email with a supported global role. Use n8n_users_get first when the address may already exist; this tool is not an update operation. Unsafe mode and exact email confirmation are required; returns delivery state without an acceptance URL.",
92
+ description: "Create a pending non-owner invitation as an additive write that may send email. Use n8n_users_get first when the address may exist; this is not an update or project-membership tool. role defaults to global:member, and confirmation must bind INVITE to the exact email. Requires unsafe mode plus invite permission. An inconclusive response may still mean a pending user exists; returns delivery state but never the acceptance URL.",
93
93
  operation: "unsafe",
94
94
  outputDataDescription: "Confirmed invitation outcome with userCreated, invited, userId, email, requestedRole, roleConfirmedByResponse, emailSent, delivery, and inviteAcceptUrlReturned=false. Acceptance URLs are never returned.",
95
+ destructive: false,
95
96
  input: {
96
97
  email,
97
98
  role: z
@@ -135,7 +136,7 @@ export const userTools = Object.freeze([
135
136
  defineTool({
136
137
  name: "n8n_users_delete",
137
138
  title: "Delete user",
138
- description: "Delete one API-eligible user; ownership handling follows n8n's Public API. Use it only after n8n_users_get verifies the target and consequences; read tools should be used for inspection. Unsafe mode and exact confirmation are required; returns deleted=true.",
139
+ description: "Permanently delete one API-eligible user. Use n8n_users_get to verify the target first; use read tools for inspection. userId accepts no transfer target, so ownership handling remains entirely with n8n, and confirmation must bind DELETE to that ID. Requires unsafe mode plus user-delete permission and exact confirmation; returns the request-bound ID with deleted=true and provides no rollback claim.",
139
140
  operation: "unsafe",
140
141
  outputDataDescription: "Object with the validated input userId and deleted=true. The tool makes no ownership-transfer claim and accepts n8n's successful empty response.",
141
142
  input: { userId: identifier("Stable ID of the API-eligible user to delete."), confirmation },
@@ -65,7 +65,7 @@ export const utilityTools = Object.freeze([
65
65
  defineTool({
66
66
  name: "n8n_health",
67
67
  title: "Check n8n health",
68
- description: "Perform a bounded same-origin health check against the configured n8n instance. Use it for reachability only; use an authenticated read tool such as n8n_workflows_list to validate API-key access. Returns ok=true and the HTTP status, not API capability.",
68
+ description: "Perform one redirect-free same-origin /healthz request with a 10-second timeout. Use it for HTTP reachability only; use an authenticated read tool such as n8n_workflows_list to validate API-key capability. The shared client still requires configured URL/key values, but no workflow data is read or changed. Returns ok=true and the successful status, never the upstream body.",
69
69
  operation: "read-only",
70
70
  outputDataDescription: "Object with ok=true and the successful n8n health endpoint HTTP status. It proves bounded reachability, not authenticated Public API capability.",
71
71
  input: {},
@@ -79,7 +79,7 @@ export const utilityTools = Object.freeze([
79
79
  defineTool({
80
80
  name: "n8n_insights_summary",
81
81
  title: "Get insights summary",
82
- description: "Get n8n's official aggregate insights summary for an optional ISO 8601 range. Use it for instance aggregates when the Community endpoint exists; use n8n_introspect for one workflow's diagnostics. Returns totals, failures, failure rate, time saved, and runtime aggregates.",
82
+ description: "Get n8n's official instance insights aggregates. Use it when the Community endpoint exists; use n8n_introspect for one workflow's diagnostics. startDate and endDate are inclusive offset-aware bounds, and supplying both requires startDate <= endDate; omitting both requests n8n's default range. Requires insights-read permission and never fabricates unavailable metrics; returns totals, failures, rates, time saved, and runtime aggregates.",
83
83
  operation: "read-only",
84
84
  outputDataDescription: "Validated aggregate object with total, failed, failureRate, timeSaved, and averageRunTime records; additional upstream aggregate fields may be present and are sanitized.",
85
85
  input: {
@@ -112,10 +112,11 @@ export const utilityTools = Object.freeze([
112
112
  defineTool({
113
113
  name: "n8n_audit_generate",
114
114
  title: "Generate security audit",
115
- description: "Generate n8n's instance-level security audit. Use it for an explicitly approved instance audit; use n8n_introspect for deterministic analysis of one workflow without this side effect. Unsafe mode and exact confirmation are required; returns an untrusted report.",
115
+ description: "Run n8n's broad, non-destructive instance security scan. Use it for an owner-approved audit; use n8n_introspect for local analysis of one workflow. Omitting categories lets n8n choose its complete default, while daysAbandonedWorkflow changes only the inactive-workflow threshold. Requires unsafe mode, owner-authorized API access, and exact GENERATE AUDIT confirmation; returns a sanitized but untrusted report without changing instance configuration.",
116
116
  operation: "unsafe",
117
117
  preserveValidatedRootRecordValues: true,
118
118
  outputDataDescription: "Map of upstream report titles to validated reports. Each report has one official risk category and bounded sections with recommendations plus typed locations or instance details; all content is sanitized and untrusted.",
119
+ destructive: false,
119
120
  input: {
120
121
  categories: z
121
122
  .array(z.enum(["credentials", "database", "nodes", "filesystem", "instance"]))
@@ -153,7 +154,7 @@ export const utilityTools = Object.freeze([
153
154
  defineTool({
154
155
  name: "n8n_search_workflows",
155
156
  title: "Search workflows",
156
- description: "Search one bounded workflow page locally by name, node type, or tag name. Use it for substring matching; use n8n_workflows_list for an unsearched API page. Continue nextCursor because this is not an instance-wide index; returns value-free matches and coverage state.",
157
+ description: "Search one workflow page locally with a case-insensitive substring. Use it for name, node-type, or tag-name matching; use n8n_workflows_list for an unsearched API page. searchIn selects local fields after active filters upstream; cursor and limit select one page, not a global index, so continue nextCursor for coverage. Requires workflow-list permission; returns at most 50 value-free matches and explicit scan state.",
157
158
  operation: "read-only",
158
159
  outputDataDescription: "Object with query, workflowsExamined, matches (up to 50 identity/state records with matchedIn scopes), nextCursor, scanComplete, and truncated. It covers one requested workflow page, not a global index.",
159
160
  input: {
@@ -219,7 +220,7 @@ export const utilityTools = Object.freeze([
219
220
  defineTool({
220
221
  name: "n8n_get_node_docs",
221
222
  title: "Get node documentation",
222
- description: "Return a bounded bundled reference for one of four allowlisted core n8n nodes. Use it for offline orientation; use n8n_list_node_types to inventory observed types, and official docs for other nodes. Performs no fetch and returns a title, summary, guidance, official URL, and provenance.",
223
+ description: "Return one immutable bundled reference selected by the four-value node key. Use it for offline orientation; use n8n_list_node_types to inventory observed types and official docs for any other node. Requires no n8n URL, API key, network, or elevated mode; it never follows the included URL. Returns source/fetched provenance, canonical type, title, summary, guidance, and official URL.",
223
224
  operation: "read-only",
224
225
  outputDataDescription: "Bundled reference with source=bundled_offline_reference, fetched=false, node type, title, concise summary/guidance, and official documentation URL. No network fetch occurs.",
225
226
  openWorld: false,
@@ -237,7 +238,7 @@ export const utilityTools = Object.freeze([
237
238
  defineTool({
238
239
  name: "n8n_list_node_types",
239
240
  title: "List observed node types",
240
- description: "List node types observed across bounded workflow pages. Use it for usage inventory, not installed-package availability; use n8n_get_node_docs for bundled references or n8n_community_packages_list for package metadata. Returns counts and scan coverage.",
241
+ description: "Inventory node-type strings observed across bounded workflow pages. Use it for usage evidence, not installed availability; use n8n_get_node_docs for bundled references or n8n_community_packages_list for package metadata. cursor selects the start, maxPages bounds continuation, and active filters upstream; a complete result requires starting at the first page and reaching the end. Requires workflow-list permission; returns counts and exact coverage under 30-second/20,000-node budgets.",
241
242
  operation: "read-only",
242
243
  outputDataDescription: "Observed-workflow inventory with scope/availabilityStatement, up to 500 sorted types and counts, page/workflow/node counters, coverage booleans, nextCursor, resultComplete, truncated, and omittedTypeCount.",
243
244
  input: {
@@ -339,7 +340,7 @@ export const utilityTools = Object.freeze([
339
340
  defineTool({
340
341
  name: "n8n_introspect",
341
342
  title: "Inspect n8n workflow",
342
- description: "Inspect one workflow and bounded saved executions with deterministic local rules. Use quick for triage and deep for limited redacted details; use n8n_audit_generate for instance security or n8n_workflows_get for raw structure. Never executes; returns findings and coverage.",
343
+ description: "Inspect one workflow and saved executions with 23 deterministic local rules. profile sets paired defaults: quick uses 24h/20 and caps maxExecutions at 25; deep uses 168h/50 and reads at most four redacted details. includeSanitizedLabels=false keeps labels opaque; true opts into bounded sanitized labels. Use n8n_audit_generate for instance security or n8n_workflows_get for raw structure. Requires workflow/execution read permission; never executes or calls AI; returns findings and coverage.",
343
344
  operation: "read-only",
344
345
  outputDataDescription: "Direct Introspect result containing schema/engine versions, status, workflow facts, sample coverage, finding/rule counts, metrics, bounded findings, rule coverage, limitations, and guidance. Every top-level field has its own published schema description.",
345
346
  outputSchema: IntrospectResultSchema,
@@ -403,7 +404,7 @@ export const utilityTools = Object.freeze([
403
404
  defineTool({
404
405
  name: "n8n_community_packages_list",
405
406
  title: "List community packages",
406
- description: "List bounded metadata for installed n8n community packages. Use it for package inventory; use n8n_list_node_types for types observed in workflows. It never installs, updates, or removes packages and returns at most 100 untrusted records plus truncation counts.",
407
+ description: "List bounded metadata for installed n8n community packages. Use it for package inventory; use n8n_list_node_types for types actually observed in workflows. This zero-input call retains at most 100 of the endpoint's records and reports exact total/omitted counts; it does not download or inspect package contents. Requires package-list permission, never installs, updates, or removes packages, and returns untrusted metadata with author emails redacted.",
407
408
  operation: "read-only",
408
409
  outputDataDescription: "Object with data (at most 100 package metadata records), totalCount, truncated, and exact omittedCount. Records may include packageName, installedVersion, author metadata, and timestamps; author emails are redacted.",
409
410
  input: {},
@@ -651,7 +651,7 @@ export const workflowTools = Object.freeze([
651
651
  defineTool({
652
652
  name: "n8n_workflows_list",
653
653
  title: "List workflows",
654
- description: "List one page of workflows visible to the configured API key. Use it for unsearched discovery; use n8n_search_workflows for local substring matching or n8n_workflows_get when the ID is known. Returns projected workflows and a cursor, never pin/static values.",
654
+ description: "List one Public API page of workflows visible to the configured API key. Use it for unsearched discovery; use n8n_search_workflows for local substring matching or n8n_workflows_get when the ID is known. Filters are applied upstream, cursor resumes a prior page, and this call never auto-paginates. Requires workflow-list permission; returns projected workflows and nextCursor, never pin/static values.",
655
655
  operation: "read-only",
656
656
  outputDataDescription: "Object with data (up to 100 projected workflows) and nextCursor (string or null). Each workflow includes structure/state plus pinDataPresent and staticDataPresent presence markers, but never pinned or static values.",
657
657
  input: {
@@ -698,7 +698,7 @@ export const workflowTools = Object.freeze([
698
698
  defineTool({
699
699
  name: "n8n_workflows_get",
700
700
  title: "Get workflow",
701
- description: "Get one current workflow by stable ID for inspection or reviewed editing. Use n8n_workflows_get_version for retained history or n8n_workflows_list for discovery. Returns structure, state, settings, and presence flags; pin/static values are always withheld.",
701
+ description: "Get one current workflow by stable ID for inspection or reviewed editing. Use n8n_workflows_get_version for retained history or n8n_workflows_list for discovery. excludePinnedData controls only the upstream request and presence reporting; values are always withheld. Requires permission to read the target; returns structure, state, settings, and presence flags.",
702
702
  operation: "read-only",
703
703
  outputDataDescription: "Current workflow projection with id, optional versionId/description/state, name, nodes, connections, settings, and pinDataPresent/staticDataPresent markers. Pinned and static values are never returned.",
704
704
  input: {
@@ -716,9 +716,10 @@ export const workflowTools = Object.freeze([
716
716
  defineTool({
717
717
  name: "n8n_workflows_create",
718
718
  title: "Create workflow",
719
- description: "Create one workflow from a complete validated definition. Use it for a new workflow; use n8n_workflows_update for an existing workflow and n8n_workflows_activate separately for triggers. Validates the graph and returns a projection without pin/static values.",
719
+ description: "Create and persist one workflow as an additive write without activating its triggers. Use it for a new workflow; use n8n_workflows_update for an existing one and n8n_workflows_activate separately for triggers. nodes and connections must describe one complete consistent graph; optional pin/static inputs are accepted but never returned. Requires write or unsafe mode plus create permission; returns the validated projection.",
720
720
  operation: "write",
721
721
  outputDataDescription: "Created workflow projection with id, optional versionId/description/state, name, nodes, connections, settings, and withheld pin/static presence markers. Pinned and static values are never returned.",
722
+ destructive: false,
722
723
  input: {
723
724
  name: z.string().min(1).max(128).describe("Workflow name (1-128 characters)."),
724
725
  description: z
@@ -763,7 +764,7 @@ export const workflowTools = Object.freeze([
763
764
  defineTool({
764
765
  name: "n8n_workflows_update",
765
766
  title: "Update workflow",
766
- description: "Update selected top-level workflow fields while preserving omitted writable fields. Use n8n_update_node for one node property and n8n_workflows_update_tags for tag assignments. Requires the current versionId, performs a non-atomic full PUT, and returns the confirmed projection.",
767
+ description: "Update selected top-level workflow fields through a non-atomic full PUT. Use n8n_update_node for one node property and n8n_workflows_update_tags for tag assignments. expectedVersionId must match both pre-write reads; omitted writable fields are preserved, while supplied nodes or other composite fields replace that field completely. Requires write/unsafe mode plus read and update permission; returns the confirmed projection.",
767
768
  operation: "write",
768
769
  outputDataDescription: "Confirmed updated workflow projection with id, version/state, name, optional description, nodes, connections, settings, and withheld pin/static presence markers. Omitted writable fields come from the immediate pre-write read.",
769
770
  destructive: true,
@@ -837,7 +838,7 @@ export const workflowTools = Object.freeze([
837
838
  defineTool({
838
839
  name: "n8n_update_node",
839
840
  title: "Update one workflow node",
840
- description: "Update one validated mutable property on one workflow node while preserving the rest. Use it instead of n8n_workflows_update for a single-node change; use the full update for top-level or multi-node edits. Requires version/risk guards and returns the path plus non-atomic risk.",
841
+ description: "Update one validated path on one workflow node while preserving sibling data. Use it instead of n8n_workflows_update for a single-node change; use the full update for top-level or multi-node edits. path selects the mutable root whose contract validates value, and both version/risk guards must match before the non-atomic PUT. Requires write/unsafe mode plus read and update permission; returns the changed path and residual race risk.",
841
842
  operation: "write",
842
843
  outputDataDescription: "Object with workflowId, versionId, nodeId, path, updated=true, atomic=false, and residualRisk describing the Public API's non-atomic full-workflow PUT limitation.",
843
844
  destructive: true,
@@ -915,7 +916,7 @@ export const workflowTools = Object.freeze([
915
916
  defineTool({
916
917
  name: "n8n_workflows_delete",
917
918
  title: "Delete workflow",
918
- description: "Permanently delete one workflow and its saved definition. Use n8n_workflows_archive when reversible removal is sufficient and n8n_workflows_get for inspection. Unsafe mode and exact target confirmation are required; returns the ID with deleted=true.",
919
+ description: "Permanently delete one workflow and its saved definition. Use n8n_workflows_archive when reversible removal is sufficient and n8n_workflows_get for inspection. confirmation must bind DELETE to the same workflowId; no rollback or transfer is provided. Requires unsafe mode plus delete permission and exact confirmation; returns the request-bound ID with deleted=true.",
919
920
  operation: "unsafe",
920
921
  outputDataDescription: "Object with the validated input workflowId and deleted=true. Identity is bound to the request and does not rely on an upstream response body.",
921
922
  input: { workflowId: identifier("Stable ID of the workflow to delete."), confirmation },
@@ -934,8 +935,8 @@ export const workflowTools = Object.freeze([
934
935
  name: `n8n_workflows_${action}`,
935
936
  title: `${action === "activate" ? "Activate" : "Deactivate"} workflow`,
936
937
  description: action === "activate"
937
- ? "Activate one workflow so production triggers can accept future events. Use it only after n8n_workflows_get review; use n8n_workflows_deactivate to stop future triggers. Unsafe mode and exact confirmation are required; returns allowlisted state metadata."
938
- : "Deactivate one workflow's production triggers without deleting saved data or stopping current work. Use n8n_workflows_activate to reverse this state or n8n_workflows_archive for lifecycle removal. Unsafe mode and exact confirmation are required; returns state metadata.",
938
+ ? "Activate one workflow so production triggers can accept future events; this does not execute it immediately. Use it only after n8n_workflows_get review; use n8n_workflows_deactivate to stop future triggers. confirmation must bind ACTIVATE to workflowId. Requires unsafe mode plus activation permission and exact confirmation; returns target-validated active state metadata."
939
+ : "Deactivate one workflow's future production triggers without deleting saved data or stopping executions already running. Use n8n_workflows_activate to reverse this state or n8n_workflows_archive for lifecycle removal. confirmation must bind DEACTIVATE to workflowId. Requires unsafe mode plus deactivation permission and exact confirmation; returns target-validated inactive state metadata.",
939
940
  operation: "unsafe",
940
941
  outputDataDescription: action === "activate"
941
942
  ? "Target-bound workflow metadata with required id and active=true; optional name, type, archive state, versionId, and timestamps are included when supplied by n8n, while all other fields are omitted."
@@ -956,7 +957,7 @@ export const workflowTools = Object.freeze([
956
957
  defineTool({
957
958
  name: "n8n_workflows_get_version",
958
959
  title: "Get workflow version",
959
- description: "Get one retained historical workflow snapshot. Use it when exact historical nodes/connections are needed; use n8n_workflows_get for current state or n8n_workflows_diff for value-free comparison. Returns validated identity and structure, subject to Community retention.",
960
+ description: "Get one retained historical workflow snapshot selected by workflowId and versionId. Use it when exact historical nodes/connections are needed; use n8n_workflows_get for current state or n8n_workflows_diff for value-free comparison. Both returned IDs must match the selectors; a missing endpoint and a pruned version share an explicit ambiguous 404 outcome. Requires history-read permission; returns validated structure subject to retention.",
960
961
  operation: "read-only",
961
962
  outputDataDescription: "Historical snapshot with workflowId, versionId, optional name, nodes, and connections. Historical settings, pin data, and static data are unavailable from the supported Public API.",
962
963
  input: {
@@ -972,7 +973,7 @@ export const workflowTools = Object.freeze([
972
973
  defineTool({
973
974
  name: "n8n_workflows_get_tags",
974
975
  title: "Get workflow tags",
975
- description: "List the tags currently assigned to one workflow. Use it before n8n_workflows_update_tags when assignments must be preserved; use n8n_tags_list to discover all available tags. Returns a bounded validated collection with exact truncation counts.",
976
+ description: "List the tags currently assigned to one workflow. Use it before n8n_workflows_update_tags when assignments must be preserved; use n8n_tags_list to discover all available tags. This endpoint has no cursor: the tool retains at most 100 records and reports totalCount plus exact omissions. Requires permission to read the workflow's assignments; returns validated tag metadata.",
976
977
  operation: "read-only",
977
978
  outputDataDescription: "Object with data (at most 100 validated tag records), totalCount, truncated, and exact omittedCount. Each tag may include id, name, createdAt, and updatedAt.",
978
979
  input: {
@@ -985,7 +986,7 @@ export const workflowTools = Object.freeze([
985
986
  defineTool({
986
987
  name: "n8n_workflows_update_tags",
987
988
  title: "Update workflow tags",
988
- description: "Replace the complete tag assignment for one workflow; this is not a merge. Use n8n_workflows_get_tags first to preserve existing assignments and n8n_tags_create for a missing tag; an empty array clears all tags. Returns n8n's validated assignment.",
989
+ description: "Replace the complete tag assignment for one workflow; this is not a merge. Use n8n_workflows_get_tags first to preserve IDs and n8n_tags_create for a missing tag. tagIds is the entire desired set, so omitted IDs are removed and an empty array clears all tags. Requires write/unsafe mode plus assignment permission; returns n8n's validated replacement.",
989
990
  operation: "write",
990
991
  outputDataDescription: "Array of at most 100 validated tag records returned by n8n after replacing the workflow's complete assignment. An empty returned array represents no assigned tags.",
991
992
  destructive: true,
@@ -1009,8 +1010,8 @@ export const workflowTools = Object.freeze([
1009
1010
  name: `n8n_workflows_${action}`,
1010
1011
  title: `${action === "archive" ? "Archive" : "Unarchive"} workflow`,
1011
1012
  description: action === "archive"
1012
- ? "Archive one workflow without deleting it. Use it instead of n8n_workflows_delete when reversible lifecycle removal is required; use n8n_workflows_deactivate only to stop triggers. Unsafe mode and exact confirmation are required; returns archive state."
1013
- : "Restore one archived workflow without activating it. Use it to reverse n8n_workflows_archive; call n8n_workflows_activate separately only if triggers should resume. Unsafe mode and exact confirmation are required; returns archive state.",
1013
+ ? "Archive one workflow without deleting its saved definition. Use it instead of n8n_workflows_delete for reversible lifecycle removal; use n8n_workflows_deactivate only to stop triggers. confirmation must bind ARCHIVE to workflowId, and the availability change can disrupt callers. Requires unsafe mode plus archive permission and exact confirmation; returns target-validated archive state."
1014
+ : "Restore one archived workflow without activating its triggers. Use it to reverse n8n_workflows_archive; call n8n_workflows_activate separately only if triggers should resume. confirmation must bind UNARCHIVE to workflowId; current active state is not inferred. Requires unsafe mode plus unarchive permission and exact confirmation; returns target-validated archive state.",
1014
1015
  operation: "unsafe",
1015
1016
  outputDataDescription: action === "archive"
1016
1017
  ? "Target-bound workflow metadata with required id and isArchived=true; optional name, type, active state, versionId, and timestamps are included when supplied by n8n, while all other fields are omitted."
@@ -1031,7 +1032,7 @@ export const workflowTools = Object.freeze([
1031
1032
  defineTool({
1032
1033
  name: "n8n_workflows_diff",
1033
1034
  title: "Compare workflow versions",
1034
- description: "Compare one retained workflow version with another or current state. Use it for value-free review; use n8n_workflows_get_version when raw historical structure is needed. Returns counts, coverage, and up to 200 changes; unavailable historical fields are explicit.",
1035
+ description: "Compare one retained workflow version with another or current state without returning changed values. Use it for review; use n8n_workflows_get_version when raw historical structure is needed. Omitting toVersionId selects current, while ignoreLayout=true suppresses position-only changes; selectors must differ. Requires workflow/history read permission; returns coverage, counts, and a byte-bounded prefix of up to 200 changes.",
1035
1036
  operation: "read-only",
1036
1037
  outputDataDescription: "Value-free comparison with workflowId, from/to selectors, comparisonCoverage, summary counts, byte-budgeted changes (up to 200), truncated, and exact omittedDetails. Modified nodes name changed fields; parameterChanges expose only sanitized paths, presence, value types, changed=true, and exact truncation metadata, while credential changes expose only referenceChanged=true. Raw values and webhook identifiers are never returned.",
1037
1038
  input: {
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const PACKAGE_VERSION = "0.1.3";
1
+ export const PACKAGE_VERSION = "0.1.4";
Binary file
@@ -17,11 +17,11 @@ release evidence from the client-matrix gates that remain open.
17
17
 
18
18
  The package declares Node.js `>=22.0.0`. That engine range is an acceptance
19
19
  floor, not evidence that every later major has completed the release matrix.
20
- The supported release lines for v0.1.3 are Node.js 22 and 24.
20
+ The supported release lines for v0.1.4 are Node.js 22 and 24.
21
21
 
22
22
  ## n8n Community Edition
23
23
 
24
- The v0.1.3 floor candidate is n8n Community Edition 2.30.5, with 2.30.7 as the
24
+ The v0.1.4 floor candidate is n8n Community Edition 2.30.5, with 2.30.7 as the
25
25
  current comparison target. The compiled public 44-tool candidate passed fresh,
26
26
  disposable lifecycles on both versions with synthetic fixtures, API-key
27
27
  revocation, resource cleanup, enforced candidate-process egress isolation, and
@@ -33,7 +33,7 @@ browser cookies, interactive `/rest` routes, paid project transfers, arbitrary
33
33
  workflow execution, or runtime node-catalog downloads.
34
34
 
35
35
  `n8n_credentials_list` is supported only from n8n 2.30.5 onward. Project
36
- selectors are intentionally absent because v0.1.3 does not present paid project
36
+ selectors are intentionally absent because v0.1.4 does not present paid project
37
37
  capabilities as Community features. Other tools may also depend on endpoint
38
38
  availability and the permissions assigned to the API key; an HTTP 403 does not
39
39
  by itself mean the endpoint is absent.
@@ -72,7 +72,7 @@ surface.
72
72
 
73
73
  ## Deliberate exclusions
74
74
 
75
- v0.1.3 does not include:
75
+ v0.1.4 does not include:
76
76
 
77
77
  - folders or data tables;
78
78
  - beta evaluation endpoints;
@@ -6,8 +6,8 @@ standards-based MCP client interaction into a short terminal-style sequence; it
6
6
  does not claim that the client narration is a project CLI command.
7
7
 
8
8
  ```text
9
- $ npx --yes n8n-mcp-community@0.1.3 --version
10
- 0.1.3
9
+ $ npx --yes n8n-mcp-community@0.1.4 --version
10
+ 0.1.4
11
11
 
12
12
  MCP client initialized n8n-community
13
13
  44 tools | 5 resources | 4 prompts
@@ -20,7 +20,7 @@ schema 1.0.0 | engine 2.0.0 | status: complete
20
20
  findings: 0 critical | 0 high | 0 medium | 0 low | 0 info
21
21
  ```
22
22
 
23
- The version command depicts the v0.1.3 source candidate and becomes an
23
+ The version command depicts the v0.1.4 source candidate and becomes an
24
24
  installation route only after external npm, GitHub, and Registry readback agree.
25
25
  The initialization and tool call lines represent MCP protocol
26
26
  events, not shell commands. The Introspect output is the documented empty
@@ -1,6 +1,6 @@
1
1
  # Security model
2
2
 
3
- This document describes the controls and residual risks of the v0.1.3 source
3
+ This document describes the controls and residual risks of the v0.1.4 source
4
4
  candidate. It is a threat model, not a claim that the software or the connected
5
5
  n8n instance is invulnerable.
6
6
 
@@ -45,8 +45,9 @@ The runtime has three local modes:
45
45
 
46
46
  - `read-only` allows only tools classified as read-only;
47
47
  - `write` also allows mutation tools that do not use the separate unsafe
48
- confirmation gate; all mutation tools still advertise the conservative MCP
49
- destructive hint;
48
+ confirmation gate. Non-read tools use the conservative MCP destructive hint
49
+ by default; reviewed additive or non-destructive operations publish an
50
+ explicit false override;
50
51
  - `unsafe` allows all tools, but each unsafe call requires an exact,
51
52
  input-bound confirmation phrase.
52
53
 
package/docs/tools.md CHANGED
@@ -83,9 +83,9 @@ reviewed edit.
83
83
  Creates a workflow from a complete validated workflow definition.
84
84
 
85
85
  - **Policy and endpoint:** write; `POST /workflows`;
86
- `RO=false, D=true, I=false, OW=true`. Requires mode `write` or `unsafe`.
86
+ `RO=false, D=false, I=false, OW=true`. Requires mode `write` or `unsafe`.
87
87
  - **Requirements:** Requires mode `write` or `unsafe` and API-key permission to create workflows.
88
- - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. Creation targets the caller's Community workspace and does not accept paid-project assignment.
88
+ - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. Creation is an additive write, targets the caller's Community workspace, does not activate triggers, and does not accept paid-project assignment.
89
89
  - **Inputs:** required `name` (1–128), `nodes` (1–1,000), and `connections`
90
90
  object; optional `description` (up to 16,384), `settings` object (default
91
91
  `{}`), `nodeGroups` (up to 1,000 safe JSON values), `staticData` (object,
@@ -463,9 +463,9 @@ Creates a credential while preventing credential values from entering MCP
463
463
  output or security logs.
464
464
 
465
465
  - **Policy and endpoint:** write; `POST /credentials`;
466
- `RO=false, D=true, I=false, OW=true`. Requires mode `write` or `unsafe`.
466
+ `RO=false, D=false, I=false, OW=true`. Requires mode `write` or `unsafe`.
467
467
  - **Requirements:** Requires mode `write` or `unsafe` and API-key permission to create the requested credential type.
468
- - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. Paid-project placement is intentionally absent, and credential values are output-prohibited.
468
+ - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. Creation is additive; paid-project placement is intentionally absent, and credential values are output-prohibited.
469
469
  - **Inputs:** required `name` (1–128), `type` (1–128 ASCII letters, digits,
470
470
  `_`, `.`, or `-`), and `data` (safe JSON object); optional
471
471
  `isResolvable?: boolean`.
@@ -581,15 +581,14 @@ Tests one stored credential through n8n.
581
581
  - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. Success depends on the credential type and its external service; no credential value is returned.
582
582
  - **Inputs:** required `credentialId`; `confirmation` must equal
583
583
  `TEST <credentialId>`.
584
- - **Returns:** the input ID plus an allowlisted status and optional bounded
585
- message. Over-long upstream status or message text is truncated to the
586
- bounded caps (64 and 512 characters) with `truncated: true` rather than
587
- rejected, so the completed test outcome is always preserved.
584
+ - **Returns:** the input ID, the allowlisted `OK` or `Error` status, and a
585
+ server-authored success/failure message. The upstream diagnostic message is
586
+ withheld because it is untrusted and may contain credential or service data.
588
587
  - **Failures and privacy:** this call may contact the credential's external
589
588
  service and cause observable authentication traffic. Denied mode or
590
- confirmation issues zero requests; returned messages are sanitized.
589
+ confirmation issues zero requests; no upstream diagnostic text is returned.
591
590
  - **Example:** `{ "credentialId": "cred_1", "confirmation": "TEST cred_1" }` →
592
- `{ "data": { "credentialId": "cred_1", "status": "OK", "message": "Connection succeeded" }, "redacted": false, "untrusted": true }`.
591
+ `{ "data": { "credentialId": "cred_1", "status": "OK", "message": "Credential test succeeded." }, "redacted": false, "untrusted": true }`.
593
592
 
594
593
  ## n8n_credentials_usage
595
594
 
@@ -651,9 +650,9 @@ Reads one workflow tag by ID.
651
650
  Creates a workflow tag.
652
651
 
653
652
  - **Policy and endpoint:** write; `POST /tags`;
654
- `RO=false, D=true, I=false, OW=true`. Requires mode `write` or `unsafe`.
653
+ `RO=false, D=false, I=false, OW=true`. Requires mode `write` or `unsafe`.
655
654
  - **Requirements:** Requires mode `write` or `unsafe` and API-key permission to create workflow tags.
656
- - **Community Edition:** Verified on Community 2.30.5 and 2.30.7 with the live Community 1–24 character name bound.
655
+ - **Community Edition:** Verified on Community 2.30.5 and 2.30.7 with the live Community 1–24 character name bound. Creation is additive and never assigns the new tag to a workflow.
657
656
  - **Inputs:** required `name`, 1–24 characters, trimmed, with no control
658
657
  characters. The 24-character limit reflects live n8n behavior that is stricter
659
658
  than the historical OpenAPI schema.
@@ -738,9 +737,9 @@ Reads one user by stable ID or exact email address.
738
737
  Invites one non-owner user.
739
738
 
740
739
  - **Policy and endpoint:** unsafe; `POST /users`;
741
- `RO=false, D=true, I=false, OW=true`.
740
+ `RO=false, D=false, I=false, OW=true`.
742
741
  - **Requirements:** Requires mode `unsafe`, exact email-bound confirmation, and API-key permission to invite users with the selected global role.
743
- - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. It invites a global member/admin only and does not manage paid project membership.
742
+ - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. The invitation is additive but may send email; it invites a global member/admin only and does not manage paid project membership.
744
743
  - **Inputs:** required `email` (valid email up to 254);
745
744
  `role?: "global:member" | "global:admin"` defaults to `global:member`;
746
745
  `confirmation` must equal `INVITE <email>`.
@@ -817,15 +816,15 @@ Reads n8n's official insights summary, optionally constrained by date.
817
816
 
818
817
  ## n8n_audit_generate
819
818
 
820
- Requests n8n's instance security audit. Although the endpoint is intended to
821
- generate a report, it uses POST and can perform broad instance inspection, so
822
- this server applies the conservative unsafe policy.
819
+ Requests n8n's instance security audit. The endpoint performs broad instance
820
+ inspection and uses POST, so this server applies the conservative unsafe
821
+ operation policy even though the scan is non-destructive.
823
822
 
824
823
  - **Policy and endpoint:** unsafe; `POST /audit`;
825
- `RO=false, D=true, I=false, OW=true`. Requires mode `unsafe` and exact
824
+ `RO=false, D=false, I=false, OW=true`. Requires mode `unsafe` and exact
826
825
  confirmation `GENERATE AUDIT`.
827
826
  - **Requirements:** Requires mode `unsafe`, exact `GENERATE AUDIT` confirmation, and API-key permission to generate the instance audit.
828
- - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. The POST report endpoint is conservatively marked destructive; category support still depends on n8n.
827
+ - **Community Edition:** Verified on Community 2.30.5 and 2.30.7. The POST report endpoint remains unsafe-gated but is annotated non-destructive because it does not change instance resources or configuration; category support still depends on n8n.
829
828
  - **Inputs:** optional `categories`, an array of at most five selections
830
829
  from `credentials`, `database`, `nodes`, `filesystem`, and `instance`;
831
830
  optional `daysAbandonedWorkflow` integer from 1 through 3,650; required
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-mcp-community",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A security-focused MCP server for self-hosted n8n Community Edition.",
5
5
  "private": false,
6
6
  "releaseState": "candidate",
@@ -122,12 +122,12 @@
122
122
  "@types/node": "22.20.1",
123
123
  "ajv": "8.20.0",
124
124
  "ajv-formats": "3.0.1",
125
- "eslint": "10.7.0",
125
+ "eslint": "10.8.0",
126
126
  "fflate": "0.8.3",
127
- "globals": "17.7.0",
128
- "prettier": "3.9.5",
127
+ "globals": "17.8.0",
128
+ "prettier": "3.9.6",
129
129
  "typescript": "5.9.3",
130
- "typescript-eslint": "8.64.0",
130
+ "typescript-eslint": "8.65.0",
131
131
  "yaml": "2.9.0"
132
132
  }
133
133
  }
package/server.json CHANGED
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/drzamarian/n8n-mcp-community",
9
9
  "source": "github"
10
10
  },
11
- "version": "0.1.3",
11
+ "version": "0.1.4",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "n8n-mcp-community",
17
- "version": "0.1.3",
17
+ "version": "0.1.4",
18
18
  "transport": {
19
19
  "type": "stdio"
20
20
  },