n8n
Version:
n8n Workflow Automation Tool
206 lines • 10.3 kB
JavaScript
;
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