Add run_webhook tool for executing workflows via webhooks
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 <noreply@anthropic.com>
This commit is contained in:
25
README.md
25
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_URL` | URL of the n8n API | `http://localhost:5678/api/v1` |
|
||||||
| `N8N_API_KEY` | API key for authenticating with n8n | `n8n_api_...` |
|
| `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` |
|
| `DEBUG` | Enable debug logging (optional) | `true` or `false` |
|
||||||
|
|
||||||
### Generating an n8n API Key
|
### Generating an n8n API Key
|
||||||
@@ -100,6 +102,26 @@ install_local_mcp_server path/to/n8n-mcp-server
|
|||||||
|
|
||||||
The server provides the following tools:
|
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 <n8n-url>/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 Management
|
||||||
|
|
||||||
- `workflow_list`: List all workflows
|
- `workflow_list`: List all workflows
|
||||||
@@ -112,7 +134,8 @@ The server provides the following tools:
|
|||||||
|
|
||||||
### Execution Management
|
### 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_get`: Get details of a specific execution
|
||||||
- `execution_list`: List executions for a workflow
|
- `execution_list`: List executions for a workflow
|
||||||
- `execution_stop`: Stop a running execution
|
- `execution_stop`: Stop a running execution
|
||||||
|
|||||||
@@ -8,6 +8,73 @@ Execution tools allow AI assistants to execute n8n workflows and manage executio
|
|||||||
|
|
||||||
## Available Tools
|
## 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
|
### execution_run
|
||||||
|
|
||||||
Executes a workflow with optional input data.
|
Executes a workflow with optional input data.
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { ErrorCode } from '../errors/error-codes.js';
|
|||||||
export const ENV_VARS = {
|
export const ENV_VARS = {
|
||||||
N8N_API_URL: 'N8N_API_URL',
|
N8N_API_URL: 'N8N_API_URL',
|
||||||
N8N_API_KEY: 'N8N_API_KEY',
|
N8N_API_KEY: 'N8N_API_KEY',
|
||||||
|
N8N_WEBHOOK_USERNAME: 'N8N_WEBHOOK_USERNAME',
|
||||||
|
N8N_WEBHOOK_PASSWORD: 'N8N_WEBHOOK_PASSWORD',
|
||||||
DEBUG: 'DEBUG',
|
DEBUG: 'DEBUG',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -20,6 +22,8 @@ export const ENV_VARS = {
|
|||||||
export interface EnvConfig {
|
export interface EnvConfig {
|
||||||
n8nApiUrl: string;
|
n8nApiUrl: string;
|
||||||
n8nApiKey: string;
|
n8nApiKey: string;
|
||||||
|
n8nWebhookUsername: string;
|
||||||
|
n8nWebhookPassword: string;
|
||||||
debug: boolean;
|
debug: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +43,8 @@ export function loadEnvironmentVariables(): void {
|
|||||||
export function getEnvConfig(): EnvConfig {
|
export function getEnvConfig(): EnvConfig {
|
||||||
const n8nApiUrl = process.env[ENV_VARS.N8N_API_URL];
|
const n8nApiUrl = process.env[ENV_VARS.N8N_API_URL];
|
||||||
const n8nApiKey = process.env[ENV_VARS.N8N_API_KEY];
|
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';
|
const debug = process.env[ENV_VARS.DEBUG]?.toLowerCase() === 'true';
|
||||||
|
|
||||||
// Validate required environment variables
|
// 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
|
// Validate URL format
|
||||||
try {
|
try {
|
||||||
new URL(n8nApiUrl);
|
new URL(n8nApiUrl);
|
||||||
@@ -69,6 +89,8 @@ export function getEnvConfig(): EnvConfig {
|
|||||||
return {
|
return {
|
||||||
n8nApiUrl,
|
n8nApiUrl,
|
||||||
n8nApiKey,
|
n8nApiKey,
|
||||||
|
n8nWebhookUsername,
|
||||||
|
n8nWebhookPassword,
|
||||||
debug,
|
debug,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ function setupToolCallRequestHandler(server: Server): void {
|
|||||||
const {
|
const {
|
||||||
ListExecutionsHandler,
|
ListExecutionsHandler,
|
||||||
GetExecutionHandler,
|
GetExecutionHandler,
|
||||||
DeleteExecutionHandler
|
DeleteExecutionHandler,
|
||||||
|
RunWebhookHandler
|
||||||
} = await import('../tools/execution/index.js');
|
} = await import('../tools/execution/index.js');
|
||||||
|
|
||||||
// Route the tool call to the appropriate handler
|
// Route the tool call to the appropriate handler
|
||||||
@@ -144,6 +145,9 @@ function setupToolCallRequestHandler(server: Server): void {
|
|||||||
} else if (toolName === 'delete_execution') {
|
} else if (toolName === 'delete_execution') {
|
||||||
const handler = new DeleteExecutionHandler();
|
const handler = new DeleteExecutionHandler();
|
||||||
result = await handler.execute(args);
|
result = await handler.execute(args);
|
||||||
|
} else if (toolName === 'run_webhook') {
|
||||||
|
const handler = new RunWebhookHandler();
|
||||||
|
result = await handler.execute(args);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown tool: ${toolName}`);
|
throw new Error(`Unknown tool: ${toolName}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import { N8nApiError, getErrorMessage } from '../../errors/index.js';
|
|||||||
import {
|
import {
|
||||||
ListExecutionsHandler,
|
ListExecutionsHandler,
|
||||||
GetExecutionHandler,
|
GetExecutionHandler,
|
||||||
DeleteExecutionHandler
|
DeleteExecutionHandler,
|
||||||
|
RunWebhookHandler
|
||||||
} from './index.js';
|
} from './index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -37,6 +38,9 @@ export default async function executionHandler(
|
|||||||
case 'delete_execution':
|
case 'delete_execution':
|
||||||
return await new DeleteExecutionHandler().execute(args);
|
return await new DeleteExecutionHandler().execute(args);
|
||||||
|
|
||||||
|
case 'run_webhook':
|
||||||
|
return await new RunWebhookHandler().execute(args);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new McpError(
|
throw new McpError(
|
||||||
ErrorCode.NotImplemented,
|
ErrorCode.NotImplemented,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { ToolDefinition } from '../../types/index.js';
|
|||||||
import { getListExecutionsToolDefinition } from './list.js';
|
import { getListExecutionsToolDefinition } from './list.js';
|
||||||
import { getGetExecutionToolDefinition } from './get.js';
|
import { getGetExecutionToolDefinition } from './get.js';
|
||||||
import { getDeleteExecutionToolDefinition } from './delete.js';
|
import { getDeleteExecutionToolDefinition } from './delete.js';
|
||||||
|
import { getRunWebhookToolDefinition } from './run.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up execution management tools
|
* Set up execution management tools
|
||||||
@@ -18,7 +19,8 @@ export async function setupExecutionTools(): Promise<ToolDefinition[]> {
|
|||||||
return [
|
return [
|
||||||
getListExecutionsToolDefinition(),
|
getListExecutionsToolDefinition(),
|
||||||
getGetExecutionToolDefinition(),
|
getGetExecutionToolDefinition(),
|
||||||
getDeleteExecutionToolDefinition()
|
getDeleteExecutionToolDefinition(),
|
||||||
|
getRunWebhookToolDefinition()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,3 +28,4 @@ export async function setupExecutionTools(): Promise<ToolDefinition[]> {
|
|||||||
export { ListExecutionsHandler } from './list.js';
|
export { ListExecutionsHandler } from './list.js';
|
||||||
export { GetExecutionHandler } from './get.js';
|
export { GetExecutionHandler } from './get.js';
|
||||||
export { DeleteExecutionHandler } from './delete.js';
|
export { DeleteExecutionHandler } from './delete.js';
|
||||||
|
export { RunWebhookHandler } from './run.js';
|
||||||
|
|||||||
154
src/tools/execution/run.ts
Normal file
154
src/tools/execution/run.ts
Normal file
@@ -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<typeof runWebhookSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string, any>): Promise<ToolCallResult> {
|
||||||
|
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']
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user