Skip to main content
Glama
DynamicEndpoints

ESPN MCP Server

index.ts9.72 kB
/** * Centralized configuration management for ESPN MCP Server * Provides type-safe configuration with validation and environment variable support */ import { z } from 'zod'; // ============================================================================ // Configuration Schema // ============================================================================ const ConfigSchema = z.object({ // Server Configuration server: z.object({ port: z.number().int().positive().default(8081), host: z.string().default('0.0.0.0'), nodeEnv: z.enum(['development', 'production', 'test']).default('development'), transport: z.enum(['stdio', 'http']).default('stdio'), }), // ESPN API Configuration espn: z.object({ baseUrl: z.string().url().default('https://site.api.espn.com/apis/site/v2/sports'), requestTimeout: z.number().positive().default(15000), maxRetries: z.number().int().min(0).max(5).default(3), retryDelay: z.number().positive().default(1000), }), // Cache Configuration cache: z.object({ enabled: z.boolean().default(true), defaultTtl: z.number().positive().default(300000), // 5 minutes maxSize: z.number().positive().default(1000), // max entries maxMemory: z.number().positive().default(100 * 1024 * 1024), // 100MB // Sport-specific TTLs (in milliseconds) sportTtls: z.object({ nfl: z.number().positive().default(180000), // 3 minutes nba: z.number().positive().default(60000), // 1 minute mlb: z.number().positive().default(120000), // 2 minutes nhl: z.number().positive().default(120000), // 2 minutes cfb: z.number().positive().default(300000), // 5 minutes soccer: z.number().positive().default(240000), // 4 minutes }), }), // Rate Limiting Configuration rateLimit: z.object({ enabled: z.boolean().default(true), maxRequests: z.number().positive().default(100), windowMs: z.number().positive().default(60000), // 1 minute skipSuccessfulRequests: z.boolean().default(false), }), // Features Configuration features: z.object({ enableStreaming: z.boolean().default(true), enableSubscriptions: z.boolean().default(true), enableResourceTemplates: z.boolean().default(true), enableSessionManagement: z.boolean().default(true), }), // Logging Configuration logging: z.object({ level: z.enum(['trace', 'debug', 'info', 'warn', 'error', 'fatal']).default('info'), prettyPrint: z.boolean().default(true), destination: z.string().optional(), }), // CORS Configuration (for HTTP mode) cors: z.object({ enabled: z.boolean().default(true), origin: z.union([z.string(), z.array(z.string())]).default('*'), credentials: z.boolean().default(true), maxAge: z.number().default(86400), }), // Health Check Configuration health: z.object({ enabled: z.boolean().default(true), endpoint: z.string().default('/health'), includeDetails: z.boolean().default(true), }), // Monitoring Configuration monitoring: z.object({ enabled: z.boolean().default(false), metricsEnabled: z.boolean().default(true), tracingEnabled: z.boolean().default(false), }), }); export type Config = z.infer<typeof ConfigSchema>; // ============================================================================ // Environment Variable Mapping // ============================================================================ function getEnvValue(key: string, defaultValue?: any): any { const value = process.env[key]; if (value === undefined) { return defaultValue; } // Handle boolean values if (value === 'true') return true; if (value === 'false') return false; // Handle numeric values const num = Number(value); if (!isNaN(num)) return num; // Return as string return value; } /** * Load configuration from environment variables */ export function loadConfig(): Config { const rawConfig = { server: { port: getEnvValue('PORT', 8081), host: getEnvValue('HOST', '0.0.0.0'), nodeEnv: getEnvValue('NODE_ENV', 'development'), transport: getEnvValue('TRANSPORT', 'stdio'), }, espn: { baseUrl: getEnvValue('ESPN_BASE_URL', 'https://site.api.espn.com/apis/site/v2/sports'), requestTimeout: getEnvValue('ESPN_REQUEST_TIMEOUT', 15000), maxRetries: getEnvValue('ESPN_MAX_RETRIES', 3), retryDelay: getEnvValue('ESPN_RETRY_DELAY', 1000), }, cache: { enabled: getEnvValue('CACHE_ENABLED', true), defaultTtl: getEnvValue('CACHE_DEFAULT_TTL', 300000), maxSize: getEnvValue('CACHE_MAX_SIZE', 1000), maxMemory: getEnvValue('CACHE_MAX_MEMORY', 100 * 1024 * 1024), sportTtls: { nfl: getEnvValue('CACHE_TTL_NFL', 180000), nba: getEnvValue('CACHE_TTL_NBA', 60000), mlb: getEnvValue('CACHE_TTL_MLB', 120000), nhl: getEnvValue('CACHE_TTL_NHL', 120000), cfb: getEnvValue('CACHE_TTL_CFB', 300000), soccer: getEnvValue('CACHE_TTL_SOCCER', 240000), }, }, rateLimit: { enabled: getEnvValue('RATE_LIMIT_ENABLED', true), maxRequests: getEnvValue('RATE_LIMIT_MAX_REQUESTS', 100), windowMs: getEnvValue('RATE_LIMIT_WINDOW_MS', 60000), skipSuccessfulRequests: getEnvValue('RATE_LIMIT_SKIP_SUCCESSFUL', false), }, features: { enableStreaming: getEnvValue('ENABLE_STREAMING', true), enableSubscriptions: getEnvValue('ENABLE_SUBSCRIPTIONS', true), enableResourceTemplates: getEnvValue('ENABLE_RESOURCE_TEMPLATES', true), enableSessionManagement: getEnvValue('ENABLE_SESSION_MANAGEMENT', true), }, logging: { level: getEnvValue('LOG_LEVEL', 'info'), prettyPrint: getEnvValue('LOG_PRETTY_PRINT', true), destination: getEnvValue('LOG_DESTINATION'), }, cors: { enabled: getEnvValue('CORS_ENABLED', true), origin: getEnvValue('CORS_ORIGIN', '*'), credentials: getEnvValue('CORS_CREDENTIALS', true), maxAge: getEnvValue('CORS_MAX_AGE', 86400), }, health: { enabled: getEnvValue('HEALTH_CHECK_ENABLED', true), endpoint: getEnvValue('HEALTH_CHECK_ENDPOINT', '/health'), includeDetails: getEnvValue('HEALTH_CHECK_INCLUDE_DETAILS', true), }, monitoring: { enabled: getEnvValue('MONITORING_ENABLED', false), metricsEnabled: getEnvValue('METRICS_ENABLED', true), tracingEnabled: getEnvValue('TRACING_ENABLED', false), }, }; try { return ConfigSchema.parse(rawConfig); } catch (error) { if (error instanceof z.ZodError) { console.error('Configuration validation failed:'); error.errors.forEach((err) => { console.error(` - ${err.path.join('.')}: ${err.message}`); }); throw new Error('Invalid configuration'); } throw error; } } // ============================================================================ // Configuration Singleton // ============================================================================ let configInstance: Config | null = null; /** * Get the current configuration instance * Loads from environment if not already loaded */ export function getConfig(): Config { if (!configInstance) { configInstance = loadConfig(); } return configInstance; } /** * Reset the configuration instance (useful for testing) */ export function resetConfig(): void { configInstance = null; } /** * Override configuration values (useful for testing) */ export function setConfig(config: Partial<Config>): void { configInstance = ConfigSchema.parse({ ...getConfig(), ...config, }); } // ============================================================================ // Configuration Helpers // ============================================================================ /** * Get TTL for a specific sport */ export function getSportTtl(sport: string, config: Config): number { const sportKey = sport.toLowerCase().replace(/-/g, ''); // Map sport names to config keys const sportMap: Record<string, keyof Config['cache']['sportTtls']> = { 'nfl': 'nfl', 'football': 'nfl', 'nba': 'nba', 'basketball': 'nba', 'mlb': 'mlb', 'baseball': 'mlb', 'nhl': 'nhl', 'hockey': 'nhl', 'collegefootball': 'cfb', 'cfb': 'cfb', 'soccer': 'soccer', 'mls': 'soccer', }; const configKey = sportMap[sportKey]; return configKey ? config.cache.sportTtls[configKey] : config.cache.defaultTtl; } /** * Check if a feature is enabled */ export function isFeatureEnabled(feature: keyof Config['features'], config: Config): boolean { return config.features[feature]; } /** * Get log level */ export function getLogLevel(config: Config): string { return config.logging.level; } /** * Check if running in development mode */ export function isDevelopment(config: Config): boolean { return config.server.nodeEnv === 'development'; } /** * Check if running in production mode */ export function isProduction(config: Config): boolean { return config.server.nodeEnv === 'production'; } /** * Check if running in test mode */ export function isTest(config: Config): boolean { return config.server.nodeEnv === 'test'; } // ============================================================================ // Export Default Configuration // ============================================================================ export default getConfig();

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