@@ -152,6 +152,21 @@ func (c *Client) Connect(ctx context.Context) error {
152152 }
153153 }
154154
155+ // CRITICAL FIX: Also cleanup process groups to prevent zombie processes on connection failure
156+ if c .processGroupID > 0 {
157+ c .logger .Warn ("Connection failed - cleaning up process group to prevent zombie processes" ,
158+ zap .String ("server" , c .config .Name ),
159+ zap .Int ("pgid" , c .processGroupID ))
160+
161+ if err := killProcessGroup (c .processGroupID , c .logger , c .config .Name ); err != nil {
162+ c .logger .Error ("Failed to clean up process group after connection failure" ,
163+ zap .String ("server" , c .config .Name ),
164+ zap .Int ("pgid" , c .processGroupID ),
165+ zap .Error (err ))
166+ }
167+ c .processGroupID = 0
168+ }
169+
155170 return fmt .Errorf ("failed to connect: %w" , err )
156171 }
157172
@@ -309,16 +324,18 @@ func (c *Client) connectStdio(ctx context.Context) error {
309324 }
310325 }
311326
312- // Upstream transport with working directory support
327+ // Upstream transport with working directory support and process group management
313328 var stdioTransport * uptransport.Stdio
314329 if c .config .WorkingDir != "" {
315- // Use custom CommandFunc to set working directory
316- commandFunc := createWorkingDirCommandFunc (c .config .WorkingDir )
330+ // CRITICAL FIX: Use enhanced CommandFunc with process group management for proper cleanup
331+ commandFunc := createEnhancedWorkingDirCommandFunc (c .config .WorkingDir , c . logger )
317332 stdioTransport = uptransport .NewStdioWithOptions (finalCommand , envVars , finalArgs ,
318333 uptransport .WithCommandFunc (commandFunc ))
319334 } else {
320- // Use default transport for backwards compatibility
321- stdioTransport = uptransport .NewStdio (finalCommand , envVars , finalArgs ... )
335+ // CRITICAL FIX: Use enhanced CommandFunc even without working directory to ensure process groups
336+ commandFunc := createEnhancedWorkingDirCommandFunc ("" , c .logger )
337+ stdioTransport = uptransport .NewStdioWithOptions (finalCommand , envVars , finalArgs ,
338+ uptransport .WithCommandFunc (commandFunc ))
322339 }
323340
324341 c .client = client .NewClient (stdioTransport )
@@ -370,6 +387,21 @@ func (c *Client) connectStdio(ctx context.Context) error {
370387 c .killDockerContainerByCommandWithContext (cleanupCtx )
371388 }
372389 }
390+
391+ // CRITICAL FIX: Also cleanup process groups to prevent zombie processes on initialization failure
392+ if c .processGroupID > 0 {
393+ c .logger .Warn ("Initialization failed - cleaning up process group to prevent zombie processes" ,
394+ zap .String ("server" , c .config .Name ),
395+ zap .Int ("pgid" , c .processGroupID ))
396+
397+ if err := killProcessGroup (c .processGroupID , c .logger , c .config .Name ); err != nil {
398+ c .logger .Error ("Failed to clean up process group after initialization failure" ,
399+ zap .String ("server" , c .config .Name ),
400+ zap .Int ("pgid" , c .processGroupID ),
401+ zap .Error (err ))
402+ }
403+ c .processGroupID = 0
404+ }
373405 return fmt .Errorf ("MCP initialize failed for stdio transport: %w" , err )
374406 }
375407
@@ -405,6 +437,14 @@ func (c *Client) connectStdio(ctx context.Context) error {
405437 c .logger .Info ("Successfully extracted process from stdio transport for lifecycle management" ,
406438 zap .String ("server" , c .config .Name ),
407439 zap .Int ("pid" , c .processCmd .Process .Pid ))
440+
441+ // CRITICAL FIX: Extract process group ID for proper cleanup
442+ c .processGroupID = extractProcessGroupID (cmd , c .logger , c .config .Name )
443+ if c .processGroupID > 0 {
444+ c .logger .Info ("Process group ID tracked for cleanup" ,
445+ zap .String ("server" , c .config .Name ),
446+ zap .Int ("pgid" , c .processGroupID ))
447+ }
408448 }
409449 } else {
410450 c .logger .Warn ("Could not extract process from stdio transport - will use alternative process tracking" ,
@@ -653,6 +693,11 @@ func createWorkingDirCommandFunc(workingDir string) uptransport.CommandFunc {
653693 }
654694}
655695
696+ // createEnhancedWorkingDirCommandFunc creates a custom CommandFunc with process group management
697+ func createEnhancedWorkingDirCommandFunc (workingDir string , logger * zap.Logger ) uptransport.CommandFunc {
698+ return createProcessGroupCommandFunc (workingDir , logger )
699+ }
700+
656701// connectHTTP establishes HTTP transport connection with auth fallback
657702func (c * Client ) connectHTTP (ctx context.Context ) error {
658703 // Try authentication strategies in order: headers -> no-auth -> OAuth
@@ -1435,6 +1480,27 @@ func (c *Client) DisconnectWithContext(_ context.Context) error {
14351480 zap .String ("server" , c .config .Name ))
14361481 }
14371482
1483+ // CRITICAL FIX: Clean up process group to prevent zombie processes
1484+ if c .processGroupID > 0 {
1485+ c .logger .Info ("Cleaning up process group to prevent zombie processes" ,
1486+ zap .String ("server" , c .config .Name ),
1487+ zap .Int ("pgid" , c .processGroupID ))
1488+
1489+ if err := killProcessGroup (c .processGroupID , c .logger , c .config .Name ); err != nil {
1490+ c .logger .Error ("Failed to clean up process group" ,
1491+ zap .String ("server" , c .config .Name ),
1492+ zap .Int ("pgid" , c .processGroupID ),
1493+ zap .Error (err ))
1494+ } else {
1495+ c .logger .Info ("Process group cleanup completed successfully" ,
1496+ zap .String ("server" , c .config .Name ),
1497+ zap .Int ("pgid" , c .processGroupID ))
1498+ }
1499+
1500+ // Reset process group ID after cleanup
1501+ c .processGroupID = 0
1502+ }
1503+
14381504 c .logger .Debug ("Closing MCP client connection" ,
14391505 zap .String ("server" , c .config .Name ))
14401506 c .client .Close ()
0 commit comments