Skip to main content
Glama
2389-research

MCP Agent Social Media Server

timeout.ts5.74 kB
// ABOUTME: Timeout management for MCP requests // ABOUTME: Prevents hanging requests and provides configurable timeout handling import { logger } from '../logger.js'; import { McpTimeoutError } from './error-handler.js'; // Simple async lock implementation for thread safety class AsyncLock { private queue: Array<() => void> = []; private locked = false; async acquire(): Promise<() => void> { return new Promise((resolve) => { const unlock = () => { this.locked = false; const next = this.queue.shift(); if (next) { this.locked = true; next(); } }; if (this.locked) { this.queue.push(() => resolve(unlock)); } else { this.locked = true; resolve(unlock); } }); } } export interface TimeoutConfig { defaultTimeout: number; methodTimeouts: Record<string, number>; maxTimeout: number; } export class TimeoutManager { private readonly config: TimeoutConfig; private timeoutCount = 0; private activeTimeouts = new Set<NodeJS.Timeout>(); private readonly timeoutLock = new AsyncLock(); constructor(config?: Partial<TimeoutConfig>) { this.config = { defaultTimeout: 30000, // 30 seconds maxTimeout: 120000, // 2 minutes methodTimeouts: { 'tools/call': 60000, // Tool calls can take longer 'sampling/create': 90000, // LLM requests need more time 'resources/read': 10000, // Resource reads should be fast 'resources/list': 5000, 'tools/list': 5000, 'prompts/list': 5000, 'prompts/get': 10000, 'roots/list': 5000, ...config?.methodTimeouts, }, ...config, }; logger.info('Timeout manager initialized', { defaultTimeout: this.config.defaultTimeout, maxTimeout: this.config.maxTimeout, methodTimeouts: Object.keys(this.config.methodTimeouts).length, }); } /** * Create a timeout promise for a specific method */ createTimeout(method: string): Promise<never> { const timeoutMs = this.getTimeoutForMethod(method); return new Promise((_, reject) => { const timeoutId = setTimeout(async () => { // Use lock for thread-safe updates const unlock = await this.timeoutLock.acquire(); try { this.timeoutCount++; this.activeTimeouts.delete(timeoutId); } finally { unlock(); } logger.warn('Request timed out', { method, timeout: timeoutMs, totalTimeouts: this.timeoutCount, }); const error = new McpTimeoutError(`Request timed out after ${timeoutMs}ms`, timeoutMs); reject(error); }, timeoutMs); // Thread-safe addition to active timeouts this.activeTimeouts.add(timeoutId); }); } /** * Create a timeout promise that can be cleared */ createClearableTimeout(method: string): { promise: Promise<never>; clear: () => void; } { const timeoutMs = this.getTimeoutForMethod(method); let timeoutId: NodeJS.Timeout; const promise = new Promise<never>((_, reject) => { timeoutId = setTimeout(async () => { // Use lock for thread-safe updates const unlock = await this.timeoutLock.acquire(); try { this.timeoutCount++; this.activeTimeouts.delete(timeoutId); } finally { unlock(); } logger.warn('Request timed out', { method, timeout: timeoutMs, }); const error = new McpTimeoutError(`Request timed out after ${timeoutMs}ms`, timeoutMs); reject(error); }, timeoutMs); this.activeTimeouts.add(timeoutId); }); const clear = async () => { if (timeoutId) { clearTimeout(timeoutId); // Use lock for thread-safe removal const unlock = await this.timeoutLock.acquire(); try { this.activeTimeouts.delete(timeoutId); } finally { unlock(); } } }; return { promise, clear }; } /** * Wrap a promise with timeout */ async withTimeout<T>(promise: Promise<T>, method: string): Promise<T> { const { promise: timeoutPromise, clear } = this.createClearableTimeout(method); try { const result = await Promise.race([promise, timeoutPromise]); clear(); return result; } catch (error) { clear(); throw error; } } /** * Get timeout duration for a specific method */ private getTimeoutForMethod(method: string): number { const methodTimeout = this.config.methodTimeouts[method]; if (methodTimeout) { return Math.min(methodTimeout, this.config.maxTimeout); } return this.config.defaultTimeout; } /** * Clear all active timeouts */ async clearAllTimeouts(): Promise<void> { const unlock = await this.timeoutLock.acquire(); try { for (const timeoutId of this.activeTimeouts) { clearTimeout(timeoutId); } this.activeTimeouts.clear(); } finally { unlock(); } logger.info('Cleared all active timeouts'); } /** * Get timeout statistics */ getStats() { return { totalTimeouts: this.timeoutCount, activeTimeouts: this.activeTimeouts.size, config: { defaultTimeout: this.config.defaultTimeout, maxTimeout: this.config.maxTimeout, methodTimeoutCount: Object.keys(this.config.methodTimeouts).length, }, }; } /** * Update timeout configuration */ updateConfig(newConfig: Partial<TimeoutConfig>): void { Object.assign(this.config, newConfig); logger.info('Timeout configuration updated', { config: this.config }); } }

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/2389-research/mcp-socialmedia'

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