Kaynağa Gözat

fix: honor per-server MCP timeouts (#8706)

Dax 1 ay önce
ebeveyn
işleme
dd1f981d23

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

@@ -395,9 +395,7 @@ export namespace Config {
         .int()
         .int()
         .positive()
         .positive()
         .optional()
         .optional()
-        .describe(
-          "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.",
-        ),
+        .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
     })
     })
     .strict()
     .strict()
     .meta({
     .meta({
@@ -436,9 +434,7 @@ export namespace Config {
         .int()
         .int()
         .positive()
         .positive()
         .optional()
         .optional()
-        .describe(
-          "Timeout in ms for fetching tools from the MCP server. Defaults to 5000 (5 seconds) if not specified.",
-        ),
+        .describe("Timeout in ms for MCP server requests. Defaults to 5000 (5 seconds) if not specified."),
     })
     })
     .strict()
     .strict()
     .meta({
     .meta({

+ 9 - 4
packages/opencode/src/mcp/index.ts

@@ -109,7 +109,7 @@ export namespace MCP {
   }
   }
 
 
   // Convert MCP tool definition to AI SDK Tool type
   // Convert MCP tool definition to AI SDK Tool type
-  async function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient): Promise<Tool> {
+  async function convertMcpTool(mcpTool: MCPToolDef, client: MCPClient, timeout?: number): 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"
@@ -119,7 +119,6 @@ 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 ?? "",
@@ -133,7 +132,7 @@ export namespace MCP {
           CallToolResultSchema,
           CallToolResultSchema,
           {
           {
             resetTimeoutOnProgress: true,
             resetTimeoutOnProgress: true,
-            timeout: config.experimental?.mcp_timeout,
+            timeout,
           },
           },
         )
         )
       },
       },
@@ -556,7 +555,10 @@ export namespace MCP {
   export async function tools() {
   export async function tools() {
     const result: Record<string, Tool> = {}
     const result: Record<string, Tool> = {}
     const s = await state()
     const s = await state()
+    const cfg = await Config.get()
+    const config = cfg.mcp ?? {}
     const clientsSnapshot = await clients()
     const clientsSnapshot = await clients()
+    const defaultTimeout = cfg.experimental?.mcp_timeout
 
 
     for (const [clientName, client] of Object.entries(clientsSnapshot)) {
     for (const [clientName, client] of Object.entries(clientsSnapshot)) {
       // Only include tools from connected MCPs (skip disabled ones)
       // Only include tools from connected MCPs (skip disabled ones)
@@ -577,10 +579,13 @@ export namespace MCP {
       if (!toolsResult) {
       if (!toolsResult) {
         continue
         continue
       }
       }
+      const mcpConfig = config[clientName]
+      const entry = isMcpConfigured(mcpConfig) ? mcpConfig : undefined
+      const timeout = entry?.timeout ?? defaultTimeout
       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] = await convertMcpTool(mcpTool, client)
+        result[sanitizedClientName + "_" + sanitizedToolName] = await convertMcpTool(mcpTool, client, timeout)
       }
       }
     }
     }
     return result
     return result