1
+ use std:: process:: Stdio ;
2
+
1
3
use process_wrap:: tokio:: { TokioChildWrapper , TokioCommandWrap } ;
2
4
use tokio:: {
3
5
io:: AsyncRead ,
4
- process:: { ChildStdin , ChildStdout } ,
6
+ process:: { ChildStderr , ChildStdin , ChildStdout } ,
5
7
} ;
6
8
7
9
use super :: { IntoTransport , Transport } ;
8
10
use crate :: service:: ServiceRole ;
9
11
10
- pub ( crate ) fn child_process (
11
- mut child : Box < dyn TokioChildWrapper > ,
12
- ) -> std:: io:: Result < ( Box < dyn TokioChildWrapper > , ( ChildStdout , ChildStdin ) ) > {
12
+ /// The parts of a child process.
13
+ type ChildProcessParts = (
14
+ Box < dyn TokioChildWrapper > ,
15
+ ChildStdout ,
16
+ ChildStdin ,
17
+ Option < ChildStderr > ,
18
+ ) ;
19
+
20
+ /// Extract the stdio handles from a spawned child.
21
+ /// Returns `(child, stdout, stdin, stderr)` where `stderr` is `Some` only
22
+ /// if the process was spawned with `Stdio::piped()`.
23
+ #[ inline]
24
+ fn child_process ( mut child : Box < dyn TokioChildWrapper > ) -> std:: io:: Result < ChildProcessParts > {
13
25
let child_stdin = match child. inner_mut ( ) . stdin ( ) . take ( ) {
14
26
Some ( stdin) => stdin,
15
- None => return Err ( std:: io:: Error :: other ( "std in was taken" ) ) ,
27
+ None => return Err ( std:: io:: Error :: other ( "stdin was already taken" ) ) ,
16
28
} ;
17
29
let child_stdout = match child. inner_mut ( ) . stdout ( ) . take ( ) {
18
30
Some ( stdout) => stdout,
19
- None => return Err ( std:: io:: Error :: other ( "std out was taken" ) ) ,
31
+ None => return Err ( std:: io:: Error :: other ( "stdout was already taken" ) ) ,
20
32
} ;
21
- Ok ( ( child, ( child_stdout, child_stdin) ) )
33
+ let child_stderr = child. inner_mut ( ) . stderr ( ) . take ( ) ;
34
+ Ok ( ( child, child_stdout, child_stdin, child_stderr) )
22
35
}
23
36
24
37
pub struct TokioChildProcess {
@@ -66,38 +79,23 @@ impl AsyncRead for TokioChildProcessOut {
66
79
}
67
80
68
81
impl TokioChildProcess {
69
- /// Create a new Tokio child process with the given command.
70
- ///
71
- /// # Manage the child process
72
- /// You can also check these issue and pr for more information on how to manage the child process:
73
- /// - [#156](https://github.com/modelcontextprotocol/rust-sdk/pull/156)
74
- /// - [#253](https://github.com/modelcontextprotocol/rust-sdk/issues/253)
75
- /// ```rust,ignore
76
- /// #[cfg(unix)]
77
- /// command_wrap.wrap(process_wrap::tokio::ProcessGroup::leader());
78
- /// #[cfg(windows)]
79
- /// command_wrap.wrap(process_wrap::tokio::JobObject);
80
- /// ```
81
- ///
82
+ /// Convenience: spawn with default `piped` stdio
82
83
pub fn new ( command : impl Into < TokioCommandWrap > ) -> std:: io:: Result < Self > {
83
- let mut command_wrap = command. into ( ) ;
84
- command_wrap
85
- . command_mut ( )
86
- . stdin ( std:: process:: Stdio :: piped ( ) )
87
- . stdout ( std:: process:: Stdio :: piped ( ) ) ;
88
- let ( child, ( child_stdout, child_stdin) ) = child_process ( command_wrap. spawn ( ) ?) ?;
89
- Ok ( Self {
90
- child : ChildWithCleanup { inner : child } ,
91
- child_stdin,
92
- child_stdout,
93
- } )
84
+ let ( proc, _ignored) = TokioChildProcessBuilder :: new ( command) . spawn ( ) ?;
85
+ Ok ( proc)
86
+ }
87
+
88
+ /// Builder entry-point allowing fine-grained stdio control.
89
+ pub fn builder ( command : impl Into < TokioCommandWrap > ) -> TokioChildProcessBuilder {
90
+ TokioChildProcessBuilder :: new ( command)
94
91
}
95
92
96
93
/// Get the process ID of the child process.
97
94
pub fn id ( & self ) -> Option < u32 > {
98
95
self . child . inner . id ( )
99
96
}
100
97
98
+ /// Split this helper into a reader (stdout) and writer (stdin).
101
99
pub fn split ( self ) -> ( TokioChildProcessOut , ChildStdin ) {
102
100
let TokioChildProcess {
103
101
child,
@@ -114,6 +112,59 @@ impl TokioChildProcess {
114
112
}
115
113
}
116
114
115
+ /// Builder for `TokioChildProcess` allowing custom `Stdio` configuration.
116
+ pub struct TokioChildProcessBuilder {
117
+ cmd : TokioCommandWrap ,
118
+ stdin : Stdio ,
119
+ stdout : Stdio ,
120
+ stderr : Stdio ,
121
+ }
122
+
123
+ impl TokioChildProcessBuilder {
124
+ fn new ( cmd : impl Into < TokioCommandWrap > ) -> Self {
125
+ Self {
126
+ cmd : cmd. into ( ) ,
127
+ stdin : Stdio :: piped ( ) ,
128
+ stdout : Stdio :: piped ( ) ,
129
+ stderr : Stdio :: inherit ( ) ,
130
+ }
131
+ }
132
+
133
+ /// Override the child stdin configuration.
134
+ pub fn stdin ( mut self , io : impl Into < Stdio > ) -> Self {
135
+ self . stdin = io. into ( ) ;
136
+ self
137
+ }
138
+ /// Override the child stdout configuration.
139
+ pub fn stdout ( mut self , io : impl Into < Stdio > ) -> Self {
140
+ self . stdout = io. into ( ) ;
141
+ self
142
+ }
143
+ /// Override the child stderr configuration.
144
+ pub fn stderr ( mut self , io : impl Into < Stdio > ) -> Self {
145
+ self . stderr = io. into ( ) ;
146
+ self
147
+ }
148
+
149
+ /// Spawn the child process. Returns the transport plus an optional captured stderr handle.
150
+ pub fn spawn ( mut self ) -> std:: io:: Result < ( TokioChildProcess , Option < ChildStderr > ) > {
151
+ self . cmd
152
+ . command_mut ( )
153
+ . stdin ( self . stdin )
154
+ . stdout ( self . stdout )
155
+ . stderr ( self . stderr ) ;
156
+
157
+ let ( child, stdout, stdin, stderr_opt) = child_process ( self . cmd . spawn ( ) ?) ?;
158
+
159
+ let proc = TokioChildProcess {
160
+ child : ChildWithCleanup { inner : child } ,
161
+ child_stdin : stdin,
162
+ child_stdout : stdout,
163
+ } ;
164
+ Ok ( ( proc, stderr_opt) )
165
+ }
166
+ }
167
+
117
168
impl < R : ServiceRole > IntoTransport < R , std:: io:: Error , ( ) > for TokioChildProcess {
118
169
fn into_transport ( self ) -> impl Transport < R , Error = std:: io:: Error > + ' static {
119
170
IntoTransport :: < R , std:: io:: Error , super :: async_rw:: TransportAdapterAsyncRW > :: into_transport (
0 commit comments