mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
fix(core): Improve documentation usage in mcp tools (#30210)
This commit is contained in:
parent
b64a84159d
commit
e8827cd6e8
|
|
@ -294,6 +294,9 @@ describe('create-workflow-from-code MCP tool', () => {
|
|||
|
||||
const response = parseResult(result);
|
||||
expect(response.hint).toContain('sdk_ref');
|
||||
expect(response.hint).toContain('Workflow SDK reference');
|
||||
expect(response.hint).toContain('validate_workflow_code until it returns valid=true');
|
||||
expect(response.hint).toContain('create_workflow_from_code again');
|
||||
});
|
||||
|
||||
test('does not include SDK reference hint for non-parse errors', async () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import { User } from '@n8n/db';
|
||||
import {
|
||||
WORKFLOW_PATTERNS_DETAILED,
|
||||
WORKFLOW_SDK_PATTERNS,
|
||||
} from '@n8n/workflow-sdk/prompts/sdk-reference';
|
||||
|
||||
import { createGetWorkflowSdkReferenceTool } from '../tools/workflow-builder/get-workflow-sdk-reference.tool';
|
||||
import { getSdkReferenceContent } from '../tools/workflow-builder/sdk-reference-content';
|
||||
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
||||
jest.mock('@n8n/ai-workflow-builder', () => ({
|
||||
SDK_IMPORT_STATEMENT: "import { workflow } from '@n8n/workflow-sdk';",
|
||||
MCP_GET_SDK_REFERENCE_TOOL: {
|
||||
toolName: 'get_sdk_reference',
|
||||
displayTitle: 'Get SDK Reference',
|
||||
},
|
||||
}));
|
||||
|
||||
describe('get-workflow-sdk-reference MCP tool', () => {
|
||||
const user = Object.assign(new User(), { id: 'user-1' });
|
||||
let telemetry: Telemetry;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
telemetry = mockInstance(Telemetry, { track: jest.fn() });
|
||||
});
|
||||
|
||||
test('returns canonical workflow SDK patterns', () => {
|
||||
const content = getSdkReferenceContent('patterns');
|
||||
|
||||
expect(content).toContain(WORKFLOW_SDK_PATTERNS);
|
||||
expect(content).toContain('<zero_item_safety>');
|
||||
expect(content).toContain('Every IF/Filter `conditions` parameter MUST include');
|
||||
});
|
||||
|
||||
test('returns detailed workflow SDK patterns', () => {
|
||||
const content = getSdkReferenceContent('patterns_detailed');
|
||||
|
||||
expect(content).toContain(WORKFLOW_PATTERNS_DETAILED);
|
||||
expect(content).toContain('output: [{}]');
|
||||
});
|
||||
|
||||
test('includes both workflow pattern sections in the full reference', () => {
|
||||
const content = getSdkReferenceContent('all');
|
||||
|
||||
expect(content).toContain('## Workflow Patterns');
|
||||
expect(content).toContain('<zero_item_safety>');
|
||||
expect(content).toContain('## Workflow Patterns Detailed');
|
||||
expect(content).toContain('output: [{}]');
|
||||
});
|
||||
|
||||
test('accepts patterns_detailed as a tool section', async () => {
|
||||
const tool = createGetWorkflowSdkReferenceTool(user, telemetry);
|
||||
const sectionSchema = tool.config.inputSchema?.section;
|
||||
|
||||
expect(tool.config.description).toContain('Required reference');
|
||||
expect(tool.config.description).toContain('BEFORE writing workflow code');
|
||||
expect(tool.config.inputSchema?.section.description).toContain(
|
||||
'Omit this for the full reference',
|
||||
);
|
||||
expect(sectionSchema?.safeParse('patterns_detailed').success).toBe(true);
|
||||
|
||||
const result = await tool.handler({ section: 'patterns_detailed' }, {} as never);
|
||||
|
||||
expect(result.structuredContent).toEqual({
|
||||
reference: getSdkReferenceContent('patterns_detailed'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -154,6 +154,8 @@ describe('validate-workflow-code MCP tool', () => {
|
|||
const response = parseResult(result);
|
||||
expect(response.valid).toBe(false);
|
||||
expect(response.hint).toContain('sdk_ref');
|
||||
expect(response.hint).toContain('Workflow SDK reference');
|
||||
expect(response.hint).toContain('validate_workflow_code');
|
||||
});
|
||||
|
||||
test('does not include SDK reference hint for non-parse errors', async () => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { getMcpWorkflow, getSdkReferenceHint } from '../tools/workflow-validatio
|
|||
|
||||
jest.mock('@n8n/ai-workflow-builder', () => ({
|
||||
MCP_GET_SDK_REFERENCE_TOOL: { toolName: 'get_sdk_reference', displayTitle: 'SDK Ref' },
|
||||
CODE_BUILDER_VALIDATE_TOOL: { toolName: 'validate_workflow', displayTitle: 'Validate' },
|
||||
}));
|
||||
|
||||
describe('getSdkReferenceHint', () => {
|
||||
|
|
@ -19,6 +20,8 @@ describe('getSdkReferenceHint', () => {
|
|||
const hint = getSdkReferenceHint(error);
|
||||
|
||||
expect(hint).toContain('get_sdk_reference');
|
||||
expect(hint).toContain('Workflow SDK reference');
|
||||
expect(hint).toContain('validate_workflow');
|
||||
});
|
||||
|
||||
test('returns hint for SyntaxError', () => {
|
||||
|
|
@ -27,6 +30,18 @@ describe('getSdkReferenceHint', () => {
|
|||
);
|
||||
|
||||
expect(hint).toContain('get_sdk_reference');
|
||||
expect(hint).toContain('required SDK patterns');
|
||||
});
|
||||
|
||||
test('uses requested follow-up action', () => {
|
||||
const error = new Error('parse failed');
|
||||
error.name = 'WorkflowCodeParseError';
|
||||
|
||||
const hint = getSdkReferenceHint(error, {
|
||||
afterReference: 'Then retry validation.',
|
||||
});
|
||||
|
||||
expect(hint).toContain('Then retry validation.');
|
||||
});
|
||||
|
||||
test('returns undefined for generic Error', () => {
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ export class McpService {
|
|||
'n8n://workflow-sdk/reference',
|
||||
{
|
||||
description:
|
||||
'n8n Workflow SDK reference — patterns, expressions, and rules for building workflows. Get this FIRST before building workflows to learn the SDK.',
|
||||
'Required n8n Workflow SDK reference for building workflows from code. Read this before writing workflow code.',
|
||||
},
|
||||
async () => ({
|
||||
contents: [
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ export const createCreateWorkflowFromCodeTool = (
|
|||
): ToolDefinition<typeof inputSchema> => ({
|
||||
name: MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.toolName,
|
||||
config: {
|
||||
description: `Create a workflow in n8n from validated SDK code. Parses the code into a workflow and saves it. Always validate with ${CODE_BUILDER_VALIDATE_TOOL.toolName} first.`,
|
||||
description: `Create a workflow in n8n from validated SDK code. This tool expects code that already follows the n8n Workflow SDK patterns and has passed ${CODE_BUILDER_VALIDATE_TOOL.toolName}. If code fails to parse, call get_sdk_reference, rewrite the code using the reference, validate again, then retry creation.`,
|
||||
inputSchema,
|
||||
outputSchema,
|
||||
annotations: {
|
||||
|
|
@ -268,7 +268,9 @@ export const createCreateWorkflowFromCodeTool = (
|
|||
};
|
||||
telemetry.track(USER_CALLED_MCP_TOOL_EVENT, telemetryPayload);
|
||||
|
||||
const hint = getSdkReferenceHint(error);
|
||||
const hint = getSdkReferenceHint(error, {
|
||||
afterReference: `Rewrite the code, call ${CODE_BUILDER_VALIDATE_TOOL.toolName} until it returns valid=true, then call ${MCP_CREATE_WORKFLOW_FROM_CODE_TOOL.toolName} again.`,
|
||||
});
|
||||
const output = { error: errorMessage, ...(hint ? { hint } : {}) };
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export const createGetSuggestedWorkflowNodesTool = (
|
|||
name: CODE_BUILDER_GET_SUGGESTED_NODES_TOOL.toolName,
|
||||
config: {
|
||||
description:
|
||||
'Get curated node recommendations for workflow technique categories. Returns recommended nodes with pattern hints and configuration guidance. Use after analyzing what kind of workflow to build.',
|
||||
'Required workflow-planning step. Get curated node recommendations for workflow technique categories before searching for nodes or writing code. Returns recommended nodes with pattern hints and configuration guidance.',
|
||||
inputSchema,
|
||||
outputSchema,
|
||||
annotations: {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { getSdkReferenceContent, type SdkReferenceSection } from './sdk-referenc
|
|||
|
||||
const VALID_SECTIONS: SdkReferenceSection[] = [
|
||||
'patterns',
|
||||
'patterns_detailed',
|
||||
'expressions',
|
||||
'functions',
|
||||
'rules',
|
||||
|
|
@ -25,7 +26,7 @@ const inputSchema = {
|
|||
.enum(VALID_SECTIONS as [string, ...string[]])
|
||||
.optional()
|
||||
.describe(
|
||||
'Optional section to retrieve: "patterns", "expressions", "functions", "rules", "import", "guidelines", "design", or "all" (default)',
|
||||
'Optional section to retrieve. Omit this for the full reference, or use a section for targeted lookup.',
|
||||
),
|
||||
} satisfies z.ZodRawShape;
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ export const createGetWorkflowSdkReferenceTool = (
|
|||
name: MCP_GET_SDK_REFERENCE_TOOL.toolName,
|
||||
config: {
|
||||
description:
|
||||
'Get the n8n Workflow SDK reference documentation including patterns, expression syntax, and rules. Call this FIRST before building workflows to learn the SDK.',
|
||||
'Required reference for building n8n Workflow SDK code. Call this BEFORE writing workflow code to learn workflow(), trigger()/node(), .add()/.to(), expr(), and credential patterns.',
|
||||
inputSchema,
|
||||
outputSchema,
|
||||
annotations: {
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ export function getMcpInstructions(isBuilderEnabled: boolean): string {
|
|||
|
||||
To build n8n workflows, follow these steps in order:
|
||||
|
||||
1. Read the SDK reference: Call ${MCP_GET_SDK_REFERENCE_TOOL.toolName} (or use the n8n://workflow-sdk/reference resource) to learn the SDK patterns and syntax.
|
||||
1. Read the SDK reference: You MUST call ${MCP_GET_SDK_REFERENCE_TOOL.toolName} (or use the n8n://workflow-sdk/reference resource) before writing workflow code. Do not guess SDK syntax.
|
||||
|
||||
2. Discover nodes: Call ${CODE_BUILDER_SEARCH_NODES_TOOL.toolName} with queries for services you need (e.g., ["gmail", "slack", "schedule trigger"]) and utility nodes (e.g., ["set", "if", "merge", "code"]). Note the discriminators (resource/operation/mode) in the results.
|
||||
2. Get suggested nodes: You MUST call ${CODE_BUILDER_GET_SUGGESTED_NODES_TOOL.toolName} with all relevant workflow technique categories before searching for nodes. Use the recommendations, pattern hints, and configuration guidance to decide which nodes and patterns to use.
|
||||
|
||||
3. (Optional) Get suggestions: Call ${CODE_BUILDER_GET_SUGGESTED_NODES_TOOL.toolName} with workflow technique categories for curated recommendations.
|
||||
3. Discover nodes: Call ${CODE_BUILDER_SEARCH_NODES_TOOL.toolName} with queries for services you need (e.g., ["gmail", "slack", "schedule trigger"]), utility nodes (e.g., ["set", "if", "merge", "code"]), and suggested nodes you plan to use. Note the discriminators (resource/operation/mode) in the results.
|
||||
|
||||
4. Get type definitions: Call ${CODE_BUILDER_GET_NODE_TYPES_TOOL.toolName} with ALL node IDs you plan to use, including discriminators from search results. This returns the exact TypeScript parameter definitions. DO NOT skip this — guessing parameter names creates invalid workflows.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
/**
|
||||
* SDK reference content for MCP workflow builder tools.
|
||||
*
|
||||
* Imports the raw (unescaped) prompt constants from the code-builder package
|
||||
* Imports the raw (unescaped) prompt constants from the workflow-sdk package
|
||||
* and assembles them into structured SDK reference documentation.
|
||||
* Served both as an MCP resource and via the n8n_get_workflow_sdk_reference tool.
|
||||
*/
|
||||
|
||||
import { SDK_IMPORT_STATEMENT } from '@n8n/ai-workflow-builder';
|
||||
import {
|
||||
SDK_IMPORT_STATEMENT,
|
||||
EXPRESSION_REFERENCE,
|
||||
WORKFLOW_PATTERNS,
|
||||
WORKFLOW_SDK_PATTERNS,
|
||||
WORKFLOW_PATTERNS_DETAILED,
|
||||
ADDITIONAL_FUNCTIONS,
|
||||
WORKFLOW_RULES,
|
||||
} from '@n8n/ai-workflow-builder';
|
||||
} from '@n8n/workflow-sdk/prompts/sdk-reference';
|
||||
|
||||
// NOTE: CODING_GUIDELINES and DESIGN_GUIDANCE are MCP-only constants defined
|
||||
// below. They are NOT shared with the code-builder agent (which has its own
|
||||
|
|
@ -40,6 +41,8 @@ const DESIGN_GUIDANCE = `Design guidance:
|
|||
- **Trace item counts**: For each connection A → B, if A returns N items, should B run N times or just once? If B doesn't need A's items (e.g., it fetches from an independent source), either set \`executeOnce: true\` on B or use parallel branches + Merge to combine results.
|
||||
- **Handling convergence after branches**: When a node receives data from multiple paths (after Switch, IF, Merge): use optional chaining \`expr('{{ $json.data?.approved ?? $json.status }}')\`, reference a node that ALWAYS runs \`expr("{{ $('Webhook').item.json.field }}")\`, or normalize data before convergence with Set nodes.
|
||||
- **Prefer dedicated integration nodes** over HTTP Request when search results show one is available.
|
||||
- **Normalize webhook payloads immediately**: Webhook data often appears under \`body\`, but clients and tests may provide fields directly on \`$json\`. Add a Set node after the webhook that uses optional chaining and defaults, e.g. \`expr('{{ $json.body?.name ?? $json.name ?? "there" }}')\`, \`expr('{{ $json.body?.email ?? $json.email ?? "" }}')\`, and \`expr('{{ $json.body?.message ?? $json.message ?? "" }}')\`.
|
||||
- **Fan out independent side effects**: For workflows that send email, notify chat, write to storage, and respond to a webhook, branch all side-effect nodes from normalized data instead of chaining them. Set \`onError: 'continueRegularOutput'\` on independent external action nodes when one failed action should not block the others.
|
||||
- **Pay attention to @builderHint annotations** in the type definitions — they provide critical guidance on how to correctly configure node parameters.`;
|
||||
|
||||
/**
|
||||
|
|
@ -47,6 +50,7 @@ const DESIGN_GUIDANCE = `Design guidance:
|
|||
*/
|
||||
export type SdkReferenceSection =
|
||||
| 'patterns'
|
||||
| 'patterns_detailed'
|
||||
| 'expressions'
|
||||
| 'functions'
|
||||
| 'rules'
|
||||
|
|
@ -57,13 +61,18 @@ export type SdkReferenceSection =
|
|||
|
||||
const SDK_IMPORT_SECTION = `## SDK Import Statement\n\n\`\`\`javascript\n${SDK_IMPORT_STATEMENT}\n\`\`\``;
|
||||
|
||||
const WORKFLOW_PATTERNS_SECTION = `## Workflow Patterns\n\n${WORKFLOW_SDK_PATTERNS}`;
|
||||
|
||||
const WORKFLOW_PATTERNS_DETAILED_SECTION = `## Workflow Patterns Detailed\n\n${WORKFLOW_PATTERNS_DETAILED}`;
|
||||
|
||||
const CODING_GUIDELINES_SECTION = `## Coding Guidelines\n\n${CODING_GUIDELINES}`;
|
||||
|
||||
const DESIGN_GUIDANCE_SECTION = `## Design Guidance\n\n${DESIGN_GUIDANCE}`;
|
||||
|
||||
const SECTIONS: Record<Exclude<SdkReferenceSection, 'all'>, string> = {
|
||||
import: SDK_IMPORT_SECTION,
|
||||
patterns: WORKFLOW_PATTERNS,
|
||||
patterns: WORKFLOW_PATTERNS_SECTION,
|
||||
patterns_detailed: WORKFLOW_PATTERNS_DETAILED_SECTION,
|
||||
expressions: EXPRESSION_REFERENCE,
|
||||
functions: ADDITIONAL_FUNCTIONS,
|
||||
rules: WORKFLOW_RULES,
|
||||
|
|
@ -86,6 +95,8 @@ export function getSdkReferenceContent(section?: SdkReferenceSection): string {
|
|||
'',
|
||||
SECTIONS.patterns,
|
||||
'',
|
||||
SECTIONS.patterns_detailed,
|
||||
'',
|
||||
SECTIONS.expressions,
|
||||
'',
|
||||
SECTIONS.functions,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const createValidateWorkflowCodeTool = (
|
|||
name: CODE_BUILDER_VALIDATE_TOOL.toolName,
|
||||
config: {
|
||||
description:
|
||||
'Validate n8n Workflow SDK code. Parses the code into a workflow and checks for errors. Returns the workflow JSON if valid, or detailed error messages to fix. Always validate before creating a workflow.',
|
||||
'Validate n8n Workflow SDK code. Required before creating or updating workflows from code. If you have not already read get_sdk_reference, call that first; guessing SDK syntax commonly creates invalid workflows.',
|
||||
inputSchema,
|
||||
outputSchema,
|
||||
annotations: {
|
||||
|
|
|
|||
|
|
@ -4,19 +4,33 @@ import type { Scope } from '@n8n/permissions';
|
|||
import type { WorkflowFinderService } from '@/workflows/workflow-finder.service';
|
||||
|
||||
import { WorkflowAccessError } from '../mcp.errors';
|
||||
import { MCP_GET_SDK_REFERENCE_TOOL } from './workflow-builder/constants';
|
||||
import {
|
||||
CODE_BUILDER_VALIDATE_TOOL,
|
||||
MCP_GET_SDK_REFERENCE_TOOL,
|
||||
} from './workflow-builder/constants';
|
||||
|
||||
type SdkReferenceHintOptions = {
|
||||
afterReference?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a hint nudging MCP clients to consult the SDK reference,
|
||||
* but only when the error is a workflow code parse error.
|
||||
*/
|
||||
export function getSdkReferenceHint(error: unknown): string | undefined {
|
||||
export function getSdkReferenceHint(
|
||||
error: unknown,
|
||||
options: SdkReferenceHintOptions = {},
|
||||
): string | undefined {
|
||||
const isParseError =
|
||||
error instanceof Error &&
|
||||
(error.name === 'WorkflowCodeParseError' || error instanceof SyntaxError);
|
||||
if (!isParseError) return undefined;
|
||||
|
||||
return `Make sure your code uses the n8n Workflow SDK syntax. Call ${MCP_GET_SDK_REFERENCE_TOOL.toolName} first to learn the correct patterns before writing workflow code.`;
|
||||
const afterReference =
|
||||
options.afterReference ??
|
||||
`Rewrite the code using the documented patterns, then call ${CODE_BUILDER_VALIDATE_TOOL.toolName} again before creating or updating a workflow.`;
|
||||
|
||||
return `The code failed to parse as n8n Workflow SDK code. This usually means it does not follow the required SDK patterns. Before retrying, call ${MCP_GET_SDK_REFERENCE_TOOL.toolName} to read the Workflow SDK reference. Use workflow(), trigger()/node(), .add()/.to(), expr(), and newCredential() exactly as documented. ${afterReference}`;
|
||||
}
|
||||
|
||||
export type FoundWorkflow = NonNullable<
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user