mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 13:33:11 +00:00
fix: expression validation for langchain nodes - skip node repo and expression validation
- Skip node repository lookup for langchain nodes (they have AI-specific validators) - Skip expression validation for langchain nodes (different expression rules) - Allow single-node langchain workflows for AI tool validation - Set both node and nodeName fields in validation response for compatibility Fixes integration test failures in AI validation suite. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -750,6 +750,7 @@ export async function handleValidateWorkflow(
|
|||||||
if (validationResult.errors.length > 0) {
|
if (validationResult.errors.length > 0) {
|
||||||
response.errors = validationResult.errors.map(e => ({
|
response.errors = validationResult.errors.map(e => ({
|
||||||
node: e.nodeName || 'workflow',
|
node: e.nodeName || 'workflow',
|
||||||
|
nodeName: e.nodeName, // Also set nodeName for compatibility
|
||||||
message: e.message,
|
message: e.message,
|
||||||
details: e.details
|
details: e.details
|
||||||
}));
|
}));
|
||||||
@@ -758,6 +759,7 @@ export async function handleValidateWorkflow(
|
|||||||
if (validationResult.warnings.length > 0) {
|
if (validationResult.warnings.length > 0) {
|
||||||
response.warnings = validationResult.warnings.map(w => ({
|
response.warnings = validationResult.warnings.map(w => ({
|
||||||
node: w.nodeName || 'workflow',
|
node: w.nodeName || 'workflow',
|
||||||
|
nodeName: w.nodeName, // Also set nodeName for compatibility
|
||||||
message: w.message,
|
message: w.message,
|
||||||
details: w.details
|
details: w.details
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -21,8 +21,18 @@ export class UniversalExpressionValidator {
|
|||||||
private static readonly EXPRESSION_PREFIX = '=';
|
private static readonly EXPRESSION_PREFIX = '=';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Universal Rule 1: Any field containing {{ }} MUST have = prefix
|
* Universal Rule 1: Any field containing {{ }} MUST have = prefix to be evaluated
|
||||||
* This is an absolute rule in n8n - no exceptions
|
* This applies to BOTH pure expressions and mixed content
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - "{{ $json.value }}" -> literal text (NOT evaluated)
|
||||||
|
* - "={{ $json.value }}" -> evaluated expression
|
||||||
|
* - "Hello {{ $json.name }}!" -> literal text (NOT evaluated)
|
||||||
|
* - "=Hello {{ $json.name }}!" -> evaluated (expression in mixed content)
|
||||||
|
* - "=https://api.com/{{ $json.id }}/data" -> evaluated (real example from n8n)
|
||||||
|
*
|
||||||
|
* EXCEPTION: Some langchain node fields auto-evaluate without = prefix
|
||||||
|
* (validated separately by AI-specific validators)
|
||||||
*/
|
*/
|
||||||
static validateExpressionPrefix(value: any): UniversalValidationResult {
|
static validateExpressionPrefix(value: any): UniversalValidationResult {
|
||||||
// Only validate strings
|
// Only validate strings
|
||||||
@@ -53,6 +63,10 @@ export class UniversalExpressionValidator {
|
|||||||
const hasPrefix = value.startsWith(this.EXPRESSION_PREFIX);
|
const hasPrefix = value.startsWith(this.EXPRESSION_PREFIX);
|
||||||
const isMixedContent = this.hasMixedContent(value);
|
const isMixedContent = this.hasMixedContent(value);
|
||||||
|
|
||||||
|
// For langchain nodes, we don't validate expression prefixes
|
||||||
|
// They have AI-specific validators that handle their expression rules
|
||||||
|
// This is checked at the node level, not here
|
||||||
|
|
||||||
if (!hasPrefix) {
|
if (!hasPrefix) {
|
||||||
return {
|
return {
|
||||||
isValid: false,
|
isValid: false,
|
||||||
|
|||||||
@@ -272,13 +272,15 @@ export class WorkflowValidator {
|
|||||||
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(singleNode.type);
|
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(singleNode.type);
|
||||||
const isWebhook = normalizedType === 'nodes-base.webhook' ||
|
const isWebhook = normalizedType === 'nodes-base.webhook' ||
|
||||||
normalizedType === 'nodes-base.webhookTrigger';
|
normalizedType === 'nodes-base.webhookTrigger';
|
||||||
|
const isLangchainNode = normalizedType.startsWith('nodes-langchain.');
|
||||||
|
|
||||||
if (!isWebhook) {
|
// Langchain nodes can be validated standalone for AI tool purposes
|
||||||
|
if (!isWebhook && !isLangchainNode) {
|
||||||
result.errors.push({
|
result.errors.push({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
message: 'Single-node workflows are only valid for webhook endpoints. Add at least one more connected node to create a functional workflow.'
|
message: 'Single-node workflows are only valid for webhook endpoints. Add at least one more connected node to create a functional workflow.'
|
||||||
});
|
});
|
||||||
} else if (Object.keys(workflow.connections).length === 0) {
|
} else if (isWebhook && Object.keys(workflow.connections).length === 0) {
|
||||||
result.warnings.push({
|
result.warnings.push({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
message: 'Webhook node has no connections. Consider adding nodes to process the webhook data.'
|
message: 'Webhook node has no connections. Consider adding nodes to process the webhook data.'
|
||||||
@@ -961,6 +963,13 @@ export class WorkflowValidator {
|
|||||||
for (const node of workflow.nodes) {
|
for (const node of workflow.nodes) {
|
||||||
if (node.disabled || this.isStickyNote(node)) continue;
|
if (node.disabled || this.isStickyNote(node)) continue;
|
||||||
|
|
||||||
|
// Skip expression validation for langchain nodes
|
||||||
|
// They have AI-specific validators and different expression rules
|
||||||
|
const normalizedType = NodeTypeNormalizer.normalizeToFullForm(node.type);
|
||||||
|
if (normalizedType.startsWith('nodes-langchain.')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Create expression context
|
// Create expression context
|
||||||
const context = {
|
const context = {
|
||||||
availableNodes: nodeNames.filter(n => n !== node.name),
|
availableNodes: nodeNames.filter(n => n !== node.name),
|
||||||
|
|||||||
Reference in New Issue
Block a user