n8n
Version:
n8n Workflow Automation Tool
165 lines • 8.06 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createPrepareTestPinDataTool = void 0;
exports.preparePinData = preparePinData;
const workflow_sdk_1 = require("@n8n/workflow-sdk");
const n8n_workflow_1 = require("n8n-workflow");
const zod_1 = __importDefault(require("zod"));
const mcp_constants_1 = require("../mcp.constants");
const workflow_validation_utils_1 = require("./workflow-validation.utils");
const inputSchema = zod_1.default.object({
workflowId: zod_1.default.string().describe('The ID of the workflow to generate test pin data for'),
});
const outputSchema = {
nodeSchemasToGenerate: zod_1.default
.record(zod_1.default.record(zod_1.default.unknown()))
.describe('Nodes that need pin data generated by you. Keys are node names, values are JSON Schema objects describing the expected output shape. Schemas may come from node type definitions or inferred from past execution output shapes. Generate realistic sample data matching each schema, wrap each item as {"json": {...}}, and collect into a pinData object to pass to test_workflow.'),
nodesWithoutSchema: zod_1.default
.array(zod_1.default.string())
.describe('Node names that need pin data but have no output schema. Generate a single item [{"json": {}}] for each, and merge into pinData before passing to test_workflow.'),
nodesSkipped: zod_1.default
.array(zod_1.default.string())
.describe('Nodes that do not need pin data and will execute normally during the test (e.g. Set, If, Code, Merge).'),
coverage: zod_1.default.object({
withSchemaFromExecution: zod_1.default
.number()
.describe('Number of nodes with schemas inferred from last successful execution output'),
withSchemaFromDefinition: zod_1.default
.number()
.describe('Number of nodes with schemas from node type definitions'),
withoutSchema: zod_1.default
.number()
.describe('Number of nodes with no data or schema — use empty defaults'),
skipped: zod_1.default.number().describe('Number of nodes that will execute normally (no pin data needed)'),
total: zod_1.default.number().describe('Total number of enabled nodes'),
}),
};
const createPrepareTestPinDataTool = (user, workflowFinderService, executionService, nodeTypes, telemetry, logger) => ({
name: 'prepare_test_pin_data',
config: {
description: 'Prepare test pin data for a workflow. Trigger nodes, nodes with credentials, and HTTP Request nodes need pin data. Logic nodes (Set, If, Code, etc.) and credential-free I/O nodes (Execute Command, file read/write) execute normally without pin data. Returns JSON Schemas describing the expected output shape for each node that needs pin data — schemas are derived from past execution output shapes or node type definitions. No actual user data is returned. You should generate realistic sample data for the schemas, use empty defaults for nodes without schema, merge everything into a single pinData object, and pass it to test_workflow.',
inputSchema: inputSchema.shape,
outputSchema,
annotations: {
title: 'Prepare Test Pin Data',
readOnlyHint: true,
destructiveHint: false,
idempotentHint: true,
openWorldHint: false,
},
},
handler: async ({ workflowId }) => {
const telemetryPayload = {
user_id: user.id,
tool_name: 'prepare_test_pin_data',
parameters: { workflowId },
};
try {
const result = await preparePinData(workflowId, user, workflowFinderService, executionService, nodeTypes, logger);
telemetryPayload.results = {
success: true,
data: result.coverage,
};
telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload);
return {
content: [{ type: 'text', text: (0, n8n_workflow_1.jsonStringify)(result) }],
structuredContent: { ...result },
};
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
telemetryPayload.results = { success: false, error: errorMessage };
telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload);
return {
content: [{ type: 'text', text: (0, n8n_workflow_1.jsonStringify)({ error: errorMessage }) }],
structuredContent: { error: errorMessage },
isError: true,
};
}
},
});
exports.createPrepareTestPinDataTool = createPrepareTestPinDataTool;
async function preparePinData(workflowId, user, workflowFinderService, executionService, nodeTypes, logger) {
const workflow = await (0, workflow_validation_utils_1.getMcpWorkflow)(workflowId, user, ['workflow:read'], workflowFinderService);
const enabledNodes = (workflow.nodes ?? []).filter((n) => !n.disabled);
const isTriggerNodeFn = (node) => {
try {
const nodeType = nodeTypes.getByNameAndVersion(node.type, node.typeVersion);
return (0, n8n_workflow_1.isTriggerNode)(nodeType.description);
}
catch {
return true;
}
};
const executionRunData = await getExecutionRunData(workflowId, user, executionService, logger);
const executionSchemas = executionRunData ? (0, workflow_sdk_1.inferSchemasFromRunData)(executionRunData) : {};
const nodeSchemasToGenerate = {};
const nodesWithoutSchema = [];
const nodesSkipped = [];
let withSchemaFromExecution = 0;
let withSchemaFromDefinition = 0;
let withoutSchema = 0;
let skipped = 0;
for (const node of enabledNodes) {
if (!(0, workflow_sdk_1.needsPinData)(node, isTriggerNodeFn)) {
nodesSkipped.push(node.name);
skipped++;
continue;
}
const execSchema = executionSchemas[node.name];
if (execSchema) {
nodeSchemasToGenerate[node.name] = execSchema;
withSchemaFromExecution++;
continue;
}
const schema = (0, workflow_sdk_1.discoverOutputSchemaForNode)(node.type, node.typeVersion, {
resource: node.parameters?.resource,
operation: node.parameters?.operation,
});
if (schema) {
nodeSchemasToGenerate[node.name] = schema;
withSchemaFromDefinition++;
continue;
}
nodesWithoutSchema.push(node.name);
withoutSchema++;
}
return {
nodeSchemasToGenerate,
nodesWithoutSchema,
nodesSkipped,
coverage: {
withSchemaFromExecution,
withSchemaFromDefinition,
withoutSchema,
skipped,
total: enabledNodes.length,
},
};
}
async function getExecutionRunData(workflowId, user, executionService, logger) {
try {
const execution = await executionService.getLastSuccessfulExecution(workflowId, user);
if (!execution?.data?.resultData?.runData)
return undefined;
const result = {};
for (const [nodeName, taskDataArray] of Object.entries(execution.data.resultData.runData)) {
const firstRun = taskDataArray[0];
if (firstRun?.data?.main?.[0]) {
result[nodeName] = firstRun.data.main[0];
}
}
return Object.keys(result).length > 0 ? result : undefined;
}
catch (error) {
logger.debug('Failed to fetch execution data for pin data generation', {
workflowId,
error: error instanceof Error ? error.message : String(error),
});
return undefined;
}
}
//# sourceMappingURL=prepare-workflow-pin-data.tool.js.map