@@ -842,5 +842,209 @@ describe("McpHub", () => {
842842 } ) ,
843843 )
844844 } )
845+
846+ it ( "should not double-wrap commands that are already cmd.exe" , async ( ) => {
847+ // Mock Windows platform
848+ Object . defineProperty ( process , "platform" , {
849+ value : "win32" ,
850+ writable : true ,
851+ enumerable : true ,
852+ configurable : true ,
853+ } )
854+
855+ // Mock StdioClientTransport
856+ const mockTransport = {
857+ start : jest . fn ( ) . mockResolvedValue ( undefined ) ,
858+ close : jest . fn ( ) . mockResolvedValue ( undefined ) ,
859+ stderr : {
860+ on : jest . fn ( ) ,
861+ } ,
862+ onerror : null ,
863+ onclose : null ,
864+ }
865+
866+ StdioClientTransport . mockImplementation ( ( config : any ) => {
867+ // Verify that cmd.exe is not double-wrapped
868+ expect ( config . command ) . toBe ( "cmd.exe" )
869+ expect ( config . args ) . toEqual ( [ "/c" , "echo" , "test" ] )
870+ return mockTransport
871+ } )
872+
873+ // Mock Client
874+ Client . mockImplementation ( ( ) => ( {
875+ connect : jest . fn ( ) . mockResolvedValue ( undefined ) ,
876+ close : jest . fn ( ) . mockResolvedValue ( undefined ) ,
877+ getInstructions : jest . fn ( ) . mockReturnValue ( "test instructions" ) ,
878+ request : jest . fn ( ) . mockResolvedValue ( { tools : [ ] , resources : [ ] , resourceTemplates : [ ] } ) ,
879+ } ) )
880+
881+ // Create a new McpHub instance
882+ const mcpHub = new McpHub ( mockProvider as ClineProvider )
883+
884+ // Mock the config file read with cmd.exe already as command
885+ ; ( fs . readFile as jest . Mock ) . mockResolvedValue (
886+ JSON . stringify ( {
887+ mcpServers : {
888+ "test-cmd-server" : {
889+ command : "cmd.exe" ,
890+ args : [ "/c" , "echo" , "test" ] ,
891+ } ,
892+ } ,
893+ } ) ,
894+ )
895+
896+ // Initialize servers (this will trigger connectToServer)
897+ await mcpHub [ "initializeGlobalMcpServers" ] ( )
898+
899+ // Verify StdioClientTransport was called without double-wrapping
900+ expect ( StdioClientTransport ) . toHaveBeenCalledWith (
901+ expect . objectContaining ( {
902+ command : "cmd.exe" ,
903+ args : [ "/c" , "echo" , "test" ] ,
904+ } ) ,
905+ )
906+ } )
907+
908+ it ( "should handle npx.ps1 scenario from node version managers" , async ( ) => {
909+ // Mock Windows platform
910+ Object . defineProperty ( process , "platform" , {
911+ value : "win32" ,
912+ writable : true ,
913+ enumerable : true ,
914+ configurable : true ,
915+ } )
916+
917+ // Mock StdioClientTransport to simulate the ENOENT error without wrapping
918+ const mockTransport = {
919+ start : jest . fn ( ) . mockResolvedValue ( undefined ) ,
920+ close : jest . fn ( ) . mockResolvedValue ( undefined ) ,
921+ stderr : {
922+ on : jest . fn ( ) ,
923+ } ,
924+ onerror : null ,
925+ onclose : null ,
926+ }
927+
928+ let callCount = 0
929+ StdioClientTransport . mockImplementation ( ( config : any ) => {
930+ callCount ++
931+ // First call would fail with ENOENT if not wrapped
932+ // Second call should be wrapped with cmd.exe
933+ if ( callCount === 1 ) {
934+ // This simulates what would happen without wrapping
935+ expect ( config . command ) . toBe ( "cmd.exe" )
936+ expect ( config . args [ 0 ] ) . toBe ( "/c" )
937+ expect ( config . args [ 1 ] ) . toBe ( "npx" )
938+ }
939+ return mockTransport
940+ } )
941+
942+ // Mock Client
943+ Client . mockImplementation ( ( ) => ( {
944+ connect : jest . fn ( ) . mockResolvedValue ( undefined ) ,
945+ close : jest . fn ( ) . mockResolvedValue ( undefined ) ,
946+ getInstructions : jest . fn ( ) . mockReturnValue ( "test instructions" ) ,
947+ request : jest . fn ( ) . mockResolvedValue ( { tools : [ ] , resources : [ ] , resourceTemplates : [ ] } ) ,
948+ } ) )
949+
950+ // Create a new McpHub instance
951+ const mcpHub = new McpHub ( mockProvider as ClineProvider )
952+
953+ // Mock the config file read - simulating fnm/nvm-windows scenario
954+ ; ( fs . readFile as jest . Mock ) . mockResolvedValue (
955+ JSON . stringify ( {
956+ mcpServers : {
957+ "test-fnm-npx-server" : {
958+ command : "npx" ,
959+ args : [ "-y" , "@modelcontextprotocol/server-example" ] ,
960+ env : {
961+ // Simulate fnm environment
962+ FNM_DIR : "C:\\Users\\test\\.fnm" ,
963+ FNM_NODE_DIST_MIRROR : "https://nodejs.org/dist" ,
964+ FNM_ARCH : "x64" ,
965+ } ,
966+ } ,
967+ } ,
968+ } ) ,
969+ )
970+
971+ // Initialize servers (this will trigger connectToServer)
972+ await mcpHub [ "initializeGlobalMcpServers" ] ( )
973+
974+ // Verify that the command was wrapped with cmd.exe
975+ expect ( StdioClientTransport ) . toHaveBeenCalledWith (
976+ expect . objectContaining ( {
977+ command : "cmd.exe" ,
978+ args : [ "/c" , "npx" , "-y" , "@modelcontextprotocol/server-example" ] ,
979+ env : expect . objectContaining ( {
980+ FNM_DIR : "C:\\Users\\test\\.fnm" ,
981+ FNM_NODE_DIST_MIRROR : "https://nodejs.org/dist" ,
982+ FNM_ARCH : "x64" ,
983+ } ) ,
984+ } ) ,
985+ )
986+ } )
987+
988+ it ( "should handle case-insensitive cmd command check" , async ( ) => {
989+ // Mock Windows platform
990+ Object . defineProperty ( process , "platform" , {
991+ value : "win32" ,
992+ writable : true ,
993+ enumerable : true ,
994+ configurable : true ,
995+ } )
996+
997+ // Mock StdioClientTransport
998+ const mockTransport = {
999+ start : jest . fn ( ) . mockResolvedValue ( undefined ) ,
1000+ close : jest . fn ( ) . mockResolvedValue ( undefined ) ,
1001+ stderr : {
1002+ on : jest . fn ( ) ,
1003+ } ,
1004+ onerror : null ,
1005+ onclose : null ,
1006+ }
1007+
1008+ StdioClientTransport . mockImplementation ( ( config : any ) => {
1009+ // Verify that CMD (uppercase) is not double-wrapped
1010+ expect ( config . command ) . toBe ( "CMD" )
1011+ expect ( config . args ) . toEqual ( [ "/c" , "echo" , "test" ] )
1012+ return mockTransport
1013+ } )
1014+
1015+ // Mock Client
1016+ Client . mockImplementation ( ( ) => ( {
1017+ connect : jest . fn ( ) . mockResolvedValue ( undefined ) ,
1018+ close : jest . fn ( ) . mockResolvedValue ( undefined ) ,
1019+ getInstructions : jest . fn ( ) . mockReturnValue ( "test instructions" ) ,
1020+ request : jest . fn ( ) . mockResolvedValue ( { tools : [ ] , resources : [ ] , resourceTemplates : [ ] } ) ,
1021+ } ) )
1022+
1023+ // Create a new McpHub instance
1024+ const mcpHub = new McpHub ( mockProvider as ClineProvider )
1025+
1026+ // Mock the config file read with CMD (uppercase) as command
1027+ ; ( fs . readFile as jest . Mock ) . mockResolvedValue (
1028+ JSON . stringify ( {
1029+ mcpServers : {
1030+ "test-cmd-uppercase-server" : {
1031+ command : "CMD" ,
1032+ args : [ "/c" , "echo" , "test" ] ,
1033+ } ,
1034+ } ,
1035+ } ) ,
1036+ )
1037+
1038+ // Initialize servers (this will trigger connectToServer)
1039+ await mcpHub [ "initializeGlobalMcpServers" ] ( )
1040+
1041+ // Verify StdioClientTransport was called without double-wrapping
1042+ expect ( StdioClientTransport ) . toHaveBeenCalledWith (
1043+ expect . objectContaining ( {
1044+ command : "CMD" ,
1045+ args : [ "/c" , "echo" , "test" ] ,
1046+ } ) ,
1047+ )
1048+ } )
8451049 } )
8461050} )
0 commit comments