@@ -2,8 +2,8 @@ import type { McpHub as McpHubType } from "../McpHub"
22import type { ClineProvider } from "../../../core/webview/ClineProvider"
33import type { ExtensionContext , Uri } from "vscode"
44import type { McpConnection } from "../McpHub"
5+ import { StdioConfigSchema } from "../McpHub"
56
6- const vscode = require ( "vscode" )
77const fs = require ( "fs/promises" )
88const { McpHub } = require ( "../McpHub" )
99
@@ -280,6 +280,7 @@ describe("McpHub", () => {
280280 } ,
281281 } ,
282282 expect . any ( Object ) ,
283+ expect . objectContaining ( { timeout : 60000 } ) , // Default 60 second timeout
283284 )
284285 } )
285286
@@ -288,5 +289,192 @@ describe("McpHub", () => {
288289 "No connection found for server: non-existent-server" ,
289290 )
290291 } )
292+
293+ describe ( "timeout configuration" , ( ) => {
294+ it ( "should validate timeout values" , ( ) => {
295+ // Test valid timeout values
296+ const validConfig = {
297+ command : "test" ,
298+ timeout : 60 ,
299+ }
300+ expect ( ( ) => StdioConfigSchema . parse ( validConfig ) ) . not . toThrow ( )
301+
302+ // Test invalid timeout values
303+ const invalidConfigs = [
304+ { command : "test" , timeout : 0 } , // Too low
305+ { command : "test" , timeout : 3601 } , // Too high
306+ { command : "test" , timeout : - 1 } , // Negative
307+ ]
308+
309+ invalidConfigs . forEach ( ( config ) => {
310+ expect ( ( ) => StdioConfigSchema . parse ( config ) ) . toThrow ( )
311+ } )
312+ } )
313+
314+ it ( "should use default timeout of 60 seconds if not specified" , async ( ) => {
315+ const mockConnection : McpConnection = {
316+ server : {
317+ name : "test-server" ,
318+ config : JSON . stringify ( { command : "test" } ) , // No timeout specified
319+ status : "connected" ,
320+ } ,
321+ client : {
322+ request : jest . fn ( ) . mockResolvedValue ( { content : [ ] } ) ,
323+ } as any ,
324+ transport : { } as any ,
325+ }
326+
327+ mcpHub . connections = [ mockConnection ]
328+ await mcpHub . callTool ( "test-server" , "test-tool" )
329+
330+ expect ( mockConnection . client . request ) . toHaveBeenCalledWith (
331+ expect . anything ( ) ,
332+ expect . anything ( ) ,
333+ expect . objectContaining ( { timeout : 60000 } ) , // 60 seconds in milliseconds
334+ )
335+ } )
336+
337+ it ( "should apply configured timeout to tool calls" , async ( ) => {
338+ const mockConnection : McpConnection = {
339+ server : {
340+ name : "test-server" ,
341+ config : JSON . stringify ( { command : "test" , timeout : 120 } ) , // 2 minutes
342+ status : "connected" ,
343+ } ,
344+ client : {
345+ request : jest . fn ( ) . mockResolvedValue ( { content : [ ] } ) ,
346+ } as any ,
347+ transport : { } as any ,
348+ }
349+
350+ mcpHub . connections = [ mockConnection ]
351+ await mcpHub . callTool ( "test-server" , "test-tool" )
352+
353+ expect ( mockConnection . client . request ) . toHaveBeenCalledWith (
354+ expect . anything ( ) ,
355+ expect . anything ( ) ,
356+ expect . objectContaining ( { timeout : 120000 } ) , // 120 seconds in milliseconds
357+ )
358+ } )
359+ } )
360+
361+ describe ( "updateServerTimeout" , ( ) => {
362+ it ( "should update server timeout in settings file" , async ( ) => {
363+ const mockConfig = {
364+ mcpServers : {
365+ "test-server" : {
366+ command : "node" ,
367+ args : [ "test.js" ] ,
368+ timeout : 60 ,
369+ } ,
370+ } ,
371+ }
372+
373+ // Mock reading initial config
374+ ; ( fs . readFile as jest . Mock ) . mockResolvedValueOnce ( JSON . stringify ( mockConfig ) )
375+
376+ await mcpHub . updateServerTimeout ( "test-server" , 120 )
377+
378+ // Verify the config was updated correctly
379+ const writeCall = ( fs . writeFile as jest . Mock ) . mock . calls [ 0 ]
380+ const writtenConfig = JSON . parse ( writeCall [ 1 ] )
381+ expect ( writtenConfig . mcpServers [ "test-server" ] . timeout ) . toBe ( 120 )
382+ } )
383+
384+ it ( "should fallback to default timeout when config has invalid timeout" , async ( ) => {
385+ const mockConfig = {
386+ mcpServers : {
387+ "test-server" : {
388+ command : "node" ,
389+ args : [ "test.js" ] ,
390+ timeout : 60 ,
391+ } ,
392+ } ,
393+ }
394+
395+ // Mock initial read
396+ ; ( fs . readFile as jest . Mock ) . mockResolvedValueOnce ( JSON . stringify ( mockConfig ) )
397+
398+ // Update with invalid timeout
399+ await mcpHub . updateServerTimeout ( "test-server" , 3601 )
400+
401+ // Config is written
402+ expect ( fs . writeFile ) . toHaveBeenCalled ( )
403+
404+ // Setup connection with invalid timeout
405+ const mockConnection : McpConnection = {
406+ server : {
407+ name : "test-server" ,
408+ config : JSON . stringify ( {
409+ command : "node" ,
410+ args : [ "test.js" ] ,
411+ timeout : 3601 , // Invalid timeout
412+ } ) ,
413+ status : "connected" ,
414+ } ,
415+ client : {
416+ request : jest . fn ( ) . mockResolvedValue ( { content : [ ] } ) ,
417+ } as any ,
418+ transport : { } as any ,
419+ }
420+
421+ mcpHub . connections = [ mockConnection ]
422+
423+ // Call tool - should use default timeout
424+ await mcpHub . callTool ( "test-server" , "test-tool" )
425+
426+ // Verify default timeout was used
427+ expect ( mockConnection . client . request ) . toHaveBeenCalledWith (
428+ expect . anything ( ) ,
429+ expect . anything ( ) ,
430+ expect . objectContaining ( { timeout : 60000 } ) , // Default 60 seconds
431+ )
432+ } )
433+
434+ it ( "should accept valid timeout values" , async ( ) => {
435+ const mockConfig = {
436+ mcpServers : {
437+ "test-server" : {
438+ command : "node" ,
439+ args : [ "test.js" ] ,
440+ timeout : 60 ,
441+ } ,
442+ } ,
443+ }
444+
445+ ; ( fs . readFile as jest . Mock ) . mockResolvedValueOnce ( JSON . stringify ( mockConfig ) )
446+
447+ // Test valid timeout values
448+ const validTimeouts = [ 1 , 60 , 3600 ]
449+ for ( const timeout of validTimeouts ) {
450+ await mcpHub . updateServerTimeout ( "test-server" , timeout )
451+ expect ( fs . writeFile ) . toHaveBeenCalled ( )
452+ jest . clearAllMocks ( ) // Reset for next iteration
453+ ; ( fs . readFile as jest . Mock ) . mockResolvedValueOnce ( JSON . stringify ( mockConfig ) )
454+ }
455+ } )
456+
457+ it ( "should notify webview after updating timeout" , async ( ) => {
458+ const mockConfig = {
459+ mcpServers : {
460+ "test-server" : {
461+ command : "node" ,
462+ args : [ "test.js" ] ,
463+ timeout : 60 ,
464+ } ,
465+ } ,
466+ }
467+
468+ ; ( fs . readFile as jest . Mock ) . mockResolvedValueOnce ( JSON . stringify ( mockConfig ) )
469+
470+ await mcpHub . updateServerTimeout ( "test-server" , 120 )
471+
472+ expect ( mockProvider . postMessageToWebview ) . toHaveBeenCalledWith (
473+ expect . objectContaining ( {
474+ type : "mcpServers" ,
475+ } ) ,
476+ )
477+ } )
478+ } )
291479 } )
292480} )
0 commit comments