Просмотр исходного кода

core: add configurable timeout for MCP tool calls to prevent hanging requests

Dax Raad 1 месяц назад
Родитель
Сommit
ed4ce67cdc
2 измененных файлов с 25 добавлено и 7 удалено
  1. 6 0
      packages/opencode/src/config/config.ts
  2. 19 7
      packages/opencode/src/mcp/index.ts

+ 6 - 0
packages/opencode/src/config/config.ts

@@ -844,6 +844,12 @@ export namespace Config {
             .optional()
             .optional()
             .describe("Tools that should only be available to primary agents."),
             .describe("Tools that should only be available to primary agents."),
           continue_loop_on_deny: z.boolean().optional().describe("Continue the agent loop when a tool call is denied"),
           continue_loop_on_deny: z.boolean().optional().describe("Continue the agent loop when a tool call is denied"),
+          mcp_timeout: z
+            .number()
+            .int()
+            .positive()
+            .optional()
+            .describe("Timeout in milliseconds for model context protocol (MCP) requests"),
         })
         })
         .optional(),
         .optional(),
     })
     })

+ 19 - 7
packages/opencode/src/mcp/index.ts

@@ -4,7 +4,11 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
 import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
 import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
 import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
 import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
 import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
 import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
-import { type Tool as MCPToolDef, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
+import {
+  CallToolResultSchema,
+  type Tool as MCPToolDef,
+  ToolListChangedNotificationSchema,
+} from "@modelcontextprotocol/sdk/types.js"
 import { Config } from "../config/config"
 import { Config } from "../config/config"
 import { Log } from "../util/log"
 import { Log } from "../util/log"
 import { NamedError } from "@opencode-ai/util/error"
 import { NamedError } from "@opencode-ai/util/error"
@@ -93,7 +97,7 @@ export namespace MCP {
   }
   }
 
 
   // Convert MCP tool definition to AI SDK Tool type
   // Convert MCP tool definition to AI SDK Tool type
-  function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient): Tool {
+  async function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient): Promise<Tool> {
     const inputSchema = mcpTool.inputSchema
     const inputSchema = mcpTool.inputSchema
 
 
     // Spread first, then override type to ensure it's always "object"
     // Spread first, then override type to ensure it's always "object"
@@ -103,15 +107,23 @@ export namespace MCP {
       properties: (inputSchema.properties ?? {}) as JSONSchema7["properties"],
       properties: (inputSchema.properties ?? {}) as JSONSchema7["properties"],
       additionalProperties: false,
       additionalProperties: false,
     }
     }
+    const config = await Config.get()
 
 
     return dynamicTool({
     return dynamicTool({
       description: mcpTool.description ?? "",
       description: mcpTool.description ?? "",
       inputSchema: jsonSchema(schema),
       inputSchema: jsonSchema(schema),
       execute: async (args: unknown) => {
       execute: async (args: unknown) => {
-        return client.callTool({
-          name: mcpTool.name,
-          arguments: args as Record<string, unknown>,
-        })
+        return client.callTool(
+          {
+            name: mcpTool.name,
+            arguments: args as Record<string, unknown>,
+          },
+          CallToolResultSchema,
+          {
+            resetTimeoutOnProgress: true,
+            timeout: config.experimental?.mcp_timeout,
+          },
+        )
       },
       },
     })
     })
   }
   }
@@ -474,7 +486,7 @@ export namespace MCP {
       for (const mcpTool of toolsResult.tools) {
       for (const mcpTool of toolsResult.tools) {
         const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
         const sanitizedClientName = clientName.replace(/[^a-zA-Z0-9_-]/g, "_")
         const sanitizedToolName = mcpTool.name.replace(/[^a-zA-Z0-9_-]/g, "_")
         const sanitizedToolName = mcpTool.name.replace(/[^a-zA-Z0-9_-]/g, "_")
-        result[sanitizedClientName + "_" + sanitizedToolName] = convertMcpTool(mcpTool, client)
+        result[sanitizedClientName + "_" + sanitizedToolName] = await convertMcpTool(mcpTool, client)
       }
       }
     }
     }
     return result
     return result