diff --git a/packages/mcp/src/mcp_call_template.ts b/packages/mcp/src/mcp_call_template.ts index 55f7489..50143fc 100644 --- a/packages/mcp/src/mcp_call_template.ts +++ b/packages/mcp/src/mcp_call_template.ts @@ -22,6 +22,7 @@ export interface McpStdioServer { args: string[]; cwd?: string; env: Record; + timeout: number; } /** @@ -33,6 +34,7 @@ export const McpStdioServerSchema: z.ZodType = z.object({ args: z.array(z.string()).optional().default([]).describe('Arguments to pass to the command.'), cwd: z.string().optional().describe('Working directory for the command.'), env: z.record(z.string(), z.string()).optional().default({}).describe('Environment variables for the command.'), + timeout: z.number().optional().default(30).describe('Timeout for MCP operations in seconds.'), }) as z.ZodType; diff --git a/packages/mcp/src/mcp_communication_protocol.ts b/packages/mcp/src/mcp_communication_protocol.ts index 3f34214..253d19a 100644 --- a/packages/mcp/src/mcp_communication_protocol.ts +++ b/packages/mcp/src/mcp_communication_protocol.ts @@ -169,26 +169,28 @@ export class McpCommunicationProtocol implements CommunicationProtocol { operation: (client: McpClient) => Promise ): Promise { const sessionKey = `${serverName}:${serverConfig.transport}`; + // Use configured timeout (in seconds) or default to 30s + const timeoutMs = (serverConfig.timeout ?? 30) * 1000; try { const client = await this._getOrCreateSession(serverName, serverConfig, auth); return await Promise.race([ operation(client), - new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP operation on '${sessionKey}' timed out after 15s.`)), 15000)) + new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP operation on '${sessionKey}' timed out after ${timeoutMs / 1000}s.`)), timeoutMs)) ]); } catch (e: any) { this._logError(`MCP operation on '${sessionKey}' failed:`, e.message); - + const errorMsg = String((e as any)?.message ?? e).toLowerCase(); // Check for connection errors or "already initialized" errors - if (errorMsg.includes('closed') || errorMsg.includes('disconnected') || + if (errorMsg.includes('closed') || errorMsg.includes('disconnected') || errorMsg.includes('econnreset') || errorMsg.includes('etimedout') || errorMsg.includes('already initialized')) { this._logInfo(`Connection/initialization error detected on '${sessionKey}'. Cleaning up and retrying once...`); await this._cleanupSession(sessionKey); const newClient = await this._getOrCreateSession(serverName, serverConfig, auth); - return await Promise.race([operation(newClient), new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP operation on '${sessionKey}' timed out after 15s.`)), 15000))]); + return await Promise.race([operation(newClient), new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP operation on '${sessionKey}' timed out after ${timeoutMs / 1000}s.`)), timeoutMs))]); } - + throw e; } }