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