n8n-mcp
Version:
Integration between n8n workflow automation and Model Context Protocol (MCP)
217 lines • 9.78 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommunityNodeService = void 0;
const logger_1 = require("../utils/logger");
const typeversion_1 = require("../utils/typeversion");
const community_node_fetcher_1 = require("./community-node-fetcher");
class CommunityNodeService {
constructor(repository, environment = 'production') {
this.repository = repository;
this.fetcher = new community_node_fetcher_1.CommunityNodeFetcher(environment);
}
async syncCommunityNodes(options = {}, progressCallback) {
const startTime = Date.now();
const result = {
verified: { fetched: 0, saved: 0, skipped: 0, errors: [] },
npm: { fetched: 0, saved: 0, skipped: 0, errors: [] },
duration: 0,
};
logger_1.logger.info('Syncing verified community nodes from Strapi API...');
try {
result.verified = await this.syncVerifiedNodes(progressCallback, options.skipExisting);
}
catch (error) {
logger_1.logger.error('Failed to sync verified nodes:', error);
result.verified.errors.push(`Strapi sync failed: ${error.message}`);
}
if (!options.verifiedOnly) {
const npmLimit = options.npmLimit ?? 100;
logger_1.logger.info(`Syncing top ${npmLimit} npm community packages...`);
try {
result.npm = await this.syncNpmNodes(npmLimit, progressCallback, options.skipExisting);
}
catch (error) {
logger_1.logger.error('Failed to sync npm nodes:', error);
result.npm.errors.push(`npm sync failed: ${error.message}`);
}
}
result.duration = Date.now() - startTime;
logger_1.logger.info(`Community node sync complete in ${(result.duration / 1000).toFixed(1)}s: ` +
`${result.verified.saved} verified, ${result.npm.saved} npm`);
return result;
}
async syncVerifiedNodes(progressCallback, skipExisting) {
const result = { fetched: 0, saved: 0, skipped: 0, errors: [] };
const strapiNodes = await this.fetcher.fetchVerifiedNodes(progressCallback);
result.fetched = strapiNodes.length;
if (strapiNodes.length === 0) {
logger_1.logger.warn('No verified nodes returned from Strapi API');
return result;
}
logger_1.logger.info(`Processing ${strapiNodes.length} verified community nodes...`);
for (const strapiNode of strapiNodes) {
try {
const { attributes } = strapiNode;
if (skipExisting && this.repository.hasNodeByNpmPackage(attributes.packageName)) {
result.skipped++;
continue;
}
const parsedNode = this.strapiNodeToParsedNode(strapiNode);
if (!parsedNode) {
result.errors.push(`Failed to parse: ${attributes.packageName}`);
continue;
}
this.repository.saveNode(parsedNode);
result.saved++;
if (progressCallback) {
progressCallback(`Saving verified nodes`, result.saved + result.skipped, strapiNodes.length);
}
}
catch (error) {
result.errors.push(`Error saving ${strapiNode.attributes.packageName}: ${error.message}`);
}
}
logger_1.logger.info(`Verified nodes: ${result.saved} saved, ${result.skipped} skipped`);
return result;
}
async syncNpmNodes(limit = 100, progressCallback, skipExisting) {
const result = { fetched: 0, saved: 0, skipped: 0, errors: [] };
const npmPackages = await this.fetcher.fetchNpmPackages(limit, progressCallback);
result.fetched = npmPackages.length;
if (npmPackages.length === 0) {
logger_1.logger.warn('No npm packages returned from registry');
return result;
}
const verifiedPackages = new Set(this.repository
.getCommunityNodes({ verified: true })
.map((n) => n.npmPackageName)
.filter(Boolean));
logger_1.logger.info(`Processing ${npmPackages.length} npm packages (skipping ${verifiedPackages.size} verified)...`);
for (const pkg of npmPackages) {
try {
const packageName = pkg.package.name;
if (verifiedPackages.has(packageName)) {
result.skipped++;
continue;
}
if (skipExisting && this.repository.hasNodeByNpmPackage(packageName)) {
result.skipped++;
continue;
}
const parsedNode = this.npmPackageToParsedNode(pkg);
this.repository.saveNode(parsedNode);
result.saved++;
if (progressCallback) {
progressCallback(`Saving npm packages`, result.saved + result.skipped, npmPackages.length);
}
}
catch (error) {
result.errors.push(`Error saving ${pkg.package.name}: ${error.message}`);
}
}
logger_1.logger.info(`npm packages: ${result.saved} saved, ${result.skipped} skipped`);
return result;
}
strapiNodeToParsedNode(strapiNode) {
const { attributes } = strapiNode;
const nodeDesc = attributes.nodeDescription;
if (!nodeDesc) {
logger_1.logger.warn(`No nodeDescription for ${attributes.packageName}`);
return null;
}
let nodeType = nodeDesc.name || `${attributes.packageName}.${attributes.name}`;
if (nodeType.includes('n8n-nodes-preview-')) {
nodeType = nodeType.replace('n8n-nodes-preview-', 'n8n-nodes-');
}
const isAITool = nodeDesc.usableAsTool === true ||
nodeDesc.codex?.categories?.includes('AI') ||
attributes.name?.toLowerCase().includes('ai');
return {
nodeType,
packageName: attributes.packageName,
displayName: nodeDesc.displayName || attributes.displayName,
description: nodeDesc.description || attributes.description,
category: nodeDesc.codex?.categories?.[0] || 'Community',
style: 'declarative',
properties: nodeDesc.properties || [],
credentials: nodeDesc.credentials || [],
operations: this.extractOperations(nodeDesc),
isAITool,
isTrigger: nodeDesc.group?.includes('trigger') || false,
isWebhook: nodeDesc.name?.toLowerCase().includes('webhook') ||
nodeDesc.group?.includes('webhook') ||
false,
isVersioned: (attributes.nodeVersions?.length || 0) > 1,
version: ((0, typeversion_1.parseTypeVersion)(nodeDesc.version) ?? 1).toString(),
outputs: nodeDesc.outputs,
outputNames: nodeDesc.outputNames,
isCommunity: true,
isVerified: true,
authorName: attributes.authorName,
authorGithubUrl: attributes.authorGithubUrl,
npmPackageName: attributes.packageName,
npmVersion: attributes.npmVersion,
npmDownloads: attributes.numberOfDownloads || 0,
communityFetchedAt: new Date().toISOString(),
};
}
npmPackageToParsedNode(pkg) {
const { package: pkgInfo, score } = pkg;
const nodeName = this.extractNodeNameFromPackage(pkgInfo.name);
const nodeType = `${pkgInfo.name}.${nodeName}`;
return {
nodeType,
packageName: pkgInfo.name,
displayName: nodeName,
description: pkgInfo.description || `Community node from ${pkgInfo.name}`,
category: 'Community',
style: 'declarative',
properties: [],
credentials: [],
operations: [],
isAITool: false,
isTrigger: pkgInfo.name.includes('trigger'),
isWebhook: pkgInfo.name.includes('webhook'),
isVersioned: false,
version: '1',
isCommunity: true,
isVerified: false,
authorName: pkgInfo.author?.name || pkgInfo.publisher?.username,
authorGithubUrl: pkgInfo.links?.repository,
npmPackageName: pkgInfo.name,
npmVersion: pkgInfo.version,
npmDownloads: Math.round(score.detail.popularity * 10000),
communityFetchedAt: new Date().toISOString(),
};
}
extractOperations(nodeDesc) {
const operations = [];
if (nodeDesc.properties) {
for (const prop of nodeDesc.properties) {
if ((prop.name === 'operation' || prop.name === 'action') && prop.options) {
const resource = prop.displayOptions?.show?.resource?.[0];
for (const op of prop.options) {
operations.push({
...op,
...(resource ? { resource } : {})
});
}
}
}
}
return operations;
}
extractNodeNameFromPackage(packageName) {
let name = packageName.replace(/^@[^/]+\//, '');
name = name.replace(/^n8n-nodes-/, '');
return name.replace(/-/g, '').toLowerCase();
}
getCommunityStats() {
return this.repository.getCommunityStats();
}
deleteCommunityNodes() {
return this.repository.deleteCommunityNodes();
}
}
exports.CommunityNodeService = CommunityNodeService;
//# sourceMappingURL=community-node-service.js.map