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_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 <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_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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ToolDefinition[]> {
|
||||
return [
|
||||
getListExecutionsToolDefinition(),
|
||||
getGetExecutionToolDefinition(),
|
||||
getDeleteExecutionToolDefinition()
|
||||
getDeleteExecutionToolDefinition(),
|
||||
getRunWebhookToolDefinition()
|
||||
];
|
||||
}
|
||||
|
||||
@@ -26,3 +28,4 @@ export async function setupExecutionTools(): Promise<ToolDefinition[]> {
|
||||
export { ListExecutionsHandler } from './list.js';
|
||||
export { GetExecutionHandler } from './get.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