UNPKG

n8n

Version:

n8n Workflow Automation Tool

206 lines 10.3 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.McpSettingsService = void 0; const backend_common_1 = require("@n8n/backend-common"); const config_1 = require("@n8n/config"); const db_1 = require("@n8n/db"); const di_1 = require("@n8n/di"); const typeorm_1 = require("@n8n/typeorm"); const n8n_workflow_1 = require("n8n-workflow"); const collaboration_service_1 = require("../../collaboration/collaboration.service"); const bad_request_error_1 = require("../../errors/response-errors/bad-request.error"); const cache_service_1 = require("../../services/cache/cache.service"); const workflow_helpers_1 = require("../../workflow-helpers"); const workflow_finder_service_1 = require("../../workflows/workflow-finder.service"); const KEY = 'mcp.access.enabled'; const BULK_CHUNK_SIZE = 500; const WORKFLOW_SETTINGS_FIELDS = ['id', 'settings']; let McpSettingsService = class McpSettingsService { constructor(settingsRepository, cacheService, workflowRepository, workflowFinderService, globalConfig, logger, collaborationService) { this.settingsRepository = settingsRepository; this.cacheService = cacheService; this.workflowRepository = workflowRepository; this.workflowFinderService = workflowFinderService; this.globalConfig = globalConfig; this.logger = logger; this.collaborationService = collaborationService; } async getEnabled() { const isMcpAccessEnabled = await this.cacheService.get(KEY); if (isMcpAccessEnabled !== undefined) { return isMcpAccessEnabled === 'true'; } const row = await this.settingsRepository.findByKey(KEY); const enabled = row?.value === 'true'; await this.cacheService.set(KEY, enabled.toString()); return enabled; } async setEnabled(enabled) { await this.settingsRepository.upsert({ key: KEY, value: enabled.toString(), loadOnStartup: true }, ['key']); await this.cacheService.set(KEY, enabled.toString()); } async bulkSetAvailableInMCP(user, dto) { const { availableInMCP, workflowIds, projectId, folderId } = dto; const scopeCount = [workflowIds, projectId, folderId].filter(Boolean).length; if (scopeCount !== 1) { throw new bad_request_error_1.BadRequestError('Provide exactly one of workflowIds, projectId or folderId'); } const candidateIds = await this.resolveCandidateIds(user, { workflowIds, projectId, folderId, }); const isWorkflowIdsScope = Boolean(workflowIds); const baselineSize = isWorkflowIdsScope ? new Set(workflowIds).size : candidateIds.length; if (candidateIds.length === 0) { return { updatedCount: 0, skippedCount: baselineSize, failedCount: 0, changedWorkflows: [], ...(isWorkflowIdsScope ? { updatedIds: [] } : {}), }; } const writtenIds = []; const changedWorkflows = []; const noOpIds = []; let failedCount = 0; for (let start = 0; start < candidateIds.length; start += BULK_CHUNK_SIZE) { const chunk = candidateIds.slice(start, start + BULK_CHUNK_SIZE); try { const chunkResult = await this.workflowRepository.manager.transaction(async (trx) => { const chunkWritten = []; const chunkNoOp = []; const now = new Date(); const settingsRows = await trx.find(db_1.WorkflowEntity, { where: { id: (0, typeorm_1.In)(chunk), isArchived: false }, select: WORKFLOW_SETTINGS_FIELDS, }); const nextSettingsByWorkflowId = new Map(); for (const row of settingsRows) { if (row.settings?.availableInMCP === availableInMCP) { chunkNoOp.push(row.id); continue; } const nextSettings = (0, workflow_helpers_1.removeDefaultValues)({ ...(row.settings ?? {}), availableInMCP }, this.globalConfig.executions.timeout); nextSettingsByWorkflowId.set(row.id, nextSettings); } if (nextSettingsByWorkflowId.size === 0) { return { written: chunkWritten, noOp: chunkNoOp }; } const rows = await trx.find(db_1.WorkflowEntity, { where: { id: (0, typeorm_1.In)([...nextSettingsByWorkflowId.keys()]), isArchived: false }, select: ['id', ...n8n_workflow_1.WORKFLOW_CHECKSUM_FIELDS], }); for (const row of rows) { const nextSettings = nextSettingsByWorkflowId.get(row.id); if (nextSettings === undefined) continue; await trx.update(db_1.WorkflowEntity, { id: row.id }, { settings: nextSettings, updatedAt: now }); const checksum = await (0, n8n_workflow_1.calculateWorkflowChecksum)({ ...row, settings: nextSettings, }); chunkWritten.push({ workflowId: row.id, settings: { availableInMCP }, checksum, }); } return { written: chunkWritten, noOp: chunkNoOp }; }); writtenIds.push(...chunkResult.written.map(({ workflowId }) => workflowId)); changedWorkflows.push(...chunkResult.written); noOpIds.push(...chunkResult.noOp); } catch (error) { failedCount += chunk.length; this.logger.error('Failed to bulk-update workflow MCP availability for chunk', { error, chunkSize: chunk.length, chunkStart: start, availableInMCP, }); } } const confirmedIds = [...writtenIds, ...noOpIds]; return { updatedCount: confirmedIds.length, skippedCount: Math.max(0, baselineSize - confirmedIds.length - failedCount), failedCount, changedWorkflows, ...(isWorkflowIdsScope ? { updatedIds: confirmedIds } : {}), }; } async broadcastWorkflowMCPAvailabilityChanged(changes) { if (changes.length === 0) return; const workflowIds = changes.map(({ workflowId }) => workflowId); let openWorkflowIds; try { openWorkflowIds = await this.collaborationService.filterOpenWorkflowIds(workflowIds); } catch (error) { this.logger.warn('Failed to resolve open workflows for settings update broadcast', { workflowCount: changes.length, workflowIds: workflowIds.slice(0, 10), cause: error instanceof Error ? error.message : String(error), }); return; } if (openWorkflowIds.length === 0) return; const changesByWorkflowId = new Map(changes.map((change) => [change.workflowId, change])); await Promise.all(openWorkflowIds.map(async (workflowId) => { try { const change = changesByWorkflowId.get(workflowId); if (!change) return; await this.collaborationService.broadcastWorkflowSettingsUpdated(workflowId, change.settings, change.checksum); } catch (error) { this.logger.warn('Failed to broadcast workflow settings update', { workflowId, cause: error instanceof Error ? error.message : String(error), }); } })); } async resolveCandidateIds(user, scope) { if (scope.workflowIds) { const uniqueIds = [...new Set(scope.workflowIds)]; const accessibleIds = await this.workflowFinderService.findWorkflowIdsWithScopeForUser(uniqueIds, user, ['workflow:update']); return uniqueIds.filter((id) => accessibleIds.has(id)); } const projectId = scope.projectId ?? (scope.folderId ? await this.workflowFinderService.findProjectIdForFolder(scope.folderId) : null); if (projectId === null || !(await this.workflowFinderService.hasProjectScopeForUser(user, ['workflow:update'], projectId))) { return []; } return await this.workflowFinderService.findAllWorkflowIdsForUser(user, ['workflow:update'], scope.folderId, projectId); } }; exports.McpSettingsService = McpSettingsService; exports.McpSettingsService = McpSettingsService = __decorate([ (0, di_1.Service)(), __metadata("design:paramtypes", [db_1.SettingsRepository, cache_service_1.CacheService, db_1.WorkflowRepository, workflow_finder_service_1.WorkflowFinderService, config_1.GlobalConfig, backend_common_1.Logger, collaboration_service_1.CollaborationService]) ], McpSettingsService); //# sourceMappingURL=mcp.settings.service.js.map