From 9a3a7a5bdba5e3a0f80a21f21600d94132a3dd2d Mon Sep 17 00:00:00 2001 From: pro-vi Date: Mon, 8 Dec 2025 23:47:12 -0800 Subject: [PATCH] fix(mcp): use configured timeout instead of hardcoded 15s The `timeout` field was defined in `McpHttpServerSchema` but never used. `_withSession()` had a hardcoded 15000ms timeout which caused issues with slow MCP servers (e.g., LLM providers via OpenRouter). Changes: - Add `timeout` field to `McpStdioServerSchema` for consistency - Use `serverConfig.timeout` in `_withSession()` instead of hardcoded 15s - Default timeout increased from 15s to 30s to match schema default Fixes #20 in code-mode repo --- packages/mcp/src/mcp_call_template.ts | 2 ++ packages/mcp/src/mcp_communication_protocol.ts | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) 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; } }