Files
n8n-mcp/src/triggers/handlers/base-handler.ts
Romuald Członkowski 33690c5650 feat: rename n8n_trigger_webhook_workflow to n8n_test_workflow with multi-trigger support (#460)
* feat: rename n8n_trigger_webhook_workflow to n8n_test_workflow with multi-trigger support

- Rename tool from n8n_trigger_webhook_workflow to n8n_test_workflow
- Add support for webhook, form, and chat triggers (auto-detection)
- Implement modular trigger system with registry pattern
- Add trigger detector for automatic trigger type inference
- Remove execute trigger type (n8n public API limitation)
- Add comprehensive tests for trigger detection and handlers

The tool now auto-detects trigger type from workflow structure and
supports all externally-triggerable workflows via n8n's public API.

Note: Direct workflow execution (Schedule/Manual triggers) requires
n8n's instance-level MCP access, not available via REST API.

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: add SSRF protection to webhook handler and update tests

- Add SSRF URL validation to webhook-handler.ts (critical security fix)
  Aligns with existing SSRF protection in form-handler.ts and chat-handler.ts
- Update parameter-validation.test.ts to use new n8n_test_workflow tool name

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: n8n_test_workflow unified trigger tool (v2.28.0)

Added new `n8n_test_workflow` tool replacing `n8n_trigger_webhook_workflow`:

Features:
- Auto-detects trigger type (webhook/form/chat) from workflow
- Supports multiple trigger types with type-specific parameters
- SSRF protection for all trigger handlers
- Extensible handler architecture with registry pattern

Changes:
- Fixed Zod schema to remove invalid 'execute' trigger type
- Updated README.md tool documentation
- Added CHANGELOG entry for v2.28.0
- Bumped version to 2.28.0

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test: add comprehensive unit tests for trigger handlers

Added 87 unit tests across 4 test files to improve code coverage:

- base-handler.test.ts (19 tests) - 100% coverage
- webhook-handler.test.ts (22 tests) - 100% coverage
- chat-handler.test.ts (23 tests) - 100% coverage
- form-handler.test.ts (23 tests) - 100% coverage

Tests cover:
- Input validation and parameter handling
- SSRF protection integration
- HTTP method handling and URL building
- Error response formatting
- Execution paths for all trigger types

Conceived by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-01 15:55:14 +01:00

150 lines
3.7 KiB
TypeScript

/**
* Base trigger handler - abstract class for all trigger handlers
*/
import { z } from 'zod';
import { Workflow } from '../../types/n8n-api';
import { InstanceContext } from '../../types/instance-context';
import { N8nApiClient } from '../../services/n8n-api-client';
import { getN8nApiConfig } from '../../config/n8n-api';
import {
TriggerType,
TriggerResponse,
TriggerHandlerCapabilities,
DetectedTrigger,
BaseTriggerInput,
} from '../types';
/**
* Constructor type for trigger handlers
*/
export type TriggerHandlerConstructor = new (
client: N8nApiClient,
context?: InstanceContext
) => BaseTriggerHandler;
/**
* Abstract base class for all trigger handlers
*
* Each handler implements:
* - Input validation via Zod schema
* - Capability declaration (active workflow required, etc.)
* - Execution logic specific to the trigger type
*/
export abstract class BaseTriggerHandler<T extends BaseTriggerInput = BaseTriggerInput> {
protected client: N8nApiClient;
protected context?: InstanceContext;
/** The trigger type this handler supports */
abstract readonly triggerType: TriggerType;
/** Handler capabilities */
abstract readonly capabilities: TriggerHandlerCapabilities;
/** Zod schema for input validation */
abstract readonly inputSchema: z.ZodSchema<T>;
constructor(client: N8nApiClient, context?: InstanceContext) {
this.client = client;
this.context = context;
}
/**
* Validate input against schema
* @throws ZodError if validation fails
*/
validate(input: unknown): T {
return this.inputSchema.parse(input);
}
/**
* Execute the trigger
*
* @param input - Validated trigger input
* @param workflow - The workflow being triggered
* @param triggerInfo - Detected trigger information (may be undefined for 'execute' type)
*/
abstract execute(
input: T,
workflow: Workflow,
triggerInfo?: DetectedTrigger
): Promise<TriggerResponse>;
/**
* Get the n8n instance base URL from context or environment config
*/
protected getBaseUrl(): string | undefined {
// First try context (for multi-tenant scenarios)
if (this.context?.n8nApiUrl) {
return this.context.n8nApiUrl.replace(/\/api\/v1\/?$/, '');
}
// Fallback to environment config
const config = getN8nApiConfig();
if (config?.baseUrl) {
return config.baseUrl.replace(/\/api\/v1\/?$/, '');
}
return undefined;
}
/**
* Get the n8n API key from context or environment config
*/
protected getApiKey(): string | undefined {
// First try context (for multi-tenant scenarios)
if (this.context?.n8nApiKey) {
return this.context.n8nApiKey;
}
// Fallback to environment config
const config = getN8nApiConfig();
return config?.apiKey;
}
/**
* Normalize response to unified format
*/
protected normalizeResponse(
result: unknown,
input: T,
startTime: number,
extra?: Partial<TriggerResponse>
): TriggerResponse {
const endTime = Date.now();
const duration = endTime - startTime;
return {
success: true,
triggerType: this.triggerType,
workflowId: input.workflowId,
data: result,
metadata: {
duration,
},
...extra,
};
}
/**
* Create error response
*/
protected errorResponse(
input: BaseTriggerInput,
error: string,
startTime: number,
extra?: Partial<TriggerResponse>
): TriggerResponse {
const endTime = Date.now();
const duration = endTime - startTime;
return {
success: false,
triggerType: this.triggerType,
workflowId: input.workflowId,
error,
metadata: {
duration,
},
...extra,
};
}
}