UNPKG

n8n

Version:

n8n Workflow Automation Tool

136 lines (133 loc) 6.67 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createGetWorkflowBestPracticesTool = void 0; const best_practices_1 = require("@n8n/workflow-sdk/prompts/best-practices"); const zod_1 = __importDefault(require("zod")); const constants_1 = require("./constants"); const mcp_constants_1 = require("../../mcp.constants"); const LIST_SENTINEL = 'list'; const GROUPING_GUIDANCE = `## Grouping Organise larger workflows into named node groups — visual frames drawn on the canvas — so the result is readable the first time the user sees it. - **When to group:** larger workflows that split into clear stages (e.g. ingest → transform → deliver). Give each stage its own group. Small workflows don't need groups — a group there is just noise. - **Groups vs sub-workflows:** a group is cosmetic organisation *inside* one workflow; a sub-workflow is a separately-executed, reusable unit. Group to make one canvas readable; extract a sub-workflow to reuse logic or isolate execution. - **Naming:** short, outcome-first titles ("Fetch new recordings", not "HTTP + Drive"). - Groups are created collapsed by default, so the name is what the user sees first — make it descriptive. Fetch the "groups" section of the SDK reference for the exact rules before creating groups.`; const inputSchema = { technique: zod_1.default .union([zod_1.default.nativeEnum(best_practices_1.WorkflowTechnique), zod_1.default.literal(LIST_SENTINEL)]) .describe(`Workflow technique key (e.g. "chatbot", "scheduling", "triage") to fetch best-practices guidance for. Pass "${LIST_SENTINEL}" to discover all available techniques.`), }; const availableTechniqueSchema = zod_1.default.object({ technique: zod_1.default.string(), description: zod_1.default.string(), hasDocumentation: zod_1.default.boolean(), }); const outputSchema = { technique: zod_1.default .string() .describe('The requested technique key, or "list" when listing all available techniques.'), message: zod_1.default.string().describe('Human-readable summary of the response.'), documentation: zod_1.default .string() .optional() .describe('Best-practices documentation for the requested technique, when one with documentation was requested.'), availableTechniques: zod_1.default .array(availableTechniqueSchema) .optional() .describe('All available techniques, returned when "list" was requested.'), }; function buildListResponse(canvasGroupsEnabled) { const availableTechniques = Object.entries(best_practices_1.TechniqueDescription).map(([key, description]) => ({ technique: key, description, hasDocumentation: best_practices_1.bestPracticesRegistry[key] !== undefined, })); const documentedCount = availableTechniques.filter((t) => t.hasDocumentation).length; const message = `Found ${availableTechniques.length} workflow techniques. ${documentedCount} have detailed best-practices documentation. Call this tool again with a specific technique key to fetch its guidance.`; const text = [ message, '', ...availableTechniques.map((t) => `- ${t.technique}${t.hasDocumentation ? '' : ' (no detailed documentation yet)'}${t.description}`), ...(canvasGroupsEnabled ? ['', GROUPING_GUIDANCE] : []), ].join('\n'); return { text, hasDocumentation: false, structured: { technique: LIST_SENTINEL, message, availableTechniques, }, }; } function buildTechniqueResponse(technique) { const doc = best_practices_1.bestPracticesRegistry[technique]; if (doc) { const documentation = doc.getDocumentation(); return { text: documentation, hasDocumentation: true, structured: { technique, message: `Best-practices documentation for "${technique}" retrieved.`, documentation, }, }; } const description = best_practices_1.TechniqueDescription[technique]; const message = `Technique "${technique}" (${description}) does not have detailed best-practices documentation yet — proceed with general n8n knowledge.`; return { text: message, hasDocumentation: false, structured: { technique, message } }; } const createGetWorkflowBestPracticesTool = (user, telemetry, { canvasGroupsEnabled }) => ({ name: constants_1.MCP_GET_WORKFLOW_BEST_PRACTICES_TOOL.toolName, config: { description: 'Required workflow-planning step. Get best-practices guidance (recommended nodes, patterns, and common pitfalls) for a specific workflow technique before searching for nodes or writing code. Call once per relevant technique. Use technique="list" first if unsure which techniques apply.', inputSchema, outputSchema, annotations: { title: constants_1.MCP_GET_WORKFLOW_BEST_PRACTICES_TOOL.displayTitle, readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, }, handler: async ({ technique }) => { const telemetryPayload = { user_id: user.id, tool_name: constants_1.MCP_GET_WORKFLOW_BEST_PRACTICES_TOOL.toolName, parameters: { technique }, }; try { const response = technique === LIST_SENTINEL ? buildListResponse(canvasGroupsEnabled) : buildTechniqueResponse(technique); telemetryPayload.results = { success: true, data: { technique, hasDocumentation: response.hasDocumentation, }, }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); return { content: [{ type: 'text', text: response.text }], structuredContent: response.structured, }; } catch (error) { telemetryPayload.results = { success: false, error: error instanceof Error ? error.message : String(error), }; telemetry.track(mcp_constants_1.USER_CALLED_MCP_TOOL_EVENT, telemetryPayload); throw error; } }, }); exports.createGetWorkflowBestPracticesTool = createGetWorkflowBestPracticesTool; //# sourceMappingURL=get-workflow-best-practices.tool.js.map