@n8n-plus/n8n-plus
Version:
n8n Workflow Automation Tool (plus edition)
224 lines • 9.66 kB
JavaScript
"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