Skip to content

Commit d80b0f3

Browse files
committed
Keep standard I/O process running in shell mode
1 parent ab99b99 commit d80b0f3

File tree

3 files changed

+37
-6
lines changed

3 files changed

+37
-6
lines changed

cmd/mcptools/commands/shell.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func ShellCmd() *cobra.Command { //nolint:gocyclo
4444
os.Exit(1)
4545
}
4646

47-
mcpClient, clientErr := CreateClientFunc(parsedArgs)
47+
mcpClient, clientErr := CreateClientFunc(parsedArgs, client.CloseTransportAfterExecute(false))
4848
if clientErr != nil {
4949
fmt.Fprintf(os.Stderr, "Error: %v\n", clientErr)
5050
os.Exit(1)

pkg/client/client.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ type Client struct {
2222
// configuration.
2323
type Option func(*Client)
2424

25+
// CloseTransportAfterExecute allows keeping a transport alive if supported by
26+
// the transport.
27+
func CloseTransportAfterExecute(closeTransport bool) Option {
28+
return func(c *Client) {
29+
t, ok := c.transport.(interface{ SetCloseAfterExecute(bool) })
30+
if ok {
31+
t.SetCloseAfterExecute(closeTransport)
32+
}
33+
}
34+
}
35+
2536
// NewWithTransport creates a new MCP client using the provided transport.
2637
// This allows callers to provide a custom transport implementation.
2738
func NewWithTransport(t transport.Transport) *Client {

pkg/transport/stdio.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
// Stdio implements the Transport interface by executing a command
1515
// and communicating with it via stdin/stdout using JSON-RPC.
1616
type Stdio struct {
17+
process *stdioProcess
1718
command []string
1819
nextID int
1920
debug bool
@@ -38,15 +39,30 @@ func NewStdio(command []string) *Stdio {
3839
}
3940
}
4041

42+
// SetCloseAfterExecute toggles whether the underlying process should be closed
43+
// or kept alive after each call to Execute.
44+
func (t *Stdio) SetCloseAfterExecute(v bool) {
45+
if v {
46+
t.process = nil
47+
} else {
48+
t.process = &stdioProcess{}
49+
}
50+
}
51+
4152
// Execute implements the Transport interface by spawning a subprocess
4253
// and communicating with it via JSON-RPC over stdin/stdout.
4354
func (t *Stdio) Execute(method string, params any) (map[string]any, error) {
44-
process := &stdioProcess{}
55+
process := t.process
56+
if process == nil {
57+
process = &stdioProcess{}
58+
}
4559

46-
var err error
47-
process.stdin, process.stdout, process.cmd, process.stderrBuf, err = t.setupCommand()
48-
if err != nil {
49-
return nil, err
60+
if process.cmd == nil {
61+
var err error
62+
process.stdin, process.stdout, process.cmd, process.stderrBuf, err = t.setupCommand()
63+
if err != nil {
64+
return nil, err
65+
}
5066
}
5167

5268
if t.debug {
@@ -94,6 +110,10 @@ func (t *Stdio) Execute(method string, params any) (map[string]any, error) {
94110

95111
// closeProcess waits for the command to finish, returning any error.
96112
func (t *Stdio) closeProcess(process *stdioProcess) error {
113+
if t.process != nil {
114+
return nil
115+
}
116+
97117
_ = process.stdin.Close()
98118

99119
// Wait for the command to finish with a timeout to prevent zombie processes

0 commit comments

Comments
 (0)