mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 06:22:04 +00:00
fix: add validation for resourceLocator properties in AI model nodes
This fixes a critical validation gap where AI agents could create invalid
configurations for nodes using resourceLocator properties (primarily AI model
nodes like OpenAI Chat Model v1.2+, Anthropic, Cohere, etc.).
Before this fix, AI agents could incorrectly pass a string value like:
model: "gpt-4o-mini"
Instead of the required object format:
model: { mode: "list", value: "gpt-4o-mini" }
These invalid configs would pass validation but fail at runtime in n8n.
Changes:
- Added resourceLocator type validation in config-validator.ts (lines 237-274)
- Validates value is an object with required 'mode' and 'value' properties
- Provides helpful error messages with exact fix suggestions
- Added 10 comprehensive test cases (100% passing)
- Updated version to 2.17.3
- Added CHANGELOG entry
Affected nodes: OpenAI Chat Model (v1.2+), Anthropic, Cohere, DeepSeek,
Groq, Mistral, OpenRouter, xAI Grok Chat Models, and embeddings nodes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
35
CHANGELOG.md
35
CHANGELOG.md
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.17.3] - 2025-10-07
|
||||
|
||||
### 🔧 Validation
|
||||
|
||||
**Fixed critical validation gap for AI model nodes with resourceLocator properties.**
|
||||
|
||||
This release adds validation for `resourceLocator` type properties, fixing a critical issue where AI agents could create invalid configurations that passed validation but failed at runtime.
|
||||
|
||||
#### Fixed
|
||||
|
||||
- **resourceLocator Property Validation**
|
||||
- **Issue:** No validation existed for `resourceLocator` type properties used in AI model nodes
|
||||
- **Impact:**
|
||||
- AI agents could create invalid configurations like `model: "gpt-4o-mini"` (string) instead of `model: {mode: "list", value: "gpt-4o-mini"}` (object)
|
||||
- Invalid configs passed validation but failed at runtime in n8n
|
||||
- Affected many langchain nodes: OpenAI Chat Model (v1.2+), Anthropic, Cohere, DeepSeek, Groq, Mistral, OpenRouter, xAI Grok, and embeddings nodes
|
||||
- **Root Cause:** `validatePropertyTypes()` method in ConfigValidator only validated `string`, `number`, `boolean`, and `options` types - `resourceLocator` was completely missing
|
||||
- **Fix:** Added comprehensive resourceLocator validation in `config-validator.ts:237-274`
|
||||
- Validates value is an object (not string, number, null, or array)
|
||||
- Validates required `mode` property exists and is a string
|
||||
- Validates required `value` property exists
|
||||
- Provides helpful error messages with exact fix suggestions
|
||||
- Example error: `Property 'model' is a resourceLocator and must be an object with 'mode' and 'value' properties, got string`
|
||||
- Example fix: `Change model to { mode: "list", value: "gpt-4o-mini" } or { mode: "id", value: "gpt-4o-mini" }`
|
||||
|
||||
#### Added
|
||||
|
||||
- 10 comprehensive test cases for resourceLocator validation covering:
|
||||
- String value rejection with helpful fix suggestions
|
||||
- Null and array value rejection
|
||||
- Missing `mode` or `value` property detection
|
||||
- Invalid `mode` type detection (e.g., number instead of string)
|
||||
- Valid resourceLocator acceptance for both "list" and "id" modes
|
||||
- All tests passing (100% coverage for new validation logic)
|
||||
|
||||
## [2.17.1] - 2025-10-07
|
||||
|
||||
### 🔧 Telemetry
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.17.2",
|
||||
"version": "2.17.3",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp-runtime",
|
||||
"version": "2.17.1",
|
||||
"version": "2.17.3",
|
||||
"description": "n8n MCP Server Runtime Dependencies Only",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -234,8 +234,45 @@ export class ConfigValidator {
|
||||
message: `Property '${key}' must be a boolean, got ${typeof value}`,
|
||||
fix: `Change ${key} to true or false`
|
||||
});
|
||||
} else if (prop.type === 'resourceLocator') {
|
||||
// resourceLocator must be an object with mode and value properties
|
||||
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
||||
const fixValue = typeof value === 'string' ? value : JSON.stringify(value);
|
||||
errors.push({
|
||||
type: 'invalid_type',
|
||||
property: key,
|
||||
message: `Property '${key}' is a resourceLocator and must be an object with 'mode' and 'value' properties, got ${typeof value}`,
|
||||
fix: `Change ${key} to { mode: "list", value: ${JSON.stringify(fixValue)} } or { mode: "id", value: ${JSON.stringify(fixValue)} }`
|
||||
});
|
||||
} else {
|
||||
// Check required properties
|
||||
if (!value.mode) {
|
||||
errors.push({
|
||||
type: 'missing_required',
|
||||
property: `${key}.mode`,
|
||||
message: `resourceLocator '${key}' is missing required property 'mode'`,
|
||||
fix: `Add mode property: { mode: "list", value: ${JSON.stringify(value.value || '')} }`
|
||||
});
|
||||
} else if (typeof value.mode !== 'string') {
|
||||
errors.push({
|
||||
type: 'invalid_type',
|
||||
property: `${key}.mode`,
|
||||
message: `resourceLocator '${key}.mode' must be a string, got ${typeof value.mode}`,
|
||||
fix: `Set mode to "list" or "id"`
|
||||
});
|
||||
}
|
||||
|
||||
if (value.value === undefined) {
|
||||
errors.push({
|
||||
type: 'missing_required',
|
||||
property: `${key}.value`,
|
||||
message: `resourceLocator '${key}' is missing required property 'value'`,
|
||||
fix: `Add value property to specify the ${prop.displayName || key}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Options validation
|
||||
if (prop.type === 'options' && prop.options) {
|
||||
const validValues = prop.options.map((opt: any) =>
|
||||
|
||||
@@ -439,4 +439,243 @@ describe('ConfigValidator - Basic Validation', () => {
|
||||
expect(result.suggestions.length).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resourceLocator validation', () => {
|
||||
it('should reject string value when resourceLocator object is required', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: 'gpt-4o-mini' // Wrong - should be object with mode and value
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
displayName: 'Model',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
default: { mode: 'list', value: 'gpt-4o-mini' }
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors).toHaveLength(1);
|
||||
expect(result.errors[0]).toMatchObject({
|
||||
type: 'invalid_type',
|
||||
property: 'model',
|
||||
message: expect.stringContaining('must be an object with \'mode\' and \'value\' properties')
|
||||
});
|
||||
expect(result.errors[0].fix).toContain('mode');
|
||||
expect(result.errors[0].fix).toContain('value');
|
||||
});
|
||||
|
||||
it('should accept valid resourceLocator with mode and value', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: {
|
||||
mode: 'list',
|
||||
value: 'gpt-4o-mini'
|
||||
}
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
displayName: 'Model',
|
||||
type: 'resourceLocator',
|
||||
required: true,
|
||||
default: { mode: 'list', value: 'gpt-4o-mini' }
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should reject null value for resourceLocator', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: null
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e =>
|
||||
e.property === 'model' &&
|
||||
e.type === 'invalid_type'
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject array value for resourceLocator', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: ['gpt-4o-mini']
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e =>
|
||||
e.property === 'model' &&
|
||||
e.type === 'invalid_type' &&
|
||||
e.message.includes('must be an object')
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect missing mode property in resourceLocator', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: {
|
||||
value: 'gpt-4o-mini'
|
||||
// Missing mode property
|
||||
}
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e =>
|
||||
e.property === 'model.mode' &&
|
||||
e.type === 'missing_required' &&
|
||||
e.message.includes('missing required property \'mode\'')
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect missing value property in resourceLocator', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: {
|
||||
mode: 'list'
|
||||
// Missing value property
|
||||
}
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
displayName: 'Model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e =>
|
||||
e.property === 'model.value' &&
|
||||
e.type === 'missing_required' &&
|
||||
e.message.includes('missing required property \'value\'')
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('should detect invalid mode type in resourceLocator', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: {
|
||||
mode: 123, // Should be string
|
||||
value: 'gpt-4o-mini'
|
||||
}
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors.some(e =>
|
||||
e.property === 'model.mode' &&
|
||||
e.type === 'invalid_type' &&
|
||||
e.message.includes('must be a string')
|
||||
)).toBe(true);
|
||||
});
|
||||
|
||||
it('should accept resourceLocator with mode "id"', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: {
|
||||
mode: 'id',
|
||||
value: 'gpt-4o-2024-11-20'
|
||||
}
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should reject number value when resourceLocator is required', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: 12345 // Wrong type
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.errors[0].type).toBe('invalid_type');
|
||||
expect(result.errors[0].message).toContain('must be an object');
|
||||
});
|
||||
|
||||
it('should provide helpful fix suggestion for string to resourceLocator conversion', () => {
|
||||
const nodeType = '@n8n/n8n-nodes-langchain.lmChatOpenAi';
|
||||
const config = {
|
||||
model: 'gpt-4o-mini'
|
||||
};
|
||||
const properties = [
|
||||
{
|
||||
name: 'model',
|
||||
type: 'resourceLocator',
|
||||
required: true
|
||||
}
|
||||
];
|
||||
|
||||
const result = ConfigValidator.validate(nodeType, config, properties);
|
||||
|
||||
expect(result.errors[0].fix).toContain('{ mode: "list", value: "gpt-4o-mini" }');
|
||||
expect(result.errors[0].fix).toContain('{ mode: "id", value: "gpt-4o-mini" }');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user