From c01a97b6fbca9335e8bce4e80f8d3230f6f480d2 Mon Sep 17 00:00:00 2001 From: rhys-041 Date: Thu, 27 Mar 2025 15:32:10 -0400 Subject: [PATCH] Add run_webhook tool for executing workflows via webhooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit adds a new MCP tool, run_webhook, which allows executing n8n workflows via webhooks instead of using the API directly. This implementation: 1. Creates a new RunWebhookHandler class that: - Takes a workflowName parameter and automatically prepends "webhook/" to create the full path - Uses basic authentication from environment variables - Makes HTTP requests to the webhook endpoints and returns the responses 2. Adds new environment variables: - N8N_WEBHOOK_USERNAME: Username for webhook basic authentication - N8N_WEBHOOK_PASSWORD: Password for webhook basic authentication 3. Updates server configuration and handlers to register and expose the new tool 4. Adds comprehensive documentation in: - execution-tools.md with examples and schema - README.md with configuration and usage information This provides a simpler alternative to the API-based workflow execution, allowing Claude to trigger n8n webhooks directly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 25 +++++- docs/api/execution-tools.md | 67 ++++++++++++++ src/config/environment.ts | 22 +++++ src/config/server.ts | 6 +- src/tools/execution/handler.ts | 6 +- src/tools/execution/index.ts | 5 +- src/tools/execution/run.ts | 154 +++++++++++++++++++++++++++++++++ 7 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 src/tools/execution/run.ts diff --git a/README.md b/README.md index 2f072e6..117b4b1 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,8 @@ Configure the following environment variables: |----------|-------------|---------| | `N8N_API_URL` | URL of the n8n API | `http://localhost:5678/api/v1` | | `N8N_API_KEY` | API key for authenticating with n8n | `n8n_api_...` | +| `N8N_WEBHOOK_USERNAME` | Username for webhook authentication | `username` | +| `N8N_WEBHOOK_PASSWORD` | Password for webhook authentication | `password` | | `DEBUG` | Enable debug logging (optional) | `true` or `false` | ### Generating an n8n API Key @@ -100,6 +102,26 @@ install_local_mcp_server path/to/n8n-mcp-server The server provides the following tools: +### Using Webhooks + +This MCP server supports executing workflows through n8n webhooks. To use this functionality: + +1. Create a webhook-triggered workflow in n8n. +2. Set up Basic Authentication on your webhook node. +3. Use the `run_webhook` tool to trigger the workflow, passing just the workflow name. + +Example: +```javascript +const result = await useRunWebhook({ + workflowName: "hello-world", // Will call /webhook/hello-world + data: { + prompt: "Hello from AI assistant!" + } +}); +``` + +The webhook authentication is handled automatically using the `N8N_WEBHOOK_USERNAME` and `N8N_WEBHOOK_PASSWORD` environment variables. + ### Workflow Management - `workflow_list`: List all workflows @@ -112,7 +134,8 @@ The server provides the following tools: ### Execution Management -- `execution_run`: Execute a workflow +- `execution_run`: Execute a workflow via the API +- `run_webhook`: Execute a workflow via a webhook - `execution_get`: Get details of a specific execution - `execution_list`: List executions for a workflow - `execution_stop`: Stop a running execution diff --git a/docs/api/execution-tools.md b/docs/api/execution-tools.md index 803f0ba..6505366 100644 --- a/docs/api/execution-tools.md +++ b/docs/api/execution-tools.md @@ -8,6 +8,73 @@ Execution tools allow AI assistants to execute n8n workflows and manage executio ## Available Tools +### run_webhook + +Executes a workflow via webhook with optional input data. + +**Input Schema:** + +```json +{ + "type": "object", + "properties": { + "workflowName": { + "type": "string", + "description": "Name of the workflow to execute (e.g., \"hello-world\")" + }, + "data": { + "type": "object", + "description": "Input data to pass to the webhook" + }, + "headers": { + "type": "object", + "description": "Additional headers to send with the request" + } + }, + "required": ["workflowName"] +} +``` + +**Example Usage:** + +```javascript +// Execute webhook with data +const webhookResult = await useRunWebhook({ + workflowName: "hello-world", + data: { + prompt: "Good morning!" + } +}); + +// Execute webhook with additional headers +const webhookWithHeaders = await useRunWebhook({ + workflowName: "hello-world", + data: { + prompt: "Hello with custom header" + }, + headers: { + "X-Custom-Header": "CustomValue" + } +}); +``` + +**Response:** + +```javascript +{ + "status": 200, + "statusText": "OK", + "data": { + // Response data from the webhook + } +} +``` + +**Note:** +- Authentication for the webhook is automatically handled using the environment variables `N8N_WEBHOOK_USERNAME` and `N8N_WEBHOOK_PASSWORD`. +- The tool automatically prefixes the `workflowName` with `webhook/` to create the full webhook path. For example, if you provide `hello-world` as the workflow name, the tool will call `{baseUrl}/webhook/hello-world`. + + ### execution_run Executes a workflow with optional input data. diff --git a/src/config/environment.ts b/src/config/environment.ts index 97e7c40..3ffda15 100644 --- a/src/config/environment.ts +++ b/src/config/environment.ts @@ -13,6 +13,8 @@ import { ErrorCode } from '../errors/error-codes.js'; export const ENV_VARS = { N8N_API_URL: 'N8N_API_URL', N8N_API_KEY: 'N8N_API_KEY', + N8N_WEBHOOK_USERNAME: 'N8N_WEBHOOK_USERNAME', + N8N_WEBHOOK_PASSWORD: 'N8N_WEBHOOK_PASSWORD', DEBUG: 'DEBUG', }; @@ -20,6 +22,8 @@ export const ENV_VARS = { export interface EnvConfig { n8nApiUrl: string; n8nApiKey: string; + n8nWebhookUsername: string; + n8nWebhookPassword: string; debug: boolean; } @@ -39,6 +43,8 @@ export function loadEnvironmentVariables(): void { export function getEnvConfig(): EnvConfig { const n8nApiUrl = process.env[ENV_VARS.N8N_API_URL]; const n8nApiKey = process.env[ENV_VARS.N8N_API_KEY]; + const n8nWebhookUsername = process.env[ENV_VARS.N8N_WEBHOOK_USERNAME]; + const n8nWebhookPassword = process.env[ENV_VARS.N8N_WEBHOOK_PASSWORD]; const debug = process.env[ENV_VARS.DEBUG]?.toLowerCase() === 'true'; // Validate required environment variables @@ -56,6 +62,20 @@ export function getEnvConfig(): EnvConfig { ); } + if (!n8nWebhookUsername) { + throw new McpError( + ErrorCode.InitializationError, + `Missing required environment variable: ${ENV_VARS.N8N_WEBHOOK_USERNAME}` + ); + } + + if (!n8nWebhookPassword) { + throw new McpError( + ErrorCode.InitializationError, + `Missing required environment variable: ${ENV_VARS.N8N_WEBHOOK_PASSWORD}` + ); + } + // Validate URL format try { new URL(n8nApiUrl); @@ -69,6 +89,8 @@ export function getEnvConfig(): EnvConfig { return { n8nApiUrl, n8nApiKey, + n8nWebhookUsername, + n8nWebhookPassword, debug, }; } diff --git a/src/config/server.ts b/src/config/server.ts index e2be374..9ce687a 100644 --- a/src/config/server.ts +++ b/src/config/server.ts @@ -110,7 +110,8 @@ function setupToolCallRequestHandler(server: Server): void { const { ListExecutionsHandler, GetExecutionHandler, - DeleteExecutionHandler + DeleteExecutionHandler, + RunWebhookHandler } = await import('../tools/execution/index.js'); // Route the tool call to the appropriate handler @@ -144,6 +145,9 @@ function setupToolCallRequestHandler(server: Server): void { } else if (toolName === 'delete_execution') { const handler = new DeleteExecutionHandler(); result = await handler.execute(args); + } else if (toolName === 'run_webhook') { + const handler = new RunWebhookHandler(); + result = await handler.execute(args); } else { throw new Error(`Unknown tool: ${toolName}`); } diff --git a/src/tools/execution/handler.ts b/src/tools/execution/handler.ts index c5f2123..818ce2b 100644 --- a/src/tools/execution/handler.ts +++ b/src/tools/execution/handler.ts @@ -11,7 +11,8 @@ import { N8nApiError, getErrorMessage } from '../../errors/index.js'; import { ListExecutionsHandler, GetExecutionHandler, - DeleteExecutionHandler + DeleteExecutionHandler, + RunWebhookHandler } from './index.js'; /** @@ -37,6 +38,9 @@ export default async function executionHandler( case 'delete_execution': return await new DeleteExecutionHandler().execute(args); + case 'run_webhook': + return await new RunWebhookHandler().execute(args); + default: throw new McpError( ErrorCode.NotImplemented, diff --git a/src/tools/execution/index.ts b/src/tools/execution/index.ts index 12e2114..fd29d67 100644 --- a/src/tools/execution/index.ts +++ b/src/tools/execution/index.ts @@ -8,6 +8,7 @@ import { ToolDefinition } from '../../types/index.js'; import { getListExecutionsToolDefinition } from './list.js'; import { getGetExecutionToolDefinition } from './get.js'; import { getDeleteExecutionToolDefinition } from './delete.js'; +import { getRunWebhookToolDefinition } from './run.js'; /** * Set up execution management tools @@ -18,7 +19,8 @@ export async function setupExecutionTools(): Promise { return [ getListExecutionsToolDefinition(), getGetExecutionToolDefinition(), - getDeleteExecutionToolDefinition() + getDeleteExecutionToolDefinition(), + getRunWebhookToolDefinition() ]; } @@ -26,3 +28,4 @@ export async function setupExecutionTools(): Promise { export { ListExecutionsHandler } from './list.js'; export { GetExecutionHandler } from './get.js'; export { DeleteExecutionHandler } from './delete.js'; +export { RunWebhookHandler } from './run.js'; diff --git a/src/tools/execution/run.ts b/src/tools/execution/run.ts new file mode 100644 index 0000000..4e8dc2e --- /dev/null +++ b/src/tools/execution/run.ts @@ -0,0 +1,154 @@ +/** + * Run Execution via Webhook Tool Handler + * + * This module provides a tool for running n8n workflows via webhooks. + */ + +import axios from 'axios'; +import { z } from 'zod'; +import { ToolCallResult } from '../../types/index.js'; +import { BaseExecutionToolHandler } from './base-handler.js'; +import { N8nApiError } from '../../errors/index.js'; +import { getEnvConfig } from '../../config/environment.js'; +import { URL } from 'url'; + +/** + * Webhook execution input schema + */ +const runWebhookSchema = z.object({ + workflowName: z.string().describe('Name of the workflow to execute (e.g., "hello-world")'), + data: z.record(z.any()).optional().describe('Input data to pass to the webhook'), + headers: z.record(z.string()).optional().describe('Additional headers to send with the request') +}); + +/** + * Type for webhook execution parameters + */ +type RunWebhookParams = z.infer; + +/** + * Handler for the run_webhook tool + */ +export class RunWebhookHandler extends BaseExecutionToolHandler { + /** + * Tool definition for execution via webhook + */ + public static readonly inputSchema = runWebhookSchema; + + /** + * Extract N8N base URL from N8N API URL by removing /api/v1 + * @returns N8N base URL + */ + private getN8nBaseUrl(): string { + const config = getEnvConfig(); + const apiUrl = new URL(config.n8nApiUrl); + + // Remove /api/v1 if it exists in the path + let path = apiUrl.pathname; + if (path.endsWith('/api/v1') || path.endsWith('/api/v1/')) { + path = path.replace(/\/api\/v1\/?$/, ''); + } + + // Create a new URL with the base path + apiUrl.pathname = path; + return apiUrl.toString(); + } + + /** + * Validate and execute webhook call + * + * @param args Tool arguments + * @returns Tool call result + */ + async execute(args: Record): Promise { + return this.handleExecution(async (args) => { + // Parse and validate arguments + const params = runWebhookSchema.parse(args); + + // Get environment config for auth credentials + const config = getEnvConfig(); + + try { + // Get the webhook URL with the proper prefix + const baseUrl = this.getN8nBaseUrl(); + const webhookPath = `webhook/${params.workflowName}`; + const webhookUrl = new URL(webhookPath, baseUrl).toString(); + + // Prepare request config with basic auth from environment + const requestConfig: any = { + headers: { + 'Content-Type': 'application/json', + ...(params.headers || {}) + }, + auth: { + username: config.n8nWebhookUsername, + password: config.n8nWebhookPassword + } + }; + + // Make the request to the webhook + const response = await axios.post( + webhookUrl, + params.data || {}, + requestConfig + ); + + // Return the webhook response + return this.formatSuccess({ + status: response.status, + statusText: response.statusText, + data: response.data + }, 'Webhook executed successfully'); + } catch (error) { + // Handle error from the webhook request + if (axios.isAxiosError(error)) { + let errorMessage = `Webhook execution failed: ${error.message}`; + + if (error.response) { + errorMessage = `Webhook execution failed with status ${error.response.status}: ${error.response.statusText}`; + if (error.response.data) { + return this.formatError(new N8nApiError( + `${errorMessage}\n\n${JSON.stringify(error.response.data, null, 2)}`, + error.response.status + )); + } + } + + return this.formatError(new N8nApiError(errorMessage, error.response?.status || 500)); + } + + throw error; // Re-throw non-axios errors for the handler to catch + } + }, args); + } +} + +/** + * Get the tool definition for run_webhook + * + * @returns Tool definition object + */ +export function getRunWebhookToolDefinition() { + return { + name: 'run_webhook', + description: 'Execute a workflow via webhook with optional input data', + inputSchema: { + type: 'object', + properties: { + workflowName: { + type: 'string', + description: 'Name of the workflow to execute (e.g., "hello-world")' + }, + data: { + type: 'object', + description: 'Input data to pass to the webhook' + }, + headers: { + type: 'object', + description: 'Additional headers to send with the request' + } + }, + required: ['workflowName'] + } + }; +} \ No newline at end of file