mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-28 23:53:07 +00:00
* chore: update n8n to 2.8.3 and bump version to 2.35.3 - Updated n8n from 2.6.3 to 2.8.3 - Updated n8n-core from 2.6.1 to 2.8.1 - Updated n8n-workflow from 2.6.0 to 2.8.0 - Updated @n8n/n8n-nodes-langchain from 2.6.2 to 2.8.1 - Fixed node loader to bypass restricted package.json exports in @n8n/n8n-nodes-langchain >=2.9.0 (resolves via absolute paths) - Fixed community doc generator for cloud LLMs: added API key env var support, switched to max_completion_tokens, auto-omit temperature - Rebuilt node database with 1,236 nodes (673 n8n-nodes-base, 133 @n8n/n8n-nodes-langchain, 430 community) - Refreshed community nodes (361 verified + 69 npm) with 424 AI summaries - Updated README badge with new n8n version and node counts - Updated CHANGELOG with dependency changes Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update documentation-generator tests for max_completion_tokens - Updated test assertions from max_tokens to max_completion_tokens - Updated testConnection token limit expectation from 10 to 200 - Added temperature to test config to match new configurable behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
107 lines
4.1 KiB
TypeScript
107 lines
4.1 KiB
TypeScript
import path from 'path';
|
|
|
|
export interface LoadedNode {
|
|
packageName: string;
|
|
nodeName: string;
|
|
NodeClass: any;
|
|
}
|
|
|
|
export class N8nNodeLoader {
|
|
private readonly CORE_PACKAGES = [
|
|
{ name: 'n8n-nodes-base', path: 'n8n-nodes-base' },
|
|
{ name: '@n8n/n8n-nodes-langchain', path: '@n8n/n8n-nodes-langchain' }
|
|
];
|
|
|
|
async loadAllNodes(): Promise<LoadedNode[]> {
|
|
const results: LoadedNode[] = [];
|
|
|
|
for (const pkg of this.CORE_PACKAGES) {
|
|
try {
|
|
console.log(`\n📦 Loading package: ${pkg.name} from ${pkg.path}`);
|
|
// Use the path property to locate the package
|
|
const packageJson = require(`${pkg.path}/package.json`);
|
|
console.log(` Found ${Object.keys(packageJson.n8n?.nodes || {}).length} nodes in package.json`);
|
|
const nodes = await this.loadPackageNodes(pkg.name, pkg.path, packageJson);
|
|
results.push(...nodes);
|
|
} catch (error) {
|
|
console.error(`Failed to load ${pkg.name}:`, error);
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Resolve the absolute directory of an installed package.
|
|
* Uses require.resolve on package.json (always exported) and strips the filename.
|
|
*/
|
|
private resolvePackageDir(packagePath: string): string {
|
|
const pkgJsonPath = require.resolve(`${packagePath}/package.json`);
|
|
return path.dirname(pkgJsonPath);
|
|
}
|
|
|
|
/**
|
|
* Load a node module by absolute file path, bypassing package.json "exports".
|
|
* Some packages (e.g. @n8n/n8n-nodes-langchain >=2.9) restrict exports but
|
|
* still list node files in the n8n.nodes array — we need direct filesystem access.
|
|
*/
|
|
private loadNodeModule(absolutePath: string): any {
|
|
return require(absolutePath);
|
|
}
|
|
|
|
private async loadPackageNodes(packageName: string, packagePath: string, packageJson: any): Promise<LoadedNode[]> {
|
|
const n8nConfig = packageJson.n8n || {};
|
|
const nodes: LoadedNode[] = [];
|
|
const packageDir = this.resolvePackageDir(packagePath);
|
|
|
|
// Check if nodes is an array or object
|
|
const nodesList = n8nConfig.nodes || [];
|
|
|
|
if (Array.isArray(nodesList)) {
|
|
// Handle array format (n8n-nodes-base uses this)
|
|
for (const nodePath of nodesList) {
|
|
try {
|
|
// Resolve absolute path directly to bypass package exports restrictions
|
|
const fullPath = path.join(packageDir, nodePath);
|
|
const nodeModule = this.loadNodeModule(fullPath);
|
|
|
|
// Extract node name from path (e.g., "dist/nodes/Slack/Slack.node.js" -> "Slack")
|
|
const nodeNameMatch = nodePath.match(/\/([^\/]+)\.node\.(js|ts)$/);
|
|
const nodeName = nodeNameMatch ? nodeNameMatch[1] : path.basename(nodePath, '.node.js');
|
|
|
|
// Handle default export and various export patterns
|
|
const NodeClass = nodeModule.default || nodeModule[nodeName] || Object.values(nodeModule)[0];
|
|
if (NodeClass) {
|
|
nodes.push({ packageName, nodeName, NodeClass });
|
|
console.log(` ✓ Loaded ${nodeName} from ${packageName}`);
|
|
} else {
|
|
console.warn(` ⚠ No valid export found for ${nodeName} in ${packageName}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(` ✗ Failed to load node from ${packageName}/${nodePath}:`, (error as Error).message);
|
|
}
|
|
}
|
|
} else {
|
|
// Handle object format (for other packages)
|
|
for (const [nodeName, nodePath] of Object.entries(nodesList)) {
|
|
try {
|
|
const fullPath = path.join(packageDir, nodePath as string);
|
|
const nodeModule = this.loadNodeModule(fullPath);
|
|
|
|
// Handle default export and various export patterns
|
|
const NodeClass = nodeModule.default || nodeModule[nodeName] || Object.values(nodeModule)[0];
|
|
if (NodeClass) {
|
|
nodes.push({ packageName, nodeName, NodeClass });
|
|
console.log(` ✓ Loaded ${nodeName} from ${packageName}`);
|
|
} else {
|
|
console.warn(` ⚠ No valid export found for ${nodeName} in ${packageName}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(` ✗ Failed to load node ${nodeName} from ${packageName}:`, (error as Error).message);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
} |