n8n-mcp 2.7.21 → 2.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -2
- package/data/nodes.db +0 -0
- package/dist/database/node-repository.d.ts +12 -1
- package/dist/database/node-repository.d.ts.map +1 -1
- package/dist/database/node-repository.js +122 -2
- package/dist/database/node-repository.js.map +1 -1
- package/dist/http-server-single-session.d.ts +21 -1
- package/dist/http-server-single-session.d.ts.map +1 -1
- package/dist/http-server-single-session.js +515 -44
- package/dist/http-server-single-session.js.map +1 -1
- package/dist/http-server.d.ts.map +1 -1
- package/dist/http-server.js +6 -3
- package/dist/http-server.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +413 -26
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools-n8n-friendly.d.ts +6 -0
- package/dist/mcp/tools-n8n-friendly.d.ts.map +1 -0
- package/dist/mcp/tools-n8n-friendly.js +131 -0
- package/dist/mcp/tools-n8n-friendly.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +187 -11
- package/dist/mcp/tools.js.map +1 -1
- package/dist/mcp/workflow-examples.d.ts +76 -0
- package/dist/mcp/workflow-examples.d.ts.map +1 -0
- package/dist/mcp/workflow-examples.js +111 -0
- package/dist/mcp/workflow-examples.js.map +1 -0
- package/dist/mcp-engine.d.ts +1 -1
- package/dist/mcp-engine.d.ts.map +1 -1
- package/dist/mcp-tools-engine.d.ts +48 -0
- package/dist/mcp-tools-engine.d.ts.map +1 -0
- package/dist/mcp-tools-engine.js +92 -0
- package/dist/mcp-tools-engine.js.map +1 -0
- package/dist/parsers/node-parser.d.ts.map +1 -1
- package/dist/parsers/node-parser.js +16 -10
- package/dist/parsers/node-parser.js.map +1 -1
- package/dist/parsers/simple-parser.d.ts.map +1 -1
- package/dist/parsers/simple-parser.js +11 -0
- package/dist/parsers/simple-parser.js.map +1 -1
- package/dist/scripts/test-protocol-negotiation.d.ts +3 -0
- package/dist/scripts/test-protocol-negotiation.d.ts.map +1 -0
- package/dist/scripts/test-protocol-negotiation.js +154 -0
- package/dist/scripts/test-protocol-negotiation.js.map +1 -0
- package/dist/services/config-validator.d.ts +6 -1
- package/dist/services/config-validator.d.ts.map +1 -1
- package/dist/services/config-validator.js +99 -25
- package/dist/services/config-validator.js.map +1 -1
- package/dist/services/enhanced-config-validator.d.ts +4 -0
- package/dist/services/enhanced-config-validator.d.ts.map +1 -1
- package/dist/services/enhanced-config-validator.js +90 -2
- package/dist/services/enhanced-config-validator.js.map +1 -1
- package/dist/services/expression-validator.d.ts.map +1 -1
- package/dist/services/expression-validator.js +36 -15
- package/dist/services/expression-validator.js.map +1 -1
- package/dist/services/n8n-validation.d.ts +5 -5
- package/dist/services/n8n-validation.d.ts.map +1 -1
- package/dist/services/n8n-validation.js +16 -12
- package/dist/services/n8n-validation.js.map +1 -1
- package/dist/services/property-filter.d.ts.map +1 -1
- package/dist/services/property-filter.js +35 -11
- package/dist/services/property-filter.js.map +1 -1
- package/dist/services/sqlite-storage-service.d.ts +11 -0
- package/dist/services/sqlite-storage-service.d.ts.map +1 -0
- package/dist/services/sqlite-storage-service.js +74 -0
- package/dist/services/sqlite-storage-service.js.map +1 -0
- package/dist/services/workflow-validator.d.ts +3 -1
- package/dist/services/workflow-validator.d.ts.map +1 -1
- package/dist/services/workflow-validator.js +296 -202
- package/dist/services/workflow-validator.js.map +1 -1
- package/dist/templates/template-repository.js +1 -1
- package/dist/templates/template-repository.js.map +1 -1
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/fixed-collection-validator.d.ts +35 -0
- package/dist/utils/fixed-collection-validator.d.ts.map +1 -0
- package/dist/utils/fixed-collection-validator.js +358 -0
- package/dist/utils/fixed-collection-validator.js.map +1 -0
- package/dist/utils/logger.d.ts +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +8 -4
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/protocol-version.d.ts +19 -0
- package/dist/utils/protocol-version.d.ts.map +1 -0
- package/dist/utils/protocol-version.js +95 -0
- package/dist/utils/protocol-version.js.map +1 -0
- package/dist/utils/template-sanitizer.d.ts.map +1 -1
- package/dist/utils/template-sanitizer.js +10 -1
- package/dist/utils/template-sanitizer.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp/server.js
CHANGED
|
@@ -44,6 +44,8 @@ const fs_1 = require("fs");
|
|
|
44
44
|
const path_1 = __importDefault(require("path"));
|
|
45
45
|
const tools_1 = require("./tools");
|
|
46
46
|
const tools_n8n_manager_1 = require("./tools-n8n-manager");
|
|
47
|
+
const tools_n8n_friendly_1 = require("./tools-n8n-friendly");
|
|
48
|
+
const workflow_examples_1 = require("./workflow-examples");
|
|
47
49
|
const logger_1 = require("../utils/logger");
|
|
48
50
|
const node_repository_1 = require("../database/node-repository");
|
|
49
51
|
const database_adapter_1 = require("../database/database-adapter");
|
|
@@ -60,22 +62,31 @@ const handlers_workflow_diff_1 = require("./handlers-workflow-diff");
|
|
|
60
62
|
const tools_documentation_1 = require("./tools-documentation");
|
|
61
63
|
const version_1 = require("../utils/version");
|
|
62
64
|
const node_utils_1 = require("../utils/node-utils");
|
|
65
|
+
const protocol_version_1 = require("../utils/protocol-version");
|
|
63
66
|
class N8NDocumentationMCPServer {
|
|
64
67
|
constructor() {
|
|
65
68
|
this.db = null;
|
|
66
69
|
this.repository = null;
|
|
67
70
|
this.templateService = null;
|
|
68
71
|
this.cache = new simple_cache_1.SimpleCache();
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
path_1.default.join(__dirname, '../../data', 'nodes.db'),
|
|
72
|
-
'./data/nodes.db'
|
|
73
|
-
];
|
|
72
|
+
this.clientInfo = null;
|
|
73
|
+
const envDbPath = process.env.NODE_DB_PATH;
|
|
74
74
|
let dbPath = null;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
let possiblePaths = [];
|
|
76
|
+
if (envDbPath && (envDbPath === ':memory:' || (0, fs_1.existsSync)(envDbPath))) {
|
|
77
|
+
dbPath = envDbPath;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
possiblePaths = [
|
|
81
|
+
path_1.default.join(process.cwd(), 'data', 'nodes.db'),
|
|
82
|
+
path_1.default.join(__dirname, '../../data', 'nodes.db'),
|
|
83
|
+
'./data/nodes.db'
|
|
84
|
+
];
|
|
85
|
+
for (const p of possiblePaths) {
|
|
86
|
+
if ((0, fs_1.existsSync)(p)) {
|
|
87
|
+
dbPath = p;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
92
|
if (!dbPath) {
|
|
@@ -102,6 +113,9 @@ class N8NDocumentationMCPServer {
|
|
|
102
113
|
async initializeDatabase(dbPath) {
|
|
103
114
|
try {
|
|
104
115
|
this.db = await (0, database_adapter_1.createDatabaseAdapter)(dbPath);
|
|
116
|
+
if (dbPath === ':memory:') {
|
|
117
|
+
await this.initializeInMemorySchema();
|
|
118
|
+
}
|
|
105
119
|
this.repository = new node_repository_1.NodeRepository(this.db);
|
|
106
120
|
this.templateService = new template_service_1.TemplateService(this.db);
|
|
107
121
|
logger_1.logger.info(`Initialized database from: ${dbPath}`);
|
|
@@ -111,6 +125,18 @@ class N8NDocumentationMCPServer {
|
|
|
111
125
|
throw new Error(`Failed to open database: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
112
126
|
}
|
|
113
127
|
}
|
|
128
|
+
async initializeInMemorySchema() {
|
|
129
|
+
if (!this.db)
|
|
130
|
+
return;
|
|
131
|
+
const schemaPath = path_1.default.join(__dirname, '../../src/database/schema.sql');
|
|
132
|
+
const schema = await fs_1.promises.readFile(schemaPath, 'utf-8');
|
|
133
|
+
const statements = schema.split(';').filter(stmt => stmt.trim());
|
|
134
|
+
for (const statement of statements) {
|
|
135
|
+
if (statement.trim()) {
|
|
136
|
+
this.db.exec(statement);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
114
140
|
async ensureInitialized() {
|
|
115
141
|
await this.initialized;
|
|
116
142
|
if (!this.db || !this.repository) {
|
|
@@ -118,9 +144,25 @@ class N8NDocumentationMCPServer {
|
|
|
118
144
|
}
|
|
119
145
|
}
|
|
120
146
|
setupHandlers() {
|
|
121
|
-
this.server.setRequestHandler(types_js_1.InitializeRequestSchema, async () => {
|
|
147
|
+
this.server.setRequestHandler(types_js_1.InitializeRequestSchema, async (request) => {
|
|
148
|
+
const clientVersion = request.params.protocolVersion;
|
|
149
|
+
const clientCapabilities = request.params.capabilities;
|
|
150
|
+
const clientInfo = request.params.clientInfo;
|
|
151
|
+
logger_1.logger.info('MCP Initialize request received', {
|
|
152
|
+
clientVersion,
|
|
153
|
+
clientCapabilities,
|
|
154
|
+
clientInfo
|
|
155
|
+
});
|
|
156
|
+
this.clientInfo = clientInfo;
|
|
157
|
+
const negotiationResult = (0, protocol_version_1.negotiateProtocolVersion)(clientVersion, clientInfo, undefined, undefined);
|
|
158
|
+
(0, protocol_version_1.logProtocolNegotiation)(negotiationResult, logger_1.logger, 'MCP_INITIALIZE');
|
|
159
|
+
if (clientVersion && clientVersion !== negotiationResult.version) {
|
|
160
|
+
logger_1.logger.warn(`Protocol version negotiated: client requested ${clientVersion}, server will use ${negotiationResult.version}`, {
|
|
161
|
+
reasoning: negotiationResult.reasoning
|
|
162
|
+
});
|
|
163
|
+
}
|
|
122
164
|
const response = {
|
|
123
|
-
protocolVersion:
|
|
165
|
+
protocolVersion: negotiationResult.version,
|
|
124
166
|
capabilities: {
|
|
125
167
|
tools: {},
|
|
126
168
|
},
|
|
@@ -129,13 +171,11 @@ class N8NDocumentationMCPServer {
|
|
|
129
171
|
version: version_1.PROJECT_VERSION,
|
|
130
172
|
},
|
|
131
173
|
};
|
|
132
|
-
|
|
133
|
-
logger_1.logger.debug('Initialize handler called', { response });
|
|
134
|
-
}
|
|
174
|
+
logger_1.logger.info('MCP Initialize response', { response });
|
|
135
175
|
return response;
|
|
136
176
|
});
|
|
137
|
-
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
138
|
-
|
|
177
|
+
this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async (request) => {
|
|
178
|
+
let tools = [...tools_1.n8nDocumentationToolsFinal];
|
|
139
179
|
const isConfigured = (0, n8n_api_1.isN8nApiConfigured)();
|
|
140
180
|
if (isConfigured) {
|
|
141
181
|
tools.push(...tools_n8n_manager_1.n8nManagementTools);
|
|
@@ -144,30 +184,124 @@ class N8NDocumentationMCPServer {
|
|
|
144
184
|
else {
|
|
145
185
|
logger_1.logger.debug(`Tool listing: ${tools.length} tools available (documentation only)`);
|
|
146
186
|
}
|
|
187
|
+
const clientInfo = this.clientInfo;
|
|
188
|
+
const isN8nClient = clientInfo?.name?.includes('n8n') ||
|
|
189
|
+
clientInfo?.name?.includes('langchain');
|
|
190
|
+
if (isN8nClient) {
|
|
191
|
+
logger_1.logger.info('Detected n8n client, using n8n-friendly tool descriptions');
|
|
192
|
+
tools = (0, tools_n8n_friendly_1.makeToolsN8nFriendly)(tools);
|
|
193
|
+
}
|
|
194
|
+
const validationTools = tools.filter(t => t.name.startsWith('validate_'));
|
|
195
|
+
validationTools.forEach(tool => {
|
|
196
|
+
logger_1.logger.info('Validation tool schema', {
|
|
197
|
+
toolName: tool.name,
|
|
198
|
+
inputSchema: JSON.stringify(tool.inputSchema, null, 2),
|
|
199
|
+
hasOutputSchema: !!tool.outputSchema,
|
|
200
|
+
description: tool.description
|
|
201
|
+
});
|
|
202
|
+
});
|
|
147
203
|
return { tools };
|
|
148
204
|
});
|
|
149
205
|
this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
150
206
|
const { name, arguments: args } = request.params;
|
|
207
|
+
logger_1.logger.info('Tool call received - DETAILED DEBUG', {
|
|
208
|
+
toolName: name,
|
|
209
|
+
arguments: JSON.stringify(args, null, 2),
|
|
210
|
+
argumentsType: typeof args,
|
|
211
|
+
argumentsKeys: args ? Object.keys(args) : [],
|
|
212
|
+
hasNodeType: args && 'nodeType' in args,
|
|
213
|
+
hasConfig: args && 'config' in args,
|
|
214
|
+
configType: args && args.config ? typeof args.config : 'N/A',
|
|
215
|
+
rawRequest: JSON.stringify(request.params)
|
|
216
|
+
});
|
|
217
|
+
let processedArgs = args;
|
|
218
|
+
if (args && typeof args === 'object' && 'output' in args) {
|
|
219
|
+
try {
|
|
220
|
+
const possibleNestedData = args.output;
|
|
221
|
+
if (typeof possibleNestedData === 'string' && possibleNestedData.trim().startsWith('{')) {
|
|
222
|
+
const parsed = JSON.parse(possibleNestedData);
|
|
223
|
+
if (parsed && typeof parsed === 'object') {
|
|
224
|
+
logger_1.logger.warn('Detected n8n nested output bug, attempting to extract actual arguments', {
|
|
225
|
+
originalArgs: args,
|
|
226
|
+
extractedArgs: parsed
|
|
227
|
+
});
|
|
228
|
+
if (this.validateExtractedArgs(name, parsed)) {
|
|
229
|
+
processedArgs = parsed;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
logger_1.logger.warn('Extracted arguments failed validation, using original args', {
|
|
233
|
+
toolName: name,
|
|
234
|
+
extractedArgs: parsed
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (parseError) {
|
|
241
|
+
logger_1.logger.debug('Failed to parse nested output, continuing with original args', {
|
|
242
|
+
error: parseError instanceof Error ? parseError.message : String(parseError)
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
151
246
|
try {
|
|
152
|
-
logger_1.logger.debug(`Executing tool: ${name}`, { args });
|
|
153
|
-
const result = await this.executeTool(name,
|
|
247
|
+
logger_1.logger.debug(`Executing tool: ${name}`, { args: processedArgs });
|
|
248
|
+
const result = await this.executeTool(name, processedArgs);
|
|
154
249
|
logger_1.logger.debug(`Tool ${name} executed successfully`);
|
|
155
|
-
|
|
250
|
+
let responseText;
|
|
251
|
+
let structuredContent = null;
|
|
252
|
+
try {
|
|
253
|
+
if (name.startsWith('validate_') && typeof result === 'object' && result !== null) {
|
|
254
|
+
const cleanResult = this.sanitizeValidationResult(result, name);
|
|
255
|
+
structuredContent = cleanResult;
|
|
256
|
+
responseText = JSON.stringify(cleanResult, null, 2);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
responseText = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (jsonError) {
|
|
263
|
+
logger_1.logger.warn(`Failed to stringify tool result for ${name}:`, jsonError);
|
|
264
|
+
responseText = String(result);
|
|
265
|
+
}
|
|
266
|
+
if (responseText.length > 1000000) {
|
|
267
|
+
logger_1.logger.warn(`Tool ${name} response is very large (${responseText.length} chars), truncating`);
|
|
268
|
+
responseText = responseText.substring(0, 999000) + '\n\n[Response truncated due to size limits]';
|
|
269
|
+
structuredContent = null;
|
|
270
|
+
}
|
|
271
|
+
const mcpResponse = {
|
|
156
272
|
content: [
|
|
157
273
|
{
|
|
158
274
|
type: 'text',
|
|
159
|
-
text:
|
|
275
|
+
text: responseText,
|
|
160
276
|
},
|
|
161
277
|
],
|
|
162
278
|
};
|
|
279
|
+
if (name.startsWith('validate_') && structuredContent !== null) {
|
|
280
|
+
mcpResponse.structuredContent = structuredContent;
|
|
281
|
+
}
|
|
282
|
+
return mcpResponse;
|
|
163
283
|
}
|
|
164
284
|
catch (error) {
|
|
165
285
|
logger_1.logger.error(`Error executing tool ${name}`, error);
|
|
286
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
287
|
+
let helpfulMessage = `Error executing tool ${name}: ${errorMessage}`;
|
|
288
|
+
if (errorMessage.includes('required') || errorMessage.includes('missing')) {
|
|
289
|
+
helpfulMessage += '\n\nNote: This error often occurs when the AI agent sends incomplete or incorrectly formatted parameters. Please ensure all required fields are provided with the correct types.';
|
|
290
|
+
}
|
|
291
|
+
else if (errorMessage.includes('type') || errorMessage.includes('expected')) {
|
|
292
|
+
helpfulMessage += '\n\nNote: This error indicates a type mismatch. The AI agent may be sending data in the wrong format (e.g., string instead of object).';
|
|
293
|
+
}
|
|
294
|
+
else if (errorMessage.includes('Unknown category') || errorMessage.includes('not found')) {
|
|
295
|
+
helpfulMessage += '\n\nNote: The requested resource or category was not found. Please check the available options.';
|
|
296
|
+
}
|
|
297
|
+
if (name.startsWith('validate_') && (errorMessage.includes('config') || errorMessage.includes('nodeType'))) {
|
|
298
|
+
helpfulMessage += '\n\nFor validation tools:\n- nodeType should be a string (e.g., "nodes-base.webhook")\n- config should be an object (e.g., {})';
|
|
299
|
+
}
|
|
166
300
|
return {
|
|
167
301
|
content: [
|
|
168
302
|
{
|
|
169
303
|
type: 'text',
|
|
170
|
-
text:
|
|
304
|
+
text: helpfulMessage,
|
|
171
305
|
},
|
|
172
306
|
],
|
|
173
307
|
isError: true,
|
|
@@ -175,82 +309,291 @@ class N8NDocumentationMCPServer {
|
|
|
175
309
|
}
|
|
176
310
|
});
|
|
177
311
|
}
|
|
312
|
+
sanitizeValidationResult(result, toolName) {
|
|
313
|
+
if (!result || typeof result !== 'object') {
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
const sanitized = { ...result };
|
|
317
|
+
if (toolName === 'validate_node_minimal') {
|
|
318
|
+
const filtered = {
|
|
319
|
+
nodeType: String(sanitized.nodeType || ''),
|
|
320
|
+
displayName: String(sanitized.displayName || ''),
|
|
321
|
+
valid: Boolean(sanitized.valid),
|
|
322
|
+
missingRequiredFields: Array.isArray(sanitized.missingRequiredFields)
|
|
323
|
+
? sanitized.missingRequiredFields.map(String)
|
|
324
|
+
: []
|
|
325
|
+
};
|
|
326
|
+
return filtered;
|
|
327
|
+
}
|
|
328
|
+
else if (toolName === 'validate_node_operation') {
|
|
329
|
+
let summary = sanitized.summary;
|
|
330
|
+
if (!summary || typeof summary !== 'object') {
|
|
331
|
+
summary = {
|
|
332
|
+
hasErrors: Array.isArray(sanitized.errors) ? sanitized.errors.length > 0 : false,
|
|
333
|
+
errorCount: Array.isArray(sanitized.errors) ? sanitized.errors.length : 0,
|
|
334
|
+
warningCount: Array.isArray(sanitized.warnings) ? sanitized.warnings.length : 0,
|
|
335
|
+
suggestionCount: Array.isArray(sanitized.suggestions) ? sanitized.suggestions.length : 0
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const filtered = {
|
|
339
|
+
nodeType: String(sanitized.nodeType || ''),
|
|
340
|
+
workflowNodeType: String(sanitized.workflowNodeType || sanitized.nodeType || ''),
|
|
341
|
+
displayName: String(sanitized.displayName || ''),
|
|
342
|
+
valid: Boolean(sanitized.valid),
|
|
343
|
+
errors: Array.isArray(sanitized.errors) ? sanitized.errors : [],
|
|
344
|
+
warnings: Array.isArray(sanitized.warnings) ? sanitized.warnings : [],
|
|
345
|
+
suggestions: Array.isArray(sanitized.suggestions) ? sanitized.suggestions : [],
|
|
346
|
+
summary: summary
|
|
347
|
+
};
|
|
348
|
+
return filtered;
|
|
349
|
+
}
|
|
350
|
+
else if (toolName.startsWith('validate_workflow')) {
|
|
351
|
+
sanitized.valid = Boolean(sanitized.valid);
|
|
352
|
+
sanitized.errors = Array.isArray(sanitized.errors) ? sanitized.errors : [];
|
|
353
|
+
sanitized.warnings = Array.isArray(sanitized.warnings) ? sanitized.warnings : [];
|
|
354
|
+
if (toolName === 'validate_workflow') {
|
|
355
|
+
if (!sanitized.summary || typeof sanitized.summary !== 'object') {
|
|
356
|
+
sanitized.summary = {
|
|
357
|
+
totalNodes: 0,
|
|
358
|
+
enabledNodes: 0,
|
|
359
|
+
triggerNodes: 0,
|
|
360
|
+
validConnections: 0,
|
|
361
|
+
invalidConnections: 0,
|
|
362
|
+
expressionsValidated: 0,
|
|
363
|
+
errorCount: sanitized.errors.length,
|
|
364
|
+
warningCount: sanitized.warnings.length
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
if (!sanitized.statistics || typeof sanitized.statistics !== 'object') {
|
|
370
|
+
sanitized.statistics = {
|
|
371
|
+
totalNodes: 0,
|
|
372
|
+
triggerNodes: 0,
|
|
373
|
+
validConnections: 0,
|
|
374
|
+
invalidConnections: 0,
|
|
375
|
+
expressionsValidated: 0
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return JSON.parse(JSON.stringify(sanitized));
|
|
381
|
+
}
|
|
382
|
+
validateToolParams(toolName, args, requiredParams) {
|
|
383
|
+
const missing = [];
|
|
384
|
+
for (const param of requiredParams) {
|
|
385
|
+
if (!(param in args) || args[param] === undefined || args[param] === null) {
|
|
386
|
+
missing.push(param);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (missing.length > 0) {
|
|
390
|
+
throw new Error(`Missing required parameters for ${toolName}: ${missing.join(', ')}. Please provide the required parameters to use this tool.`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
validateExtractedArgs(toolName, args) {
|
|
394
|
+
if (!args || typeof args !== 'object') {
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
const allTools = [...tools_1.n8nDocumentationToolsFinal, ...tools_n8n_manager_1.n8nManagementTools];
|
|
398
|
+
const tool = allTools.find(t => t.name === toolName);
|
|
399
|
+
if (!tool || !tool.inputSchema) {
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
const schema = tool.inputSchema;
|
|
403
|
+
const required = schema.required || [];
|
|
404
|
+
const properties = schema.properties || {};
|
|
405
|
+
for (const requiredField of required) {
|
|
406
|
+
if (!(requiredField in args)) {
|
|
407
|
+
logger_1.logger.debug(`Extracted args missing required field: ${requiredField}`, {
|
|
408
|
+
toolName,
|
|
409
|
+
extractedArgs: args,
|
|
410
|
+
required
|
|
411
|
+
});
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
for (const [fieldName, fieldValue] of Object.entries(args)) {
|
|
416
|
+
if (properties[fieldName]) {
|
|
417
|
+
const expectedType = properties[fieldName].type;
|
|
418
|
+
const actualType = Array.isArray(fieldValue) ? 'array' : typeof fieldValue;
|
|
419
|
+
if (expectedType && expectedType !== actualType) {
|
|
420
|
+
if (expectedType === 'number' && actualType === 'string' && !isNaN(Number(fieldValue))) {
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
logger_1.logger.debug(`Extracted args field type mismatch: ${fieldName}`, {
|
|
424
|
+
toolName,
|
|
425
|
+
expectedType,
|
|
426
|
+
actualType,
|
|
427
|
+
fieldValue
|
|
428
|
+
});
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
if (schema.additionalProperties === false) {
|
|
434
|
+
const allowedFields = Object.keys(properties);
|
|
435
|
+
const extraFields = Object.keys(args).filter(field => !allowedFields.includes(field));
|
|
436
|
+
if (extraFields.length > 0) {
|
|
437
|
+
logger_1.logger.debug(`Extracted args have extra fields`, {
|
|
438
|
+
toolName,
|
|
439
|
+
extraFields,
|
|
440
|
+
allowedFields
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return true;
|
|
445
|
+
}
|
|
178
446
|
async executeTool(name, args) {
|
|
447
|
+
args = args || {};
|
|
448
|
+
logger_1.logger.info(`Tool execution: ${name}`, {
|
|
449
|
+
args: typeof args === 'object' ? JSON.stringify(args) : args,
|
|
450
|
+
argsType: typeof args,
|
|
451
|
+
argsKeys: typeof args === 'object' ? Object.keys(args) : 'not-object'
|
|
452
|
+
});
|
|
453
|
+
if (typeof args !== 'object' || args === null) {
|
|
454
|
+
throw new Error(`Invalid arguments for tool ${name}: expected object, got ${typeof args}`);
|
|
455
|
+
}
|
|
179
456
|
switch (name) {
|
|
180
457
|
case 'tools_documentation':
|
|
181
458
|
return this.getToolsDocumentation(args.topic, args.depth);
|
|
182
459
|
case 'list_nodes':
|
|
183
460
|
return this.listNodes(args);
|
|
184
461
|
case 'get_node_info':
|
|
462
|
+
this.validateToolParams(name, args, ['nodeType']);
|
|
185
463
|
return this.getNodeInfo(args.nodeType);
|
|
186
464
|
case 'search_nodes':
|
|
187
|
-
|
|
465
|
+
this.validateToolParams(name, args, ['query']);
|
|
466
|
+
const limit = args.limit !== undefined ? Number(args.limit) || 20 : 20;
|
|
467
|
+
return this.searchNodes(args.query, limit, { mode: args.mode });
|
|
188
468
|
case 'list_ai_tools':
|
|
189
469
|
return this.listAITools();
|
|
190
470
|
case 'get_node_documentation':
|
|
471
|
+
this.validateToolParams(name, args, ['nodeType']);
|
|
191
472
|
return this.getNodeDocumentation(args.nodeType);
|
|
192
473
|
case 'get_database_statistics':
|
|
193
474
|
return this.getDatabaseStatistics();
|
|
194
475
|
case 'get_node_essentials':
|
|
476
|
+
this.validateToolParams(name, args, ['nodeType']);
|
|
195
477
|
return this.getNodeEssentials(args.nodeType);
|
|
196
478
|
case 'search_node_properties':
|
|
197
|
-
|
|
479
|
+
this.validateToolParams(name, args, ['nodeType', 'query']);
|
|
480
|
+
const maxResults = args.maxResults !== undefined ? Number(args.maxResults) || 20 : 20;
|
|
481
|
+
return this.searchNodeProperties(args.nodeType, args.query, maxResults);
|
|
198
482
|
case 'get_node_for_task':
|
|
483
|
+
this.validateToolParams(name, args, ['task']);
|
|
199
484
|
return this.getNodeForTask(args.task);
|
|
200
485
|
case 'list_tasks':
|
|
201
486
|
return this.listTasks(args.category);
|
|
202
487
|
case 'validate_node_operation':
|
|
488
|
+
this.validateToolParams(name, args, ['nodeType', 'config']);
|
|
489
|
+
if (typeof args.config !== 'object' || args.config === null) {
|
|
490
|
+
logger_1.logger.warn(`validate_node_operation called with invalid config type: ${typeof args.config}`);
|
|
491
|
+
return {
|
|
492
|
+
nodeType: args.nodeType || 'unknown',
|
|
493
|
+
workflowNodeType: args.nodeType || 'unknown',
|
|
494
|
+
displayName: 'Unknown Node',
|
|
495
|
+
valid: false,
|
|
496
|
+
errors: [{
|
|
497
|
+
type: 'config',
|
|
498
|
+
property: 'config',
|
|
499
|
+
message: 'Invalid config format - expected object',
|
|
500
|
+
fix: 'Provide config as an object with node properties'
|
|
501
|
+
}],
|
|
502
|
+
warnings: [],
|
|
503
|
+
suggestions: [],
|
|
504
|
+
summary: {
|
|
505
|
+
hasErrors: true,
|
|
506
|
+
errorCount: 1,
|
|
507
|
+
warningCount: 0,
|
|
508
|
+
suggestionCount: 0
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
}
|
|
203
512
|
return this.validateNodeConfig(args.nodeType, args.config, 'operation', args.profile);
|
|
204
513
|
case 'validate_node_minimal':
|
|
514
|
+
this.validateToolParams(name, args, ['nodeType', 'config']);
|
|
515
|
+
if (typeof args.config !== 'object' || args.config === null) {
|
|
516
|
+
logger_1.logger.warn(`validate_node_minimal called with invalid config type: ${typeof args.config}`);
|
|
517
|
+
return {
|
|
518
|
+
nodeType: args.nodeType || 'unknown',
|
|
519
|
+
displayName: 'Unknown Node',
|
|
520
|
+
valid: false,
|
|
521
|
+
missingRequiredFields: ['Invalid config format - expected object']
|
|
522
|
+
};
|
|
523
|
+
}
|
|
205
524
|
return this.validateNodeMinimal(args.nodeType, args.config);
|
|
206
525
|
case 'get_property_dependencies':
|
|
526
|
+
this.validateToolParams(name, args, ['nodeType']);
|
|
207
527
|
return this.getPropertyDependencies(args.nodeType, args.config);
|
|
208
528
|
case 'get_node_as_tool_info':
|
|
529
|
+
this.validateToolParams(name, args, ['nodeType']);
|
|
209
530
|
return this.getNodeAsToolInfo(args.nodeType);
|
|
210
531
|
case 'list_node_templates':
|
|
211
|
-
|
|
532
|
+
this.validateToolParams(name, args, ['nodeTypes']);
|
|
533
|
+
const templateLimit = args.limit !== undefined ? Number(args.limit) || 10 : 10;
|
|
534
|
+
return this.listNodeTemplates(args.nodeTypes, templateLimit);
|
|
212
535
|
case 'get_template':
|
|
213
|
-
|
|
536
|
+
this.validateToolParams(name, args, ['templateId']);
|
|
537
|
+
const templateId = Number(args.templateId);
|
|
538
|
+
return this.getTemplate(templateId);
|
|
214
539
|
case 'search_templates':
|
|
215
|
-
|
|
540
|
+
this.validateToolParams(name, args, ['query']);
|
|
541
|
+
const searchLimit = args.limit !== undefined ? Number(args.limit) || 20 : 20;
|
|
542
|
+
return this.searchTemplates(args.query, searchLimit);
|
|
216
543
|
case 'get_templates_for_task':
|
|
544
|
+
this.validateToolParams(name, args, ['task']);
|
|
217
545
|
return this.getTemplatesForTask(args.task);
|
|
218
546
|
case 'validate_workflow':
|
|
547
|
+
this.validateToolParams(name, args, ['workflow']);
|
|
219
548
|
return this.validateWorkflow(args.workflow, args.options);
|
|
220
549
|
case 'validate_workflow_connections':
|
|
550
|
+
this.validateToolParams(name, args, ['workflow']);
|
|
221
551
|
return this.validateWorkflowConnections(args.workflow);
|
|
222
552
|
case 'validate_workflow_expressions':
|
|
553
|
+
this.validateToolParams(name, args, ['workflow']);
|
|
223
554
|
return this.validateWorkflowExpressions(args.workflow);
|
|
224
555
|
case 'n8n_create_workflow':
|
|
556
|
+
this.validateToolParams(name, args, ['name', 'nodes', 'connections']);
|
|
225
557
|
return n8nHandlers.handleCreateWorkflow(args);
|
|
226
558
|
case 'n8n_get_workflow':
|
|
559
|
+
this.validateToolParams(name, args, ['id']);
|
|
227
560
|
return n8nHandlers.handleGetWorkflow(args);
|
|
228
561
|
case 'n8n_get_workflow_details':
|
|
562
|
+
this.validateToolParams(name, args, ['id']);
|
|
229
563
|
return n8nHandlers.handleGetWorkflowDetails(args);
|
|
230
564
|
case 'n8n_get_workflow_structure':
|
|
565
|
+
this.validateToolParams(name, args, ['id']);
|
|
231
566
|
return n8nHandlers.handleGetWorkflowStructure(args);
|
|
232
567
|
case 'n8n_get_workflow_minimal':
|
|
568
|
+
this.validateToolParams(name, args, ['id']);
|
|
233
569
|
return n8nHandlers.handleGetWorkflowMinimal(args);
|
|
234
570
|
case 'n8n_update_full_workflow':
|
|
571
|
+
this.validateToolParams(name, args, ['id']);
|
|
235
572
|
return n8nHandlers.handleUpdateWorkflow(args);
|
|
236
573
|
case 'n8n_update_partial_workflow':
|
|
574
|
+
this.validateToolParams(name, args, ['id', 'operations']);
|
|
237
575
|
return (0, handlers_workflow_diff_1.handleUpdatePartialWorkflow)(args);
|
|
238
576
|
case 'n8n_delete_workflow':
|
|
577
|
+
this.validateToolParams(name, args, ['id']);
|
|
239
578
|
return n8nHandlers.handleDeleteWorkflow(args);
|
|
240
579
|
case 'n8n_list_workflows':
|
|
241
580
|
return n8nHandlers.handleListWorkflows(args);
|
|
242
581
|
case 'n8n_validate_workflow':
|
|
582
|
+
this.validateToolParams(name, args, ['id']);
|
|
243
583
|
await this.ensureInitialized();
|
|
244
584
|
if (!this.repository)
|
|
245
585
|
throw new Error('Repository not initialized');
|
|
246
586
|
return n8nHandlers.handleValidateWorkflow(args, this.repository);
|
|
247
587
|
case 'n8n_trigger_webhook_workflow':
|
|
588
|
+
this.validateToolParams(name, args, ['webhookUrl']);
|
|
248
589
|
return n8nHandlers.handleTriggerWebhookWorkflow(args);
|
|
249
590
|
case 'n8n_get_execution':
|
|
591
|
+
this.validateToolParams(name, args, ['id']);
|
|
250
592
|
return n8nHandlers.handleGetExecution(args);
|
|
251
593
|
case 'n8n_list_executions':
|
|
252
594
|
return n8nHandlers.handleListExecutions(args);
|
|
253
595
|
case 'n8n_delete_execution':
|
|
596
|
+
this.validateToolParams(name, args, ['id']);
|
|
254
597
|
return n8nHandlers.handleDeleteExecution(args);
|
|
255
598
|
case 'n8n_health_check':
|
|
256
599
|
return n8nHandlers.handleHealthCheck();
|
|
@@ -1441,6 +1784,50 @@ Full documentation is being prepared. For now, use get_node_essentials for confi
|
|
|
1441
1784
|
await this.ensureInitialized();
|
|
1442
1785
|
if (!this.repository)
|
|
1443
1786
|
throw new Error('Repository not initialized');
|
|
1787
|
+
logger_1.logger.info('Workflow validation requested', {
|
|
1788
|
+
hasWorkflow: !!workflow,
|
|
1789
|
+
workflowType: typeof workflow,
|
|
1790
|
+
hasNodes: workflow?.nodes !== undefined,
|
|
1791
|
+
nodesType: workflow?.nodes ? typeof workflow.nodes : 'undefined',
|
|
1792
|
+
nodesIsArray: Array.isArray(workflow?.nodes),
|
|
1793
|
+
nodesCount: Array.isArray(workflow?.nodes) ? workflow.nodes.length : 0,
|
|
1794
|
+
hasConnections: workflow?.connections !== undefined,
|
|
1795
|
+
connectionsType: workflow?.connections ? typeof workflow.connections : 'undefined',
|
|
1796
|
+
options: options
|
|
1797
|
+
});
|
|
1798
|
+
if (!workflow || typeof workflow !== 'object') {
|
|
1799
|
+
return {
|
|
1800
|
+
valid: false,
|
|
1801
|
+
errors: [{
|
|
1802
|
+
node: 'workflow',
|
|
1803
|
+
message: 'Workflow must be an object with nodes and connections',
|
|
1804
|
+
details: 'Expected format: ' + (0, workflow_examples_1.getWorkflowExampleString)()
|
|
1805
|
+
}],
|
|
1806
|
+
summary: { errorCount: 1 }
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
if (!workflow.nodes || !Array.isArray(workflow.nodes)) {
|
|
1810
|
+
return {
|
|
1811
|
+
valid: false,
|
|
1812
|
+
errors: [{
|
|
1813
|
+
node: 'workflow',
|
|
1814
|
+
message: 'Workflow must have a nodes array',
|
|
1815
|
+
details: 'Expected: workflow.nodes = [array of node objects]. ' + (0, workflow_examples_1.getWorkflowExampleString)()
|
|
1816
|
+
}],
|
|
1817
|
+
summary: { errorCount: 1 }
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
if (!workflow.connections || typeof workflow.connections !== 'object') {
|
|
1821
|
+
return {
|
|
1822
|
+
valid: false,
|
|
1823
|
+
errors: [{
|
|
1824
|
+
node: 'workflow',
|
|
1825
|
+
message: 'Workflow must have a connections object',
|
|
1826
|
+
details: 'Expected: workflow.connections = {} (can be empty object). ' + (0, workflow_examples_1.getWorkflowExampleString)()
|
|
1827
|
+
}],
|
|
1828
|
+
summary: { errorCount: 1 }
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1444
1831
|
const validator = new workflow_validator_1.WorkflowValidator(this.repository, enhanced_config_validator_1.EnhancedConfigValidator);
|
|
1445
1832
|
try {
|
|
1446
1833
|
const result = await validator.validateWorkflow(workflow, options);
|