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