feat: integrate n8n management tools from n8n-manager-for-ai-agents (v2.6.0)
- Added 14 n8n management tools for workflow CRUD and execution management - Integrated n8n API client with full error handling and validation - Added conditional tool registration (only when N8N_API_URL configured) - Complete workflow lifecycle: discover → build → validate → deploy → execute - Updated documentation and added integration tests - Maintains backward compatibility - existing functionality unchanged 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
732
src/mcp/handlers-n8n-manager.ts
Normal file
732
src/mcp/handlers-n8n-manager.ts
Normal file
@@ -0,0 +1,732 @@
|
||||
import { N8nApiClient } from '../services/n8n-api-client';
|
||||
import { n8nApiConfig } from '../config/n8n-api';
|
||||
import {
|
||||
Workflow,
|
||||
WorkflowNode,
|
||||
WorkflowConnection,
|
||||
ExecutionStatus,
|
||||
WebhookRequest,
|
||||
McpToolResponse
|
||||
} from '../types/n8n-api';
|
||||
import {
|
||||
validateWorkflowStructure,
|
||||
hasWebhookTrigger,
|
||||
getWebhookUrl
|
||||
} from '../services/n8n-validation';
|
||||
import {
|
||||
N8nApiError,
|
||||
N8nNotFoundError,
|
||||
getUserFriendlyErrorMessage
|
||||
} from '../utils/n8n-errors';
|
||||
import { logger } from '../utils/logger';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Singleton n8n API client instance
|
||||
let apiClient: N8nApiClient | null = null;
|
||||
|
||||
// Get or create API client
|
||||
export function getN8nApiClient(): N8nApiClient | null {
|
||||
if (!n8nApiConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!apiClient) {
|
||||
apiClient = new N8nApiClient(n8nApiConfig);
|
||||
}
|
||||
|
||||
return apiClient;
|
||||
}
|
||||
|
||||
// Helper to ensure API is configured
|
||||
function ensureApiConfigured(): N8nApiClient {
|
||||
const client = getN8nApiClient();
|
||||
if (!client) {
|
||||
throw new Error('n8n API not configured. Please set N8N_API_URL and N8N_API_KEY environment variables.');
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
// Zod schemas for input validation
|
||||
const createWorkflowSchema = z.object({
|
||||
name: z.string(),
|
||||
nodes: z.array(z.any()),
|
||||
connections: z.record(z.any()),
|
||||
settings: z.object({
|
||||
executionOrder: z.enum(['v0', 'v1']).optional(),
|
||||
timezone: z.string().optional(),
|
||||
saveDataErrorExecution: z.enum(['all', 'none']).optional(),
|
||||
saveDataSuccessExecution: z.enum(['all', 'none']).optional(),
|
||||
saveManualExecutions: z.boolean().optional(),
|
||||
saveExecutionProgress: z.boolean().optional(),
|
||||
executionTimeout: z.number().optional(),
|
||||
errorWorkflow: z.string().optional(),
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
const updateWorkflowSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string().optional(),
|
||||
nodes: z.array(z.any()).optional(),
|
||||
connections: z.record(z.any()).optional(),
|
||||
settings: z.any().optional(),
|
||||
});
|
||||
|
||||
const listWorkflowsSchema = z.object({
|
||||
limit: z.number().min(1).max(100).optional(),
|
||||
cursor: z.string().optional(),
|
||||
active: z.boolean().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
projectId: z.string().optional(),
|
||||
excludePinnedData: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const triggerWebhookSchema = z.object({
|
||||
webhookUrl: z.string().url(),
|
||||
httpMethod: z.enum(['GET', 'POST', 'PUT', 'DELETE']).optional(),
|
||||
data: z.record(z.unknown()).optional(),
|
||||
headers: z.record(z.string()).optional(),
|
||||
waitForResponse: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const listExecutionsSchema = z.object({
|
||||
limit: z.number().min(1).max(100).optional(),
|
||||
cursor: z.string().optional(),
|
||||
workflowId: z.string().optional(),
|
||||
projectId: z.string().optional(),
|
||||
status: z.enum(['success', 'error', 'waiting']).optional(),
|
||||
includeData: z.boolean().optional(),
|
||||
});
|
||||
|
||||
// Workflow Management Handlers
|
||||
|
||||
export async function handleCreateWorkflow(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const input = createWorkflowSchema.parse(args);
|
||||
|
||||
// Validate workflow structure
|
||||
const errors = validateWorkflowStructure(input);
|
||||
if (errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Workflow validation failed',
|
||||
details: { errors }
|
||||
};
|
||||
}
|
||||
|
||||
// Create workflow
|
||||
const workflow = await client.createWorkflow(input);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: workflow,
|
||||
message: `Workflow "${workflow.name}" created successfully with ID: ${workflow.id}`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetWorkflow(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id } = z.object({ id: z.string() }).parse(args);
|
||||
|
||||
const workflow = await client.getWorkflow(id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: workflow
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetWorkflowDetails(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id } = z.object({ id: z.string() }).parse(args);
|
||||
|
||||
const workflow = await client.getWorkflow(id);
|
||||
|
||||
// Get recent executions for this workflow
|
||||
const executions = await client.listExecutions({
|
||||
workflowId: id,
|
||||
limit: 10
|
||||
});
|
||||
|
||||
// Calculate execution statistics
|
||||
const stats = {
|
||||
totalExecutions: executions.data.length,
|
||||
successCount: executions.data.filter(e => e.status === ExecutionStatus.SUCCESS).length,
|
||||
errorCount: executions.data.filter(e => e.status === ExecutionStatus.ERROR).length,
|
||||
lastExecutionTime: executions.data[0]?.startedAt || null
|
||||
};
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
workflow,
|
||||
executionStats: stats,
|
||||
hasWebhookTrigger: hasWebhookTrigger(workflow),
|
||||
webhookPath: getWebhookUrl(workflow)
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetWorkflowStructure(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id } = z.object({ id: z.string() }).parse(args);
|
||||
|
||||
const workflow = await client.getWorkflow(id);
|
||||
|
||||
// Simplify nodes to just essential structure
|
||||
const simplifiedNodes = workflow.nodes.map(node => ({
|
||||
id: node.id,
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
position: node.position,
|
||||
disabled: node.disabled || false
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
nodes: simplifiedNodes,
|
||||
connections: workflow.connections,
|
||||
nodeCount: workflow.nodes.length,
|
||||
connectionCount: Object.keys(workflow.connections).length
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetWorkflowMinimal(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id } = z.object({ id: z.string() }).parse(args);
|
||||
|
||||
const workflow = await client.getWorkflow(id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
tags: workflow.tags || [],
|
||||
createdAt: workflow.createdAt,
|
||||
updatedAt: workflow.updatedAt
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleUpdateWorkflow(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const input = updateWorkflowSchema.parse(args);
|
||||
const { id, ...updateData } = input;
|
||||
|
||||
// If nodes/connections are being updated, validate the structure
|
||||
if (updateData.nodes || updateData.connections) {
|
||||
// Fetch current workflow if only partial update
|
||||
let fullWorkflow = updateData as Partial<Workflow>;
|
||||
|
||||
if (!updateData.nodes || !updateData.connections) {
|
||||
const current = await client.getWorkflow(id);
|
||||
fullWorkflow = {
|
||||
...current,
|
||||
...updateData
|
||||
};
|
||||
}
|
||||
|
||||
const errors = validateWorkflowStructure(fullWorkflow);
|
||||
if (errors.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Workflow validation failed',
|
||||
details: { errors }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Update workflow
|
||||
const workflow = await client.updateWorkflow(id, updateData);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: workflow,
|
||||
message: `Workflow "${workflow.name}" updated successfully`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDeleteWorkflow(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id } = z.object({ id: z.string() }).parse(args);
|
||||
|
||||
await client.deleteWorkflow(id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Workflow ${id} deleted successfully`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleListWorkflows(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const input = listWorkflowsSchema.parse(args || {});
|
||||
|
||||
const response = await client.listWorkflows({
|
||||
limit: input.limit || 100,
|
||||
cursor: input.cursor,
|
||||
active: input.active,
|
||||
tags: input.tags,
|
||||
projectId: input.projectId,
|
||||
excludePinnedData: input.excludePinnedData ?? true
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
workflows: response.data,
|
||||
nextCursor: response.nextCursor,
|
||||
total: response.data.length
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Execution Management Handlers
|
||||
|
||||
export async function handleTriggerWebhookWorkflow(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const input = triggerWebhookSchema.parse(args);
|
||||
|
||||
const webhookRequest: WebhookRequest = {
|
||||
webhookUrl: input.webhookUrl,
|
||||
httpMethod: input.httpMethod || 'POST',
|
||||
data: input.data,
|
||||
headers: input.headers,
|
||||
waitForResponse: input.waitForResponse ?? true
|
||||
};
|
||||
|
||||
const response = await client.triggerWebhook(webhookRequest);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: response,
|
||||
message: 'Webhook triggered successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: error.details as Record<string, unknown> | undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleGetExecution(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id, includeData } = z.object({
|
||||
id: z.string(),
|
||||
includeData: z.boolean().optional()
|
||||
}).parse(args);
|
||||
|
||||
const execution = await client.getExecution(id, includeData || false);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: execution
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleListExecutions(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const input = listExecutionsSchema.parse(args || {});
|
||||
|
||||
const response = await client.listExecutions({
|
||||
limit: input.limit || 100,
|
||||
cursor: input.cursor,
|
||||
workflowId: input.workflowId,
|
||||
projectId: input.projectId,
|
||||
status: input.status as ExecutionStatus | undefined,
|
||||
includeData: input.includeData || false
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
executions: response.data,
|
||||
nextCursor: response.nextCursor,
|
||||
total: response.data.length
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleDeleteExecution(args: unknown): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const { id } = z.object({ id: z.string() }).parse(args);
|
||||
|
||||
await client.deleteExecution(id);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Execution ${id} deleted successfully`
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof z.ZodError) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Invalid input',
|
||||
details: { errors: error.errors }
|
||||
};
|
||||
}
|
||||
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// System Tools Handlers
|
||||
|
||||
export async function handleHealthCheck(): Promise<McpToolResponse> {
|
||||
try {
|
||||
const client = ensureApiConfigured();
|
||||
const health = await client.healthCheck();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
status: health.status,
|
||||
instanceId: health.instanceId,
|
||||
n8nVersion: health.n8nVersion,
|
||||
features: health.features,
|
||||
apiUrl: n8nApiConfig?.baseUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof N8nApiError) {
|
||||
return {
|
||||
success: false,
|
||||
error: getUserFriendlyErrorMessage(error),
|
||||
code: error.code,
|
||||
details: {
|
||||
apiUrl: n8nApiConfig?.baseUrl,
|
||||
hint: 'Check if n8n is running and API is enabled'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleListAvailableTools(): Promise<McpToolResponse> {
|
||||
const tools = [
|
||||
{
|
||||
category: 'Workflow Management',
|
||||
tools: [
|
||||
{ name: 'n8n_create_workflow', description: 'Create new workflows' },
|
||||
{ name: 'n8n_get_workflow', description: 'Get workflow by ID' },
|
||||
{ name: 'n8n_get_workflow_details', description: 'Get detailed workflow info with stats' },
|
||||
{ name: 'n8n_get_workflow_structure', description: 'Get simplified workflow structure' },
|
||||
{ name: 'n8n_get_workflow_minimal', description: 'Get minimal workflow info' },
|
||||
{ name: 'n8n_update_workflow', description: 'Update existing workflows' },
|
||||
{ name: 'n8n_delete_workflow', description: 'Delete workflows' },
|
||||
{ name: 'n8n_list_workflows', description: 'List workflows with filters' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'Execution Management',
|
||||
tools: [
|
||||
{ name: 'n8n_trigger_webhook_workflow', description: 'Trigger workflows via webhook' },
|
||||
{ name: 'n8n_get_execution', description: 'Get execution details' },
|
||||
{ name: 'n8n_list_executions', description: 'List executions with filters' },
|
||||
{ name: 'n8n_delete_execution', description: 'Delete execution records' }
|
||||
]
|
||||
},
|
||||
{
|
||||
category: 'System',
|
||||
tools: [
|
||||
{ name: 'n8n_health_check', description: 'Check API connectivity' },
|
||||
{ name: 'n8n_list_available_tools', description: 'List all available tools' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const apiConfigured = n8nApiConfig !== null;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
tools,
|
||||
apiConfigured,
|
||||
configuration: apiConfigured ? {
|
||||
apiUrl: n8nApiConfig!.baseUrl,
|
||||
timeout: n8nApiConfig!.timeout,
|
||||
maxRetries: n8nApiConfig!.maxRetries
|
||||
} : null,
|
||||
limitations: [
|
||||
'Cannot activate/deactivate workflows via API',
|
||||
'Cannot execute workflows directly (must use webhooks)',
|
||||
'Cannot stop running executions',
|
||||
'Tags and credentials have limited API support'
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { n8nDocumentationToolsFinal } from './tools-update';
|
||||
import { n8nManagementTools } from './tools-n8n-manager';
|
||||
import { logger } from '../utils/logger';
|
||||
import { NodeRepository } from '../database/node-repository';
|
||||
import { DatabaseAdapter, createDatabaseAdapter } from '../database/database-adapter';
|
||||
@@ -20,6 +21,8 @@ import { PropertyDependencies } from '../services/property-dependencies';
|
||||
import { SimpleCache } from '../utils/simple-cache';
|
||||
import { TemplateService } from '../templates/template-service';
|
||||
import { WorkflowValidator } from '../services/workflow-validator';
|
||||
import { isN8nApiConfigured } from '../config/n8n-api';
|
||||
import * as n8nHandlers from './handlers-n8n-manager';
|
||||
|
||||
interface NodeRow {
|
||||
node_type: string;
|
||||
@@ -130,9 +133,19 @@ export class N8NDocumentationMCPServer {
|
||||
});
|
||||
|
||||
// Handle tool listing
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: n8nDocumentationToolsFinal,
|
||||
}));
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
// Combine documentation tools with management tools if API is configured
|
||||
const tools = [...n8nDocumentationToolsFinal];
|
||||
|
||||
if (isN8nApiConfigured()) {
|
||||
tools.push(...n8nManagementTools);
|
||||
logger.info('n8n management tools enabled');
|
||||
} else {
|
||||
logger.info('n8n management tools disabled (API not configured)');
|
||||
}
|
||||
|
||||
return { tools };
|
||||
});
|
||||
|
||||
// Handle tool execution
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
@@ -211,6 +224,37 @@ export class N8NDocumentationMCPServer {
|
||||
return this.validateWorkflowConnections(args.workflow);
|
||||
case 'validate_workflow_expressions':
|
||||
return this.validateWorkflowExpressions(args.workflow);
|
||||
|
||||
// n8n Management Tools (if API is configured)
|
||||
case 'n8n_create_workflow':
|
||||
return n8nHandlers.handleCreateWorkflow(args);
|
||||
case 'n8n_get_workflow':
|
||||
return n8nHandlers.handleGetWorkflow(args);
|
||||
case 'n8n_get_workflow_details':
|
||||
return n8nHandlers.handleGetWorkflowDetails(args);
|
||||
case 'n8n_get_workflow_structure':
|
||||
return n8nHandlers.handleGetWorkflowStructure(args);
|
||||
case 'n8n_get_workflow_minimal':
|
||||
return n8nHandlers.handleGetWorkflowMinimal(args);
|
||||
case 'n8n_update_workflow':
|
||||
return n8nHandlers.handleUpdateWorkflow(args);
|
||||
case 'n8n_delete_workflow':
|
||||
return n8nHandlers.handleDeleteWorkflow(args);
|
||||
case 'n8n_list_workflows':
|
||||
return n8nHandlers.handleListWorkflows(args);
|
||||
case 'n8n_trigger_webhook_workflow':
|
||||
return n8nHandlers.handleTriggerWebhookWorkflow(args);
|
||||
case 'n8n_get_execution':
|
||||
return n8nHandlers.handleGetExecution(args);
|
||||
case 'n8n_list_executions':
|
||||
return n8nHandlers.handleListExecutions(args);
|
||||
case 'n8n_delete_execution':
|
||||
return n8nHandlers.handleDeleteExecution(args);
|
||||
case 'n8n_health_check':
|
||||
return n8nHandlers.handleHealthCheck();
|
||||
case 'n8n_list_available_tools':
|
||||
return n8nHandlers.handleListAvailableTools();
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
|
||||
322
src/mcp/tools-n8n-manager.ts
Normal file
322
src/mcp/tools-n8n-manager.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
import { ToolDefinition } from '../types';
|
||||
|
||||
/**
|
||||
* n8n Management Tools
|
||||
*
|
||||
* These tools enable AI agents to manage n8n workflows through the n8n API.
|
||||
* They require N8N_API_URL and N8N_API_KEY to be configured.
|
||||
*/
|
||||
export const n8nManagementTools: ToolDefinition[] = [
|
||||
// Workflow Management Tools
|
||||
{
|
||||
name: 'n8n_create_workflow',
|
||||
description: `Create a new workflow in n8n. Requires workflow name, nodes array, and connections object. The workflow will be created in inactive state and must be manually activated in the UI. Returns the created workflow with its ID.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Workflow name (required)'
|
||||
},
|
||||
nodes: {
|
||||
type: 'array',
|
||||
description: 'Array of workflow nodes. Each node must have: id, name, type, typeVersion, position, and parameters',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['id', 'name', 'type', 'typeVersion', 'position', 'parameters'],
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
name: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
typeVersion: { type: 'number' },
|
||||
position: {
|
||||
type: 'array',
|
||||
items: { type: 'number' },
|
||||
minItems: 2,
|
||||
maxItems: 2
|
||||
},
|
||||
parameters: { type: 'object' },
|
||||
credentials: { type: 'object' },
|
||||
disabled: { type: 'boolean' },
|
||||
notes: { type: 'string' },
|
||||
continueOnFail: { type: 'boolean' },
|
||||
retryOnFail: { type: 'boolean' },
|
||||
maxTries: { type: 'number' },
|
||||
waitBetweenTries: { type: 'number' }
|
||||
}
|
||||
}
|
||||
},
|
||||
connections: {
|
||||
type: 'object',
|
||||
description: 'Workflow connections object. Keys are source node IDs, values define output connections'
|
||||
},
|
||||
settings: {
|
||||
type: 'object',
|
||||
description: 'Optional workflow settings (execution order, timezone, error handling)',
|
||||
properties: {
|
||||
executionOrder: { type: 'string', enum: ['v0', 'v1'] },
|
||||
timezone: { type: 'string' },
|
||||
saveDataErrorExecution: { type: 'string', enum: ['all', 'none'] },
|
||||
saveDataSuccessExecution: { type: 'string', enum: ['all', 'none'] },
|
||||
saveManualExecutions: { type: 'boolean' },
|
||||
saveExecutionProgress: { type: 'boolean' },
|
||||
executionTimeout: { type: 'number' },
|
||||
errorWorkflow: { type: 'string' }
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['name', 'nodes', 'connections']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_get_workflow',
|
||||
description: `Get a workflow by ID. Returns the complete workflow including nodes, connections, and settings.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Workflow ID'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_get_workflow_details',
|
||||
description: `Get detailed workflow information including metadata, version, and execution statistics. More comprehensive than get_workflow.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Workflow ID'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_get_workflow_structure',
|
||||
description: `Get simplified workflow structure showing only nodes and their connections. Useful for understanding workflow flow without parameter details.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Workflow ID'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_get_workflow_minimal',
|
||||
description: `Get minimal workflow information (ID, name, active status, tags). Fast and lightweight for listing purposes.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Workflow ID'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_update_workflow',
|
||||
description: `Update an existing workflow. Requires the full nodes array when modifying nodes/connections. Cannot activate workflows via API - use UI instead.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Workflow ID to update'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'New workflow name'
|
||||
},
|
||||
nodes: {
|
||||
type: 'array',
|
||||
description: 'Complete array of workflow nodes (required if modifying workflow structure)'
|
||||
},
|
||||
connections: {
|
||||
type: 'object',
|
||||
description: 'Complete connections object (required if modifying workflow structure)'
|
||||
},
|
||||
settings: {
|
||||
type: 'object',
|
||||
description: 'Workflow settings to update'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_delete_workflow',
|
||||
description: `Permanently delete a workflow. This action cannot be undone.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Workflow ID to delete'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_list_workflows',
|
||||
description: `List workflows with optional filters. Supports pagination via cursor.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of workflows to return (1-100, default: 100)'
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor from previous response'
|
||||
},
|
||||
active: {
|
||||
type: 'boolean',
|
||||
description: 'Filter by active status'
|
||||
},
|
||||
tags: {
|
||||
type: 'array',
|
||||
items: { type: 'string' },
|
||||
description: 'Filter by tags (exact match)'
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Filter by project ID (enterprise feature)'
|
||||
},
|
||||
excludePinnedData: {
|
||||
type: 'boolean',
|
||||
description: 'Exclude pinned data from response (default: true)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Execution Management Tools
|
||||
{
|
||||
name: 'n8n_trigger_webhook_workflow',
|
||||
description: `Trigger a workflow via webhook. Workflow must be ACTIVE and have a Webhook trigger node. HTTP method must match webhook configuration.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
webhookUrl: {
|
||||
type: 'string',
|
||||
description: 'Full webhook URL from n8n workflow (e.g., https://n8n.example.com/webhook/abc-def-ghi)'
|
||||
},
|
||||
httpMethod: {
|
||||
type: 'string',
|
||||
enum: ['GET', 'POST', 'PUT', 'DELETE'],
|
||||
description: 'HTTP method (must match webhook configuration, often GET)'
|
||||
},
|
||||
data: {
|
||||
type: 'object',
|
||||
description: 'Data to send with the webhook request'
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
description: 'Additional HTTP headers'
|
||||
},
|
||||
waitForResponse: {
|
||||
type: 'boolean',
|
||||
description: 'Wait for workflow completion (default: true)'
|
||||
}
|
||||
},
|
||||
required: ['webhookUrl']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_get_execution',
|
||||
description: `Get details of a specific execution by ID.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Execution ID'
|
||||
},
|
||||
includeData: {
|
||||
type: 'boolean',
|
||||
description: 'Include full execution data (default: false)'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_list_executions',
|
||||
description: `List workflow executions with optional filters. Supports pagination.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of executions to return (1-100, default: 100)'
|
||||
},
|
||||
cursor: {
|
||||
type: 'string',
|
||||
description: 'Pagination cursor from previous response'
|
||||
},
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
description: 'Filter by workflow ID'
|
||||
},
|
||||
projectId: {
|
||||
type: 'string',
|
||||
description: 'Filter by project ID (enterprise feature)'
|
||||
},
|
||||
status: {
|
||||
type: 'string',
|
||||
enum: ['success', 'error', 'waiting'],
|
||||
description: 'Filter by execution status'
|
||||
},
|
||||
includeData: {
|
||||
type: 'boolean',
|
||||
description: 'Include execution data (default: false)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_delete_execution',
|
||||
description: `Delete an execution record. This only removes the execution history, not any data processed.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'Execution ID to delete'
|
||||
}
|
||||
},
|
||||
required: ['id']
|
||||
}
|
||||
},
|
||||
|
||||
// System Tools
|
||||
{
|
||||
name: 'n8n_health_check',
|
||||
description: `Check n8n instance health and API connectivity. Returns status and available features.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'n8n_list_available_tools',
|
||||
description: `List all available n8n management tools and their capabilities. Useful for understanding what operations are possible.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
];
|
||||
Reference in New Issue
Block a user