mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
fix(core): Report success from mcp tool if workflow is created in DB (no-changelog) (#28529)
This commit is contained in:
parent
b2fdcf16c0
commit
0fc2d90b52
4 changed files with 64 additions and 5 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -320,6 +320,7 @@ export class McpService {
|
|||
const createTool = createCreateWorkflowFromCodeTool(
|
||||
user,
|
||||
this.workflowCreationService,
|
||||
this.workflowFinderService,
|
||||
this.urlService,
|
||||
this.telemetry,
|
||||
this.nodeTypes,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue