11import type { ArgumentsCamelCase , Argv } from "yargs"
2+ import process from "node:process"
23import { logger } from "../logger"
34import { blue , green , red } from "picocolors"
45import {
@@ -12,6 +13,18 @@ import {
1213} from "../client-config"
1314import { spawn } from "node:child_process"
1415
16+ const isWindows = process . platform === "win32"
17+
18+ // On Windows, shell scripts like `npx` cannot be spawned directly by MCP clients
19+ // because they use child_process.spawn() without shell:true. The standard workaround
20+ // is to wrap the command with `cmd /c` so Windows can resolve the .cmd/.ps1 shim.
21+ function wrapCommandForPlatform ( command : string , args : Array < string > ) : { command : string ; args : Array < string > } {
22+ if ( isWindows ) {
23+ return { command : "cmd" , args : [ "/c" , command , ...args ] }
24+ }
25+ return { command, args }
26+ }
27+
1528// Helper to set a server config in a nested structure
1629function setServerConfig (
1730 config : ClientConfig ,
@@ -53,7 +66,12 @@ function setServerConfig(
5366 }
5467 } else if ( client === "opencode" ) {
5568 // OpenCode has a different config structure for MCP servers
56- if ( serverConfig . command === "npx" && serverConfig . args ?. includes ( "mcp-remote@latest" ) ) {
69+ // Check for npx directly or wrapped via cmd /c npx (Windows)
70+ const isNpxCommand =
71+ serverConfig . command === "npx" ||
72+ ( serverConfig . command === "cmd" && serverConfig . args ?. [ 0 ] === "/c" && serverConfig . args ?. [ 1 ] === "npx" )
73+ const isNpxMcpRemote = isNpxCommand && serverConfig . args ?. includes ( "mcp-remote@latest" )
74+ if ( isNpxMcpRemote ) {
5775 // For remote MCP servers, OpenCode uses a different structure
5876 const urlIndex = serverConfig . args . indexOf ( "mcp-remote@latest" ) + 1
5977 const url = serverConfig . args [ urlIndex ]
@@ -232,6 +250,7 @@ async function runAuthentication(url: string): Promise<void> {
232250 return new Promise ( ( resolve , reject ) => {
233251 const child = spawn ( "npx" , [ "-y" , "-p" , "mcp-remote@latest" , "mcp-remote-client" , url ] , {
234252 stdio : [ "ignore" , "ignore" , "ignore" ] , // Hide all output
253+ shell : isWindows , // Required on Windows where npx is a .cmd script
235254 } )
236255
237256 child . on ( "close" , ( code ) => {
@@ -381,9 +400,10 @@ export async function handler(argv: ArgumentsCamelCase<InstallArgv>) {
381400 if ( projectHeader ) {
382401 args . push ( "--header" , projectHeader )
383402 }
403+ const wrapped = wrapCommandForPlatform ( "npx" , args )
384404 const serverConfig : ClientConfig = {
385- command : "npx" ,
386- args : args ,
405+ command : wrapped . command ,
406+ args : wrapped . args ,
387407 }
388408 if ( envVars ) {
389409 serverConfig . env = envVars
@@ -392,9 +412,10 @@ export async function handler(argv: ArgumentsCamelCase<InstallArgv>) {
392412 } else {
393413 // Command-based installation (including simple package names)
394414 const cmdParts = command . split ( " " )
415+ const wrapped = wrapCommandForPlatform ( cmdParts [ 0 ] || command , cmdParts . slice ( 1 ) )
395416 const serverConfig : ClientConfig = {
396- command : cmdParts [ 0 ] ,
397- args : cmdParts . slice ( 1 ) ,
417+ command : wrapped . command ,
418+ args : wrapped . args ,
398419 }
399420 if ( envVars ) {
400421 serverConfig . env = envVars
0 commit comments