Skip to main content
Glama

Stampchain MCP Server

Official
connection.ts6.44 kB
/** * Connection Handler * Manages client connections and session state */ import { EventEmitter } from 'events'; import type { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { createLogger, type Logger } from '../utils/logger.js'; import type { ServerConfig } from '../config/index.js'; export interface ConnectionInfo { id: string; connectedAt: Date; lastActivity: Date; requestCount: number; transport: 'stdio' | 'websocket' | 'http'; } export interface ConnectionEvents { connect: (info: ConnectionInfo) => void; disconnect: (id: string) => void; error: (id: string, error: Error) => void; activity: (id: string) => void; } export interface ConnectionHandlerOptions { config: ServerConfig; logger?: Logger; maxConnections?: number; sessionTimeout?: number; } /** * Manages client connections and sessions */ export class ConnectionHandler extends EventEmitter { private connections: Map<string, ConnectionInfo> = new Map(); private logger: Logger; private config: ServerConfig; private maxConnections: number; private sessionTimeout: number; private cleanupInterval?: NodeJS.Timeout; constructor(options: ConnectionHandlerOptions) { super(); this.config = options.config; this.logger = options.logger || createLogger('connection', { level: options.config.logging.level, }); this.maxConnections = options.maxConnections || 100; this.sessionTimeout = options.sessionTimeout || 3600000; // 1 hour default // Start cleanup interval this.startCleanupInterval(); } /** * Register a new connection */ async registerConnection(transport: StdioServerTransport): Promise<string> { // Check connection limit if (this.connections.size >= this.maxConnections) { throw new Error(`Maximum connections (${this.maxConnections}) reached`); } // Generate unique connection ID const id = this.generateConnectionId(); // Create connection info const info: ConnectionInfo = { id, connectedAt: new Date(), lastActivity: new Date(), requestCount: 0, transport: 'stdio', // For now, we only support stdio }; // Store connection this.connections.set(id, info); this.logger.info('New connection registered', { id, totalConnections: this.connections.size, }); // Emit connect event this.emit('connect', info); return id; } /** * Unregister a connection */ unregisterConnection(id: string): void { const connection = this.connections.get(id); if (!connection) { this.logger.warn('Attempted to unregister unknown connection', { id }); return; } this.connections.delete(id); this.logger.info('Connection unregistered', { id, duration: Date.now() - connection.connectedAt.getTime(), requests: connection.requestCount, totalConnections: this.connections.size, }); // Emit disconnect event this.emit('disconnect', id); } /** * Update connection activity */ updateActivity(id: string): void { const connection = this.connections.get(id); if (!connection) { this.logger.warn('Activity update for unknown connection', { id }); return; } connection.lastActivity = new Date(); connection.requestCount++; // Emit activity event this.emit('activity', id); } /** * Get connection info */ getConnection(id: string): ConnectionInfo | undefined { return this.connections.get(id); } /** * Get all connections */ getAllConnections(): ConnectionInfo[] { return Array.from(this.connections.values()); } /** * Get connection statistics */ getStats() { const connections = this.getAllConnections(); const now = Date.now(); return { totalConnections: connections.length, activeConnections: connections.filter( (c) => now - c.lastActivity.getTime() < 60000 // Active in last minute ).length, totalRequests: connections.reduce((sum, c) => sum + c.requestCount, 0), averageRequestsPerConnection: connections.length > 0 ? connections.reduce((sum, c) => sum + c.requestCount, 0) / connections.length : 0, oldestConnection: connections.reduce( (oldest, c) => (!oldest || c.connectedAt < oldest.connectedAt ? c : oldest), null as ConnectionInfo | null ), }; } /** * Clean up inactive connections */ private cleanupInactiveConnections(): void { const now = Date.now(); const timeout = this.sessionTimeout; for (const [id, connection] of this.connections) { const inactiveTime = now - connection.lastActivity.getTime(); if (inactiveTime > timeout) { this.logger.info('Cleaning up inactive connection', { id, inactiveTime, timeout, }); this.unregisterConnection(id); } } } /** * Start cleanup interval */ private startCleanupInterval(): void { // Run cleanup every 5 minutes this.cleanupInterval = setInterval(() => { this.cleanupInactiveConnections(); }, 300000); } /** * Stop cleanup interval */ private stopCleanupInterval(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); this.cleanupInterval = undefined; } } /** * Generate unique connection ID */ private generateConnectionId(): string { return `conn_${Date.now()}_${Math.random().toString(36).substring(7)}`; } /** * Shutdown connection handler */ async shutdown(): Promise<void> { this.logger.info('Shutting down connection handler'); // Stop cleanup interval this.stopCleanupInterval(); // Disconnect all connections const connectionIds = Array.from(this.connections.keys()); for (const id of connectionIds) { this.unregisterConnection(id); } // Remove all listeners this.removeAllListeners(); this.logger.info('Connection handler shutdown complete'); } /** * Type-safe event emitter methods */ on<K extends keyof ConnectionEvents>(event: K, listener: ConnectionEvents[K]): this { return super.on(event, listener); } emit<K extends keyof ConnectionEvents>( event: K, ...args: Parameters<ConnectionEvents[K]> ): boolean { return super.emit(event, ...args); } }

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/stampchain-io/stampchain-mcp'

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