feat: add anonymous telemetry system with Supabase integration

- Implement telemetry manager for tracking tool usage and workflows
- Add workflow sanitizer to remove sensitive data before storage
- Create config manager with opt-in/opt-out mechanism
- Integrate telemetry tracking into MCP server and workflow handlers
- Add CLI commands for telemetry control (enable/disable/status)
- Show first-run notice with clear privacy information
- Add comprehensive unit tests for sanitization and config
- Track tool usage metrics, workflow patterns, and errors
- Ensure complete anonymity with deterministic user IDs
- Never collect URLs, API keys, or sensitive information
This commit is contained in:
czlonkowski
2025-09-25 13:33:16 +02:00
parent 78abda601a
commit 5960d2826e
12 changed files with 1569 additions and 30 deletions

View File

@@ -27,6 +27,7 @@ import { InstanceContext, validateInstanceContext } from '../types/instance-cont
import { WorkflowAutoFixer, AutoFixConfig } from '../services/workflow-auto-fixer';
import { ExpressionFormatValidator } from '../services/expression-format-validator';
import { handleUpdatePartialWorkflow } from './handlers-workflow-diff';
import { telemetry } from '../telemetry';
import {
createCacheKey,
createInstanceCache,
@@ -280,16 +281,22 @@ export async function handleCreateWorkflow(args: unknown, context?: InstanceCont
// Validate workflow structure
const errors = validateWorkflowStructure(input);
if (errors.length > 0) {
// Track validation failure
telemetry.trackWorkflowCreation(input, false);
return {
success: false,
error: 'Workflow validation failed',
details: { errors }
};
}
// Create workflow
const workflow = await client.createWorkflow(input);
// Track successful workflow creation
telemetry.trackWorkflowCreation(workflow, true);
return {
success: true,
data: workflow,

View File

@@ -2,6 +2,7 @@
import { N8NDocumentationMCPServer } from './server';
import { logger } from '../utils/logger';
import { TelemetryConfigManager } from '../telemetry/config-manager';
// Add error details to stderr for Claude Desktop debugging
process.on('uncaughtException', (error) => {
@@ -21,8 +22,42 @@ process.on('unhandledRejection', (reason, promise) => {
});
async function main() {
// Handle telemetry CLI commands
const args = process.argv.slice(2);
if (args.length > 0 && args[0] === 'telemetry') {
const telemetryConfig = TelemetryConfigManager.getInstance();
const action = args[1];
switch (action) {
case 'enable':
telemetryConfig.enable();
process.exit(0);
break;
case 'disable':
telemetryConfig.disable();
process.exit(0);
break;
case 'status':
console.log(telemetryConfig.getStatus());
process.exit(0);
break;
default:
console.log(`
Usage: n8n-mcp telemetry [command]
Commands:
enable Enable anonymous telemetry
disable Disable anonymous telemetry
status Show current telemetry status
Learn more: https://github.com/czlonkowski/n8n-mcp/privacy
`);
process.exit(args[1] ? 1 : 0);
}
}
const mode = process.env.MCP_MODE || 'stdio';
try {
// Only show debug messages in HTTP mode to avoid corrupting stdio communication
if (mode === 'http') {

View File

@@ -35,6 +35,7 @@ import {
STANDARD_PROTOCOL_VERSION
} from '../utils/protocol-version';
import { InstanceContext } from '../types/instance-context';
import { telemetry } from '../telemetry';
interface NodeRow {
node_type: string;
@@ -180,7 +181,10 @@ export class N8NDocumentationMCPServer {
clientCapabilities,
clientInfo
});
// Track session start
telemetry.trackSessionStart();
// Store client info for later use
this.clientInfo = clientInfo;
@@ -322,8 +326,13 @@ export class N8NDocumentationMCPServer {
try {
logger.debug(`Executing tool: ${name}`, { args: processedArgs });
const startTime = Date.now();
const result = await this.executeTool(name, processedArgs);
const duration = Date.now() - startTime;
logger.debug(`Tool ${name} executed successfully`);
// Track tool usage
telemetry.trackToolUsage(name, true, duration);
// Ensure the result is properly formatted for MCP
let responseText: string;
@@ -370,7 +379,15 @@ export class N8NDocumentationMCPServer {
} catch (error) {
logger.error(`Error executing tool ${name}`, error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
// Track tool error
telemetry.trackToolUsage(name, false);
telemetry.trackError(
error instanceof Error ? error.constructor.name : 'UnknownError',
`tool_execution`,
name
);
// Provide more helpful error messages for common n8n issues
let helpfulMessage = `Error executing tool ${name}: ${errorMessage}`;