UNPKG

@n8n-plus/n8n-plus

Version:

n8n Workflow Automation Tool (plus edition)

224 lines 9.66 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.McpRegistryService = void 0; const backend_common_1 = require("@n8n/backend-common"); const constants_1 = require("@n8n/constants"); const decorators_1 = require("@n8n/decorators"); const di_1 = require("@n8n/di"); const n8n_core_1 = require("n8n-core"); const load_nodes_and_credentials_1 = require("../../../load-nodes-and-credentials"); const push_1 = require("../../../push"); const publisher_service_1 = require("../../../scaling/pubsub/publisher.service"); const mcp_registry_server_repository_1 = require("./mcp-registry-server.repository"); const mcp_registry_node_loader_1 = require("../mcp-registry-node-loader"); const mcp_registry_api_client_1 = require("./mcp-registry-api.client"); const mcp_registry_types_1 = require("./mcp-registry.types"); const node_description_transform_1 = require("../node-description-transform"); const REFRESH_INTERVAL_HOURS = 8; const REFRESH_INTERVAL_MS = REFRESH_INTERVAL_HOURS * constants_1.Time.hours.toMilliseconds; let McpRegistryService = class McpRegistryService { constructor(logger, repository, apiClient, instanceSettings, loadNodesAndCredentials, push, publisher) { this.logger = logger; this.repository = repository; this.apiClient = apiClient; this.instanceSettings = instanceSettings; this.loadNodesAndCredentials = loadNodesAndCredentials; this.push = push; this.publisher = publisher; this.isShuttingDown = false; this.logger = logger.scoped('mcp-registry'); } async init() { await this.refreshRegistryNodeTypes(false); if (this.instanceSettings.isLeader) { void this.refreshFromApi('startup'); this.startPeriodicRefresh(); } } async onLeaderTakeover() { await this.refreshFromApi('leader-takeover'); this.startPeriodicRefresh(); } onLeaderStepdown() { this.stopPeriodicRefresh(); } shutdown() { this.isShuttingDown = true; this.stopPeriodicRefresh(); } async handleReloadMcpRegistry() { await this.refreshRegistryNodeTypes(true); if (this.isMainInstance()) { this.notifyNodeDescriptionsUpdated(); } } async getAll({ includeDeprecated = false, } = {}) { const entities = includeDeprecated ? await this.repository.find() : await this.repository.findBy({ status: 'active' }); return entities.map(mcp_registry_types_1.fromEntity); } async get(slug) { const entity = await this.repository.findOneBy({ slug }); return entity ? (0, mcp_registry_types_1.fromEntity)(entity) : undefined; } startPeriodicRefresh() { if (this.isShuttingDown || this.refreshInterval) { return; } this.refreshInterval = setInterval(() => { void this.refreshFromApi('interval'); }, REFRESH_INTERVAL_MS); this.logger.debug('Scheduled MCP registry refresh', { intervalHours: REFRESH_INTERVAL_HOURS, }); } stopPeriodicRefresh() { clearInterval(this.refreshInterval); this.refreshInterval = undefined; } async refreshFromApi(reason) { if (this.refreshPromise) { await this.refreshPromise; return; } this.refreshPromise = this.refreshFromApiInternal(reason); try { await this.refreshPromise; } finally { this.refreshPromise = undefined; } } async refreshFromApiInternal(reason) { try { const existingServers = await this.getAll({ includeDeprecated: true }); let updatedServers; if (existingServers.length === 0) { updatedServers = await this.apiClient.fetchAllServers(); } else { const result = await this.refreshUpdatedServers(existingServers); if (result === null) { this.logger.debug('MCP registry is up to date', { reason }); return; } updatedServers = result; } await this.saveServers(updatedServers); await this.refreshRegistryNodeTypes(true); this.notifyNodeDescriptionsUpdated(); await this.publishReloadCommand(); this.logger.debug('MCP registry refreshed', { reason, serverCount: updatedServers.length, }); } catch (error) { this.logger.error('Failed to refresh MCP registry', { error, reason }); } } async refreshUpdatedServers(existingServers) { const now = new Date().toISOString(); const metadata = await this.apiClient.fetchServersMetadata(); const existingBySlug = new Map(existingServers.map((server) => [server.slug, server])); const metadataSlugs = new Set(metadata.map(({ slug }) => slug)); const slugsToFetch = metadata .filter((entry) => this.shouldFetchFullServer(entry, existingBySlug.get(entry.slug))) .map(({ slug }) => slug); const serversToDeprecate = existingServers .filter((server) => !metadataSlugs.has(server.slug) && server.status !== 'deprecated') .map((server) => ({ ...server, status: 'deprecated', updatedAt: now })); if (slugsToFetch.length === 0 && serversToDeprecate.length === 0) { return null; } if (slugsToFetch.length === 0) { return serversToDeprecate; } const updatedServers = await this.apiClient.fetchServersBySlugs(slugsToFetch); return [...updatedServers, ...serversToDeprecate]; } shouldFetchFullServer(metadata, existing) { return (!existing || existing.version !== metadata.version || existing.updatedAt !== metadata.updatedAt); } async saveServers(servers) { const entities = servers.map(mcp_registry_types_1.toEntity); await this.repository.upsert(entities, ['slug']); } async refreshRegistryNodeTypes(releaseTypes) { const loader = this.loadNodesAndCredentials.loaders[node_description_transform_1.MCP_REGISTRY_PACKAGE_NAME]; if (!loader) { return; } if (!(loader instanceof mcp_registry_node_loader_1.McpRegistryNodeLoader)) { this.logger.warn('Unexpected MCP registry loader instance type', { loaderType: loader.constructor.name, }); return; } const servers = await this.getAll({ includeDeprecated: true }); loader.setServers(servers); await loader.loadAll(); await this.loadNodesAndCredentials.postProcessLoaders(); if (releaseTypes) { this.loadNodesAndCredentials.releaseTypes(); } this.logger.debug('MCP registry loader done', { serverCount: servers.length }); } async publishReloadCommand() { await this.publisher.publishCommand({ command: 'reload-mcp-registry' }); } notifyNodeDescriptionsUpdated() { this.push.broadcast({ type: 'nodeDescriptionUpdated', data: {} }); } isMainInstance() { return this.instanceSettings.instanceType === 'main'; } }; exports.McpRegistryService = McpRegistryService; __decorate([ (0, decorators_1.OnLeaderTakeover)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], McpRegistryService.prototype, "onLeaderTakeover", null); __decorate([ (0, decorators_1.OnLeaderStepdown)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], McpRegistryService.prototype, "onLeaderStepdown", null); __decorate([ (0, decorators_1.OnShutdown)(), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], McpRegistryService.prototype, "shutdown", null); __decorate([ (0, decorators_1.OnPubSubEvent)('reload-mcp-registry'), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], McpRegistryService.prototype, "handleReloadMcpRegistry", null); exports.McpRegistryService = McpRegistryService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [backend_common_1.Logger, mcp_registry_server_repository_1.McpRegistryServerRepository, mcp_registry_api_client_1.McpRegistryApiClient, n8n_core_1.InstanceSettings, load_nodes_and_credentials_1.LoadNodesAndCredentials, push_1.Push, publisher_service_1.Publisher]) ], McpRegistryService); //# sourceMappingURL=mcp-registry.service.js.map