UNPKG

auto-publishing-mcp-server

Version:

Enterprise-grade MCP Server for Auto-Publishing with pre-publish validation, multi-cloud deployment, and monitoring

331 lines (285 loc) 9.03 kB
/** * Prometheus Metrics Collection for MCP Server * Exposes metrics for monitoring MCP tool performance and system health */ export class PrometheusMetrics { constructor() { this.metrics = new Map(); this.counters = new Map(); this.histograms = new Map(); this.gauges = new Map(); this.initializeMetrics(); } /** * Initialize default metrics */ initializeMetrics() { // MCP Server metrics this.counters.set('mcp_requests_total', { value: 0, help: 'Total number of MCP requests', labels: [] }); this.counters.set('mcp_tool_calls_total', { value: 0, help: 'Total number of MCP tool calls', labels: [] }); this.counters.set('mcp_tool_errors_total', { value: 0, help: 'Total number of MCP tool errors', labels: [] }); this.counters.set('mcp_deployment_total', { value: 0, help: 'Total number of deployments initiated', labels: [] }); this.counters.set('mcp_deployment_failures_total', { value: 0, help: 'Total number of failed deployments', labels: [] }); // Gauges for current state this.gauges.set('mcp_active_sessions', { value: 0, help: 'Number of active MCP sessions' }); this.gauges.set('mcp_registered_tools', { value: 0, help: 'Number of registered MCP tools' }); this.gauges.set('mcp_allocated_resources', { value: 0, help: 'Number of allocated deployment resources' }); this.gauges.set('mcp_deployment_duration_seconds', { value: 0, help: 'Duration of current deployment in seconds' }); // System metrics this.gauges.set('mcp_server_uptime_seconds', { value: 0, help: 'MCP server uptime in seconds' }); // Initialize histograms for request duration this.histograms.set('mcp_request_duration_seconds', { buckets: [0.1, 0.25, 0.5, 1, 2.5, 5, 10], values: new Map(), help: 'MCP request duration in seconds' }); this.histograms.set('mcp_tool_execution_duration_seconds', { buckets: [0.1, 0.5, 1, 2, 5, 10, 30, 60], values: new Map(), help: 'MCP tool execution duration in seconds' }); console.log('📊 Prometheus metrics initialized'); } /** * Increment a counter metric */ incrementCounter(name, labels = {}, value = 1) { const metric = this.counters.get(name); if (metric) { const labelKey = this.getLabelKey(labels); if (!metric.labels[labelKey]) { metric.labels[labelKey] = { labels, value: 0 }; } metric.labels[labelKey].value += value; metric.value += value; } } /** * Set a gauge metric value */ setGauge(name, value, labels = {}) { const metric = this.gauges.get(name); if (metric) { metric.value = value; if (Object.keys(labels).length > 0) { if (!metric.labels) metric.labels = []; const labelKey = this.getLabelKey(labels); metric.labels[labelKey] = { labels, value }; } } } /** * Observe a histogram metric */ observeHistogram(name, value, labels = {}) { const histogram = this.histograms.get(name); if (histogram) { const labelKey = this.getLabelKey(labels); if (!histogram.values.has(labelKey)) { histogram.values.set(labelKey, { labels, buckets: new Map(histogram.buckets.map(bucket => [bucket, 0])), sum: 0, count: 0 }); } const entry = histogram.values.get(labelKey); entry.sum += value; entry.count += 1; // Update buckets histogram.buckets.forEach(bucket => { if (value <= bucket) { entry.buckets.set(bucket, entry.buckets.get(bucket) + 1); } }); } } /** * Get label key for grouping */ getLabelKey(labels) { return Object.entries(labels) .sort(([a], [b]) => a.localeCompare(b)) .map(([key, value]) => `${key}="${value}"`) .join(','); } /** * Record MCP request */ recordRequest(method, duration, success = true) { this.incrementCounter('mcp_requests_total', { method, status: success ? 'success' : 'error' }); this.observeHistogram('mcp_request_duration_seconds', duration, { method }); } /** * Record MCP tool call */ recordToolCall(toolName, duration, success = true) { this.incrementCounter('mcp_tool_calls_total', { tool: toolName, status: success ? 'success' : 'error' }); if (!success) { this.incrementCounter('mcp_tool_errors_total', { tool: toolName }); } this.observeHistogram('mcp_tool_execution_duration_seconds', duration, { tool: toolName }); } /** * Record deployment metrics */ recordDeployment(environment, success = true, duration = 0) { this.incrementCounter('mcp_deployment_total', { environment, status: success ? 'success' : 'failure' }); if (!success) { this.incrementCounter('mcp_deployment_failures_total', { environment }); } if (duration > 0) { this.observeHistogram('mcp_tool_execution_duration_seconds', duration, { tool: 'deploy/to-environment', environment }); } } /** * Update server state metrics */ updateServerMetrics(activeSessions, registeredTools, allocatedResources, uptimeSeconds) { this.setGauge('mcp_active_sessions', activeSessions); this.setGauge('mcp_registered_tools', registeredTools); this.setGauge('mcp_allocated_resources', allocatedResources); this.setGauge('mcp_server_uptime_seconds', uptimeSeconds); } /** * Set deployment duration */ setDeploymentDuration(duration) { this.setGauge('mcp_deployment_duration_seconds', duration); } /** * Generate Prometheus exposition format */ generatePrometheusFormat() { let output = ''; // Generate counters for (const [name, metric] of this.counters) { output += `# HELP ${name} ${metric.help}\n`; output += `# TYPE ${name} counter\n`; if (metric.labels.length === 0) { output += `${name} ${metric.value}\n`; } else { Object.values(metric.labels).forEach(entry => { const labelStr = Object.entries(entry.labels) .map(([key, value]) => `${key}="${value}"`) .join(','); output += `${name}{${labelStr}} ${entry.value}\n`; }); } output += '\n'; } // Generate gauges for (const [name, metric] of this.gauges) { output += `# HELP ${name} ${metric.help}\n`; output += `# TYPE ${name} gauge\n`; if (!metric.labels) { output += `${name} ${metric.value}\n`; } else { Object.values(metric.labels).forEach(entry => { const labelStr = Object.entries(entry.labels) .map(([key, value]) => `${key}="${value}"`) .join(','); output += `${name}{${labelStr}} ${entry.value}\n`; }); } output += '\n'; } // Generate histograms for (const [name, histogram] of this.histograms) { output += `# HELP ${name} ${histogram.help}\n`; output += `# TYPE ${name} histogram\n`; for (const [labelKey, entry] of histogram.values) { const labelStr = Object.entries(entry.labels) .map(([key, value]) => `${key}="${value}"`) .join(','); const labelPrefix = labelStr ? `{${labelStr},` : '{'; // Bucket counts entry.buckets.forEach((count, bucket) => { output += `${name}_bucket${labelPrefix}le="${bucket}"} ${count}\n`; }); output += `${name}_bucket${labelPrefix}le="+Inf"} ${entry.count}\n`; // Sum and count output += `${name}_sum{${labelStr}} ${entry.sum}\n`; output += `${name}_count{${labelStr}} ${entry.count}\n`; } output += '\n'; } return output; } /** * Get metrics summary for debugging */ getSummary() { return { counters: Object.fromEntries( Array.from(this.counters.entries()).map(([name, metric]) => [name, metric.value]) ), gauges: Object.fromEntries( Array.from(this.gauges.entries()).map(([name, metric]) => [name, metric.value]) ), histograms: Object.fromEntries( Array.from(this.histograms.entries()).map(([name, histogram]) => [ name, Array.from(histogram.values.values()).map(entry => ({ labels: entry.labels, count: entry.count, sum: entry.sum })) ]) ) }; } /** * Reset all metrics (for testing) */ reset() { this.counters.forEach(metric => { metric.value = 0; metric.labels = []; }); this.gauges.forEach(metric => { metric.value = 0; delete metric.labels; }); this.histograms.forEach(histogram => { histogram.values.clear(); }); console.log('📊 Metrics reset'); } } export default PrometheusMetrics;