@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
JavaScript
/**
* 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