UNPKG

@venly/wallet-mcp

Version:

Production-ready MCP server enabling AI agents to perform Web3 wallet operations through Venly's blockchain infrastructure. Supports 14+ networks including Ethereum, Polygon, Arbitrum, and stablecoin operations.

354 lines 13.5 kB
/** * Prometheus Metrics Export for Grafana Integration * * Provides /metrics endpoint for existing Grafana monitoring */ import { VenlyClient } from '../venly/VenlyClient.js'; import winston from 'winston'; /** * Prometheus Metrics Service */ export class PrometheusMetricsService { startTime; // Metrics storage metrics = new Map(); // Counters requestCount = 0; successCount = 0; errorCount = 0; toolUsageCount = new Map(); // Response time tracking responseTimes = []; venlyApiResponseTimes = []; constructor(_venlyClient, _logger) { // Parameters kept for future use but prefixed with underscore to indicate intentionally unused this.startTime = Date.now(); this.initializeMetrics(); } /** * Initialize default metrics */ initializeMetrics() { // Server uptime this.metrics.set('venly_mcp_server_uptime_seconds', { name: 'venly_mcp_server_uptime_seconds', help: 'Server uptime in seconds', type: 'gauge', value: 0 }); // Request metrics this.metrics.set('venly_mcp_requests_total', { name: 'venly_mcp_requests_total', help: 'Total number of MCP requests', type: 'counter', value: 0 }); this.metrics.set('venly_mcp_requests_success_total', { name: 'venly_mcp_requests_success_total', help: 'Total number of successful MCP requests', type: 'counter', value: 0 }); this.metrics.set('venly_mcp_requests_error_total', { name: 'venly_mcp_requests_error_total', help: 'Total number of failed MCP requests', type: 'counter', value: 0 }); // Response time metrics this.metrics.set('venly_mcp_request_duration_seconds', { name: 'venly_mcp_request_duration_seconds', help: 'Average MCP request duration in seconds', type: 'gauge', value: 0 }); // Venly API metrics this.metrics.set('venly_api_requests_total', { name: 'venly_api_requests_total', help: 'Total number of Venly API requests', type: 'counter', value: 0 }); this.metrics.set('venly_api_response_time_seconds', { name: 'venly_api_response_time_seconds', help: 'Average Venly API response time in seconds', type: 'gauge', value: 0 }); // Memory metrics this.metrics.set('venly_mcp_memory_usage_bytes', { name: 'venly_mcp_memory_usage_bytes', help: 'Memory usage in bytes', type: 'gauge', value: 0 }); this.metrics.set('venly_mcp_memory_usage_percentage', { name: 'venly_mcp_memory_usage_percentage', help: 'Memory usage percentage', type: 'gauge', value: 0 }); // Authentication status this.metrics.set('venly_api_authenticated', { name: 'venly_api_authenticated', help: 'Venly API authentication status (1 = authenticated, 0 = not authenticated)', type: 'gauge', value: 0 }); } /** * Update server uptime metric */ updateUptimeMetric() { const uptime = Math.floor((Date.now() - this.startTime) / 1000); const metric = this.metrics.get('venly_mcp_server_uptime_seconds'); if (metric) { metric.value = uptime; } } /** * Update memory metrics */ updateMemoryMetrics() { const memoryUsage = process.memoryUsage(); const memoryUsageMetric = this.metrics.get('venly_mcp_memory_usage_bytes'); if (memoryUsageMetric) { memoryUsageMetric.value = memoryUsage.heapUsed; } const memoryPercentageMetric = this.metrics.get('venly_mcp_memory_usage_percentage'); if (memoryPercentageMetric) { memoryPercentageMetric.value = Math.round((memoryUsage.heapUsed / memoryUsage.heapTotal) * 100); } } /** * Update authentication status metric */ updateAuthMetric() { try { // Note: getAuthStatus method not available in new authentication model const authStatus = { isAuthenticated: true, status: 'OK' }; const authMetric = this.metrics.get('venly_api_authenticated'); if (authMetric) { authMetric.value = authStatus.isAuthenticated ? 1 : 0; } } catch (error) { const authMetric = this.metrics.get('venly_api_authenticated'); if (authMetric) { authMetric.value = 0; } } } /** * Record MCP request */ recordRequest(toolName, responseTime, success) { this.requestCount++; this.responseTimes.push(responseTime); if (success) { this.successCount++; } else { this.errorCount++; } // Track tool usage const currentCount = this.toolUsageCount.get(toolName) || 0; this.toolUsageCount.set(toolName, currentCount + 1); // Update metrics const requestsMetric = this.metrics.get('venly_mcp_requests_total'); if (requestsMetric) { requestsMetric.value = this.requestCount; } const successMetric = this.metrics.get('venly_mcp_requests_success_total'); if (successMetric) { successMetric.value = this.successCount; } const errorMetric = this.metrics.get('venly_mcp_requests_error_total'); if (errorMetric) { errorMetric.value = this.errorCount; } // Update average response time const avgResponseTime = this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length; const responseTimeMetric = this.metrics.get('venly_mcp_request_duration_seconds'); if (responseTimeMetric) { responseTimeMetric.value = avgResponseTime / 1000; // Convert to seconds } // Keep only last 1000 response times to prevent memory growth if (this.responseTimes.length > 1000) { this.responseTimes = this.responseTimes.slice(-1000); } } /** * Record Venly API request */ recordVenlyApiRequest(responseTime) { this.venlyApiResponseTimes.push(responseTime); const apiRequestsMetric = this.metrics.get('venly_api_requests_total'); if (apiRequestsMetric) { apiRequestsMetric.value++; } // Update average Venly API response time const avgResponseTime = this.venlyApiResponseTimes.reduce((sum, time) => sum + time, 0) / this.venlyApiResponseTimes.length; const apiResponseTimeMetric = this.metrics.get('venly_api_response_time_seconds'); if (apiResponseTimeMetric) { apiResponseTimeMetric.value = avgResponseTime / 1000; // Convert to seconds } // Keep only last 1000 response times if (this.venlyApiResponseTimes.length > 1000) { this.venlyApiResponseTimes = this.venlyApiResponseTimes.slice(-1000); } } /** * Get tool usage metrics */ getToolUsageMetrics() { const toolMetrics = []; for (const [toolName, count] of this.toolUsageCount.entries()) { toolMetrics.push({ name: 'venly_mcp_tool_usage_total', help: 'Total usage count per MCP tool', type: 'counter', value: count, labels: { tool: toolName } }); } return toolMetrics; } /** * Generate Prometheus metrics format */ generateMetrics() { // Update dynamic metrics this.updateUptimeMetric(); this.updateMemoryMetrics(); this.updateAuthMetric(); const lines = []; // Add standard metrics for (const metric of this.metrics.values()) { lines.push(`# HELP ${metric.name} ${metric.help}`); lines.push(`# TYPE ${metric.name} ${metric.type}`); if (metric.labels) { const labelStr = Object.entries(metric.labels) .map(([key, value]) => `${key}="${value}"`) .join(','); lines.push(`${metric.name}{${labelStr}} ${metric.value}`); } else { lines.push(`${metric.name} ${metric.value}`); } lines.push(''); } // Add tool usage metrics const toolMetrics = this.getToolUsageMetrics(); if (toolMetrics.length > 0) { lines.push('# HELP venly_mcp_tool_usage_total Total usage count per MCP tool'); lines.push('# TYPE venly_mcp_tool_usage_total counter'); for (const toolMetric of toolMetrics) { if (toolMetric.labels) { const labelStr = Object.entries(toolMetric.labels) .map(([key, value]) => `${key}="${value}"`) .join(','); lines.push(`${toolMetric.name}{${labelStr}} ${toolMetric.value}`); } } lines.push(''); } // Add Node.js process metrics const processMetrics = this.getProcessMetrics(); lines.push(...processMetrics); return lines.join('\n'); } /** * Get Node.js process metrics */ getProcessMetrics() { const lines = []; const memoryUsage = process.memoryUsage(); const cpuUsage = process.cpuUsage(); // Process memory metrics lines.push('# HELP process_resident_memory_bytes Resident memory size in bytes'); lines.push('# TYPE process_resident_memory_bytes gauge'); lines.push(`process_resident_memory_bytes ${memoryUsage.rss}`); lines.push(''); lines.push('# HELP process_heap_bytes Process heap size in bytes'); lines.push('# TYPE process_heap_bytes gauge'); lines.push(`process_heap_bytes ${memoryUsage.heapTotal}`); lines.push(''); lines.push('# HELP process_heap_used_bytes Process heap used in bytes'); lines.push('# TYPE process_heap_used_bytes gauge'); lines.push(`process_heap_used_bytes ${memoryUsage.heapUsed}`); lines.push(''); // Process CPU metrics lines.push('# HELP process_cpu_user_seconds_total Total user CPU time spent in seconds'); lines.push('# TYPE process_cpu_user_seconds_total counter'); lines.push(`process_cpu_user_seconds_total ${cpuUsage.user / 1000000}`); lines.push(''); lines.push('# HELP process_cpu_system_seconds_total Total system CPU time spent in seconds'); lines.push('# TYPE process_cpu_system_seconds_total counter'); lines.push(`process_cpu_system_seconds_total ${cpuUsage.system / 1000000}`); lines.push(''); // Process uptime lines.push('# HELP process_start_time_seconds Start time of the process since unix epoch in seconds'); lines.push('# TYPE process_start_time_seconds gauge'); lines.push(`process_start_time_seconds ${Math.floor(this.startTime / 1000)}`); lines.push(''); return lines; } /** * Get metrics summary for health checks */ getMetricsSummary() { const uptime = Math.floor((Date.now() - this.startTime) / 1000); const successRate = this.requestCount > 0 ? (this.successCount / this.requestCount) * 100 : 0; const errorRate = this.requestCount > 0 ? (this.errorCount / this.requestCount) * 100 : 0; const avgResponseTime = this.responseTimes.length > 0 ? this.responseTimes.reduce((sum, time) => sum + time, 0) / this.responseTimes.length : 0; const memoryUsage = process.memoryUsage(); const memoryPercentage = (memoryUsage.heapUsed / memoryUsage.heapTotal) * 100; return { totalRequests: this.requestCount, successRate: Math.round(successRate * 100) / 100, errorRate: Math.round(errorRate * 100) / 100, averageResponseTime: Math.round(avgResponseTime * 100) / 100, memoryUsage: Math.round(memoryPercentage * 100) / 100, uptime }; } } /** * Metrics endpoint handler */ export class MetricsRoutes { metricsService; constructor(metricsService) { this.metricsService = metricsService; } /** * GET /metrics - Prometheus metrics endpoint */ async handleMetrics() { try { const metrics = this.metricsService.generateMetrics(); return { statusCode: 200, headers: { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }, body: metrics }; } catch (error) { return { statusCode: 500, headers: { 'Content-Type': 'text/plain' }, body: '# Error generating metrics\n' }; } } } //# sourceMappingURL=prometheus.js.map