fix(core): Report success from mcp tool if workflow is created in DB (no-changelog) (#28529)

This commit is contained in:
Milorad FIlipović 2026-04-20 10:48:32 +02:00 committed by GitHub
parent b2fdcf16c0
commit 0fc2d90b52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 5 deletions

View file

@ -9,6 +9,7 @@ import { NodeTypes } from '@/node-types';
import { UrlService } from '@/services/url.service';
import { Telemetry } from '@/telemetry';
import { WorkflowCreationService } from '@/workflows/workflow-creation.service';
import { WorkflowFinderService } from '@/workflows/workflow-finder.service';
// Mock dynamic imports
const mockParseAndValidate = jest.fn();
@ -100,11 +101,15 @@ describe('create-workflow-from-code MCP tool', () => {
const projectRepository = mockInstance(ProjectRepository, {
getPersonalProjectForUserOrFail: jest.fn().mockResolvedValue({ id: 'personal-project-1' }),
});
const workflowFinderService = mockInstance(WorkflowFinderService, {
findWorkflowForUser: jest.fn().mockResolvedValue(null),
});
const createTool = () =>
createCreateWorkflowFromCodeTool(
user,
workflowCreationService,
workflowFinderService,
urlService,
telemetry,
nodeTypes,

View file

@ -320,6 +320,7 @@ export class McpService {
const createTool = createCreateWorkflowFromCodeTool(
user,
this.workflowCreationService,
this.workflowFinderService,
this.urlService,
this.telemetry,
this.nodeTypes,

View file

@ -1,4 +1,5 @@
import { type User, type ProjectRepository, WorkflowEntity } from '@n8n/db';
import { layoutWorkflowJSON } from '@n8n/workflow-sdk';
import z from 'zod';
import { MCP_CREATE_WORKFLOW_FROM_CODE_TOOL, CODE_BUILDER_VALIDATE_TOOL } from './constants';
@ -13,6 +14,7 @@ import type { UrlService } from '@/services/url.service';
import type { Telemetry } from '@/telemetry';
import { resolveNodeWebhookIds } from '@/workflow-helpers';
import type { WorkflowCreationService } from '@/workflows/workflow-creation.service';
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
const inputSchema = {
code: z
@ -80,6 +82,7 @@ const outputSchema = {
export const createCreateWorkflowFromCodeTool = (
user: User,
workflowCreationService: WorkflowCreationService,
workflowFinderService: WorkflowFinderService,
urlService: UrlService,
telemetry: Telemetry,
nodeTypes: NodeTypes,
@ -134,6 +137,8 @@ export const createCreateWorkflowFromCodeTool = (
};
}
let newWorkflow: WorkflowEntity | undefined;
try {
const { ParseValidateHandler, stripImportStatements } = await import(
'@n8n/ai-workflow-builder'
@ -142,9 +147,10 @@ export const createCreateWorkflowFromCodeTool = (
const handler = new ParseValidateHandler({ generatePinData: false });
const strippedCode = stripImportStatements(code);
const result = await handler.parseAndValidate(strippedCode);
const workflowJson = result.workflow;
const newWorkflow = new WorkflowEntity();
const workflowJson = layoutWorkflowJSON(result.workflow);
newWorkflow = new WorkflowEntity();
Object.assign(newWorkflow, {
name: name ?? workflowJson.name ?? 'Untitled Workflow',
...(description ? { description } : {}),
@ -152,7 +158,7 @@ export const createCreateWorkflowFromCodeTool = (
connections: workflowJson.connections,
settings: { ...workflowJson.settings, executionOrder: 'v1', availableInMCP: true },
pinData: workflowJson.pinData,
meta: { ...workflowJson.meta, aiBuilderAssisted: true },
meta: { ...workflowJson.meta, aiBuilderAssisted: true, builderVariant: 'mcp' },
});
resolveNodeWebhookIds(newWorkflow, nodeTypes);
@ -210,6 +216,51 @@ export const createCreateWorkflowFromCodeTool = (
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
// Check whether the workflow was actually persisted despite the error.
// TypeORM sets the entity id during save(), even inside a transaction that
// may later roll back, so newWorkflow.id alone is not a reliable signal.
// A DB lookup confirms the row truly exists before we report success.
if (newWorkflow?.id) {
let persisted: Awaited<ReturnType<WorkflowFinderService['findWorkflowForUser']>> | null =
null;
try {
persisted = await workflowFinderService.findWorkflowForUser(newWorkflow.id, user, [
'workflow:read',
]);
} catch {
// Verification lookup failed — fall through and report the original error.
}
if (persisted) {
const baseUrl = urlService.getInstanceBaseUrl();
const workflowUrl = `${baseUrl}/workflow/${persisted.id}`;
telemetryPayload.results = {
success: true,
data: {
workflowId: persisted.id,
nodeCount: persisted.nodes.length,
postSaveError: errorMessage,
},
};
telemetry.track(USER_CALLED_MCP_TOOL_EVENT, telemetryPayload);
const output = {
workflowId: persisted.id,
name: persisted.name,
nodeCount: persisted.nodes.length,
url: workflowUrl,
autoAssignedCredentials: [],
note: `Workflow was created successfully, but a post-save operation failed: ${errorMessage}`,
};
return {
content: [{ type: 'text', text: JSON.stringify(output, null, 2) }],
structuredContent: output,
};
}
}
telemetryPayload.results = {
success: false,
error: errorMessage,

View file

@ -1,4 +1,5 @@
import { type User, type SharedWorkflowRepository, WorkflowEntity } from '@n8n/db';
import { layoutWorkflowJSON } from '@n8n/workflow-sdk';
import z from 'zod';
import { USER_CALLED_MCP_TOOL_EVENT } from '../../mcp.constants';
@ -125,7 +126,8 @@ export const createUpdateWorkflowTool = (
const handler = new ParseValidateHandler({ generatePinData: false });
const strippedCode = stripImportStatements(code);
const result = await handler.parseAndValidate(strippedCode);
const workflowJson = result.workflow;
const workflowJson = layoutWorkflowJSON(result.workflow);
const workflowUpdateData = new WorkflowEntity();
Object.assign(workflowUpdateData, {
@ -134,7 +136,7 @@ export const createUpdateWorkflowTool = (
nodes: workflowJson.nodes,
connections: workflowJson.connections,
pinData: workflowJson.pinData,
meta: { ...workflowJson.meta, aiBuilderAssisted: true },
meta: { ...workflowJson.meta, aiBuilderAssisted: true, builderVariant: 'mcp' },
});
resolveNodeWebhookIds(workflowUpdateData, nodeTypes);