diff --git a/packages/mcp/src/mcp_communication_protocol.ts b/packages/mcp/src/mcp_communication_protocol.ts index 253d19a..3fbea97 100644 --- a/packages/mcp/src/mcp_communication_protocol.ts +++ b/packages/mcp/src/mcp_communication_protocol.ts @@ -162,6 +162,14 @@ export class McpCommunicationProtocol implements CommunicationProtocol { return mcpClient; } + /** + * Returns the configured timeout for an MCP server in milliseconds. + * Defaults to 30 seconds when not specified. + */ + private _getTimeoutMs(serverConfig: McpServerConfig): number { + return (serverConfig.timeout ?? 30) * 1000; + } + private async _withSession( serverName: string, serverConfig: McpServerConfig, @@ -169,8 +177,7 @@ 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; + const timeoutMs = this._getTimeoutMs(serverConfig); try { const client = await this._getOrCreateSession(serverName, serverConfig, auth); return await Promise.race([ @@ -211,8 +218,9 @@ export class McpCommunicationProtocol implements CommunicationProtocol { for (const [serverName, serverConfig] of Object.entries(mcpCallTemplate.config.mcpServers)) { try { this._logInfo(`Discovering tools from MCP server '${serverName}'...`); + const requestTimeout = this._getTimeoutMs(serverConfig); const mcpToolsResult = await this._withSession(serverName, serverConfig, mcpCallTemplate.auth, - (client) => client.listTools() + (client) => client.listTools(undefined, { timeout: requestTimeout }) ); if (!isMcpToolsResponse(mcpToolsResult)) { @@ -292,8 +300,9 @@ export class McpCommunicationProtocol implements CommunicationProtocol { } this._logInfo(`Calling tool '${actualToolName}' on MCP server '${serverName}'...`); + const requestTimeout = this._getTimeoutMs(serverConfig); const result = await this._withSession(serverName, serverConfig, mcpCallTemplate.auth, - (client) => client.callTool({ name: actualToolName, arguments: toolArgs }) + (client) => client.callTool({ name: actualToolName, arguments: toolArgs }, undefined, { timeout: requestTimeout }) ); return this._processMcpToolResult(result); diff --git a/packages/mcp/tests/mcp_communication_protocol.test.ts b/packages/mcp/tests/mcp_communication_protocol.test.ts index 869e1a1..23da133 100644 --- a/packages/mcp/tests/mcp_communication_protocol.test.ts +++ b/packages/mcp/tests/mcp_communication_protocol.test.ts @@ -247,4 +247,36 @@ describe("McpCommunicationProtocol", () => { ).rejects.toThrow("Configuration for MCP server 'unknown_server' not found in manual 'mock_http_manual'."); }, 10000); }); + + describe("Timeout forwarding", () => { + test("forwards configured timeout to listTools and callTool", async () => { + const capturedOpts: any[] = []; + const fakeClient = { + listTools: (_params: any, opts: any) => { + capturedOpts.push({ method: "listTools", opts }); + return Promise.resolve({ tools: [{ name: "t", description: "", inputSchema: {}, outputSchema: {} }] }); + }, + callTool: (_params: any, _result: any, opts: any) => { + capturedOpts.push({ method: "callTool", opts }); + return Promise.resolve({ content: [{ type: "text", text: "ok" }] }); + }, + }; + + const protocol = new McpCommunicationProtocol(); + (protocol as any)._getOrCreateSession = () => Promise.resolve(fakeClient); + + const template: McpCallTemplate = { + name: "m", + call_template_type: "mcp", + config: { mcpServers: { s: { transport: "stdio" as const, command: "true", timeout: 90 } } }, + }; + + await protocol.registerManual(mockClient, template); + await protocol.callTool(mockClient, "s.t", {}, template); + + expect(capturedOpts).toHaveLength(2); + expect(capturedOpts[0]).toEqual({ method: "listTools", opts: { timeout: 90_000 } }); + expect(capturedOpts[1]).toEqual({ method: "callTool", opts: { timeout: 90_000 } }); + }); + }); }); \ No newline at end of file