Skip to main content
Glama

CTS MCP Server

by EricA1019
index.js9.3 kB
/** * Observability Module - Structured Logging and Metrics * * Provides: * - Structured logging with severity levels * - Performance metrics tracking * - Tool execution monitoring * - Cache statistics logging * - Error tracking and reporting */ export var LogLevel; (function (LogLevel) { LogLevel["DEBUG"] = "debug"; LogLevel["INFO"] = "info"; LogLevel["WARN"] = "warn"; LogLevel["ERROR"] = "error"; })(LogLevel || (LogLevel = {})); /** * Logger with structured output */ export class Logger { minLevel; context; constructor(minLevel = LogLevel.INFO, context = {}) { this.minLevel = this.getLogLevelFromEnv() || minLevel; this.context = context; } getLogLevelFromEnv() { const level = process.env.LOG_LEVEL?.toLowerCase(); if (level && Object.values(LogLevel).includes(level)) { return level; } return null; } shouldLog(level) { const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR]; return levels.indexOf(level) >= levels.indexOf(this.minLevel); } formatLog(level, message, context) { const timestamp = new Date().toISOString(); const mergedContext = { ...this.context, ...context }; const logData = { timestamp, level, message, ...mergedContext, }; // JSON format for machine parsing if (process.env.LOG_FORMAT === 'json') { return JSON.stringify(logData); } // Human-readable format with context const hasContext = Object.keys(mergedContext).length > 0; const contextStr = hasContext ? ` ${JSON.stringify(mergedContext)}` : ''; return `[${timestamp}] ${level.toUpperCase()} ${message}${contextStr}`; } debug(message, context) { if (this.shouldLog(LogLevel.DEBUG)) { console.debug(this.formatLog(LogLevel.DEBUG, message, context)); } } info(message, context) { if (this.shouldLog(LogLevel.INFO)) { console.info(this.formatLog(LogLevel.INFO, message, context)); } } warn(message, context) { if (this.shouldLog(LogLevel.WARN)) { console.warn(this.formatLog(LogLevel.WARN, message, context)); } } error(message, context) { if (this.shouldLog(LogLevel.ERROR)) { console.error(this.formatLog(LogLevel.ERROR, message, context)); } } child(context) { return new Logger(this.minLevel, { ...this.context, ...context }); } } /** * Metrics collector */ export class MetricsCollector { metrics = new Map(); toolMetrics = new Map(); enabled; constructor(enabled = true) { this.enabled = enabled; } recordMetric(name, value, unit = '', tags) { if (!this.enabled) return; const metric = { name, value, unit, timestamp: Date.now(), tags, }; if (!this.metrics.has(name)) { this.metrics.set(name, []); } this.metrics.get(name).push(metric); // Keep only last 1000 metrics per name to prevent memory leaks const metrics = this.metrics.get(name); if (metrics.length > 1000) { metrics.shift(); } } recordToolExecution(toolName, duration, success, cacheHit = false) { if (!this.enabled) return; let metrics = this.toolMetrics.get(toolName); if (!metrics) { metrics = { toolName, executionCount: 0, totalDuration: 0, averageDuration: 0, minDuration: Infinity, maxDuration: 0, errorCount: 0, cacheHitRate: 0, lastExecuted: 0, }; this.toolMetrics.set(toolName, metrics); } metrics.executionCount++; metrics.totalDuration += duration; metrics.averageDuration = metrics.totalDuration / metrics.executionCount; metrics.minDuration = Math.min(metrics.minDuration, duration); metrics.maxDuration = Math.max(metrics.maxDuration, duration); if (!success) metrics.errorCount++; metrics.lastExecuted = Date.now(); // Update cache hit rate const cacheHits = cacheHit ? 1 : 0; const totalRequests = metrics.executionCount; metrics.cacheHitRate = (metrics.cacheHitRate * (totalRequests - 1) + cacheHits) / totalRequests; // Record as time-series metric this.recordMetric(`tool.${toolName}.duration`, duration, 'ms', { success: success.toString(), cacheHit: cacheHit.toString(), }); } getMetrics(name) { return this.metrics.get(name) || []; } getToolMetrics(toolName) { if (toolName) { return this.toolMetrics.get(toolName) || this.createEmptyToolMetrics(toolName); } return Array.from(this.toolMetrics.values()); } getAllMetrics() { const result = {}; this.metrics.forEach((value, key) => { result[key] = value; }); return result; } getSummary() { const tools = Array.from(this.toolMetrics.values()); const totalExecutions = tools.reduce((sum, t) => sum + t.executionCount, 0); const totalErrors = tools.reduce((sum, t) => sum + t.errorCount, 0); const avgCacheHitRate = tools.reduce((sum, t) => sum + t.cacheHitRate, 0) / (tools.length || 1); return { totalTools: tools.length, totalExecutions, totalErrors, averageCacheHitRate: avgCacheHitRate, toolSummaries: tools, }; } reset() { this.metrics.clear(); this.toolMetrics.clear(); } createEmptyToolMetrics(toolName) { return { toolName, executionCount: 0, totalDuration: 0, averageDuration: 0, minDuration: 0, maxDuration: 0, errorCount: 0, cacheHitRate: 0, lastExecuted: 0, }; } } /** * Global logger instance */ export const logger = new Logger(process.env.NODE_ENV === 'development' ? LogLevel.DEBUG : LogLevel.INFO, { service: 'cts-mcp-server', version: '3.0.0' }); /** * Global metrics collector */ export const metrics = new MetricsCollector(true); /** * Performance monitoring decorator */ export function monitored(toolName) { return function (target, propertyKey, descriptor) { const originalMethod = descriptor.value; descriptor.value = async function (...args) { const startTime = performance.now(); const log = logger.child({ toolName, operation: propertyKey }); log.debug(`Starting ${propertyKey}`, { args: args.length }); try { const result = await originalMethod.apply(this, args); const duration = performance.now() - startTime; metrics.recordToolExecution(toolName, duration, true); log.info(`Completed ${propertyKey}`, { duration }); return result; } catch (error) { const duration = performance.now() - startTime; metrics.recordToolExecution(toolName, duration, false); log.error(`Failed ${propertyKey}`, { duration, error: error instanceof Error ? error.message : String(error), }); throw error; } }; return descriptor; }; } /** * Export metrics in Prometheus format */ export function exportPrometheusMetrics() { const summary = metrics.getSummary(); const lines = []; // Help and type declarations lines.push('# HELP cts_tool_executions_total Total number of tool executions'); lines.push('# TYPE cts_tool_executions_total counter'); lines.push('# HELP cts_tool_duration_seconds Tool execution duration in seconds'); lines.push('# TYPE cts_tool_duration_seconds gauge'); lines.push('# HELP cts_tool_errors_total Total number of tool errors'); lines.push('# TYPE cts_tool_errors_total counter'); lines.push('# HELP cts_tool_cache_hit_rate Cache hit rate for tool'); lines.push('# TYPE cts_tool_cache_hit_rate gauge'); // Metrics for each tool summary.toolSummaries.forEach(tool => { lines.push(`cts_tool_executions_total{tool="${tool.toolName}"} ${tool.executionCount}`); lines.push(`cts_tool_duration_seconds{tool="${tool.toolName}",stat="avg"} ${(tool.averageDuration / 1000).toFixed(6)}`); lines.push(`cts_tool_duration_seconds{tool="${tool.toolName}",stat="min"} ${(tool.minDuration / 1000).toFixed(6)}`); lines.push(`cts_tool_duration_seconds{tool="${tool.toolName}",stat="max"} ${(tool.maxDuration / 1000).toFixed(6)}`); lines.push(`cts_tool_errors_total{tool="${tool.toolName}"} ${tool.errorCount}`); lines.push(`cts_tool_cache_hit_rate{tool="${tool.toolName}"} ${tool.cacheHitRate.toFixed(4)}`); }); return lines.join('\n'); } //# sourceMappingURL=index.js.map

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/EricA1019/CTS_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server