@@ -384,6 +384,31 @@ func TestStdio(t *testing.T) {
384384 t .Errorf ("Expected array with 3 items, got %v" , result .Params ["array" ])
385385 }
386386 })
387+
388+ t .Run ("SendRequestFailsIfSubprocessExited" , func (t * testing.T ) {
389+ // Start a subprocess that exits immediately
390+ ctx := context .Background ()
391+ stdio := NewStdio ("sh" , nil , "-c" , "exit 0" )
392+
393+ err := stdio .Start (ctx )
394+ require .NoError (t , err )
395+
396+ // Wait for subprocess to exit
397+ require .Eventually (t , func () bool {
398+ return stdio .IsClosed ()
399+ }, time .Second , 10 * time .Millisecond )
400+
401+ // Try to send a request
402+ _ , err = stdio .SendRequest (ctx , JSONRPCRequest {
403+ JSONRPC : "2.0" ,
404+ ID : mcp .NewRequestId ("dead" ),
405+ Method : "noop" ,
406+ })
407+
408+ require .Error (t , err )
409+ require .Contains (t , err .Error (), "subprocess" )
410+ })
411+
387412}
388413
389414func TestStdioErrors (t * testing.T ) {
@@ -609,6 +634,32 @@ func TestStdio_SpawnCommand_UsesCommandFunc_Error(t *testing.T) {
609634 require .EqualError (t , err , "test error" )
610635}
611636
637+ func TestStdio_DoneClosedWhenSubcommandExits (t * testing.T ) {
638+ ctx := context .Background ()
639+
640+ stdio := NewStdioWithOptions (
641+ "sh" ,
642+ nil ,
643+ []string {"-c" , "exit 0" },
644+ )
645+
646+ require .NotNil (t , stdio )
647+
648+ err := stdio .spawnCommand (ctx )
649+ require .NoError (t , err )
650+
651+ t .Cleanup (func () {
652+ if stdio .cmd .Process != nil {
653+ _ = stdio .cmd .Process .Kill ()
654+ }
655+ })
656+
657+ // Wait up to 200ms for the done channel to close
658+ require .Eventually (t , func () bool {
659+ return stdio .IsClosed ()
660+ }, 200 * time .Millisecond , 10 * time .Millisecond , "expected done to be closed after subprocess exited" )
661+ }
662+
612663func TestStdio_NewStdioWithOptions_AppliesOptions (t * testing.T ) {
613664 configured := false
614665
@@ -620,3 +671,28 @@ func TestStdio_NewStdioWithOptions_AppliesOptions(t *testing.T) {
620671 require .NotNil (t , stdio )
621672 require .True (t , configured , "option was not applied" )
622673}
674+
675+ func TestStdio_IsClosed (t * testing.T ) {
676+ t .Run ("returns false before Start" , func (t * testing.T ) {
677+ stdio := NewStdio ("sh" , nil , "-c" , "sleep 1" )
678+ require .False (t , stdio .IsClosed (), "expected IsClosed to be false before Start" )
679+ })
680+
681+ t .Run ("returns false after Start" , func (t * testing.T ) {
682+ stdio := NewStdio ("sh" , nil , "-c" , "sleep 1" )
683+ err := stdio .Start (context .Background ())
684+ require .NoError (t , err )
685+ defer stdio .Close ()
686+ require .False (t , stdio .IsClosed (), "expected IsClosed to be false right after Start" )
687+ })
688+
689+ t .Run ("returns true after subprocess exits" , func (t * testing.T ) {
690+ stdio := NewStdio ("sh" , nil , "-c" , "exit 0" )
691+ err := stdio .Start (context .Background ())
692+ require .NoError (t , err )
693+
694+ require .Eventually (t , func () bool {
695+ return stdio .IsClosed ()
696+ }, 200 * time .Millisecond , 10 * time .Millisecond , "expected IsClosed to return true after subprocess exits" )
697+ })
698+ }
0 commit comments