Skip to main content
Glama
DynamicEndpoints

ESPN MCP Server

client-pool.ts5.92 kB
/** * Client pool for proper resource management * Prevents memory leaks by reusing client instances */ import { EventEmitter } from 'events'; import type { ModernESPNClient } from '../client/espn-client.js'; import type { Config } from '../config/index.js'; import { logger } from './logger.js'; // ============================================================================ // Client Pool // ============================================================================ /** * Singleton client pool to manage ESPN client instances * Prevents creating new clients for every request */ export class ESPNClientPool extends EventEmitter { private static instance: ESPNClientPool | null = null; private client: ModernESPNClient | null = null; private refCount: number = 0; private config: Config | null = null; private constructor() { super(); } /** * Get the singleton instance */ static getInstance(): ESPNClientPool { if (!ESPNClientPool.instance) { ESPNClientPool.instance = new ESPNClientPool(); } return ESPNClientPool.instance; } /** * Initialize the pool with configuration */ initialize(config: Config, ClientClass: new (config: Config) => ModernESPNClient): void { if (this.client) { logger.warn('Client pool already initialized, skipping'); return; } this.config = config; this.client = new ClientClass(config); this.refCount = 0; logger.info({ type: 'client_pool_init', cacheTimeout: config.cache.defaultTtl, }, 'ESPN client pool initialized'); this.emit('initialized'); } /** * Get a client instance * Increments reference count */ acquire(): ModernESPNClient { if (!this.client) { throw new Error('Client pool not initialized. Call initialize() first.'); } this.refCount++; this.emit('acquire', this.refCount); logger.trace({ type: 'client_acquire', refCount: this.refCount, }, 'Client acquired from pool'); return this.client; } /** * Release a client instance * Decrements reference count */ release(): void { if (this.refCount > 0) { this.refCount--; this.emit('release', this.refCount); logger.trace({ type: 'client_release', refCount: this.refCount, }, 'Client released to pool'); } } /** * Get current reference count */ getRefCount(): number { return this.refCount; } /** * Get pool statistics */ getStats() { return { initialized: this.client !== null, refCount: this.refCount, cacheStats: this.client?.getCacheStats() || null, }; } /** * Destroy the pool and cleanup resources * Should only be called on server shutdown */ async destroy(): Promise<void> { if (!this.client) { return; } logger.info({ type: 'client_pool_destroy', refCount: this.refCount, }, 'Destroying ESPN client pool'); // Warn if there are active references if (this.refCount > 0) { logger.warn({ type: 'client_pool_destroy_warning', refCount: this.refCount, }, `Destroying client pool with ${this.refCount} active references`); } // Destroy the client await this.client.destroy(); this.client = null; this.refCount = 0; this.config = null; this.emit('destroyed'); } /** * Reset the pool (useful for testing) */ static reset(): void { if (ESPNClientPool.instance?.client) { ESPNClientPool.instance.client.destroy(); } ESPNClientPool.instance = null; } } // ============================================================================ // Convenience Functions // ============================================================================ /** * Get the global client pool instance */ export function getClientPool(): ESPNClientPool { return ESPNClientPool.getInstance(); } /** * Initialize the global client pool */ export function initializeClientPool( config: Config, ClientClass: new (config: Config) => ModernESPNClient ): void { getClientPool().initialize(config, ClientClass); } /** * Get a client from the global pool */ export function getClient(): ModernESPNClient { return getClientPool().acquire(); } /** * Release a client back to the global pool */ export function releaseClient(): void { getClientPool().release(); } /** * Destroy the global client pool */ export async function destroyClientPool(): Promise<void> { await getClientPool().destroy(); } // ============================================================================ // Client Wrapper with Auto-Release // ============================================================================ /** * Execute a function with a client and automatically release it */ export async function withClient<T>( fn: (client: ModernESPNClient) => Promise<T> ): Promise<T> { const client = getClient(); try { return await fn(client); } finally { releaseClient(); } } /** * Create a scoped client that auto-releases */ export class ScopedClient { private client: ModernESPNClient; private released = false; constructor() { this.client = getClient(); } /** * Get the underlying client */ get(): ModernESPNClient { if (this.released) { throw new Error('Client has been released'); } return this.client; } /** * Release the client back to the pool */ release(): void { if (!this.released) { releaseClient(); this.released = true; } } /** * Auto-release when scope ends */ [Symbol.dispose](): void { this.release(); } }

Latest Blog Posts

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/DynamicEndpoints/espn-mcp'

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