Skip to content

Support clean shutdown for stdio servers (from a client PoV) #347

@crepererum

Description

@crepererum

Is your feature request related to a problem? Please describe.
If you want to use an stdio server, you wrap a tokio process into a service like this:

let client = ()
.serve(
TokioChildProcess::new(Command::new("uvx").configure(|cmd| {
cmd.arg("mcp-server-git");
}))
.map_err(RmcpError::transport_creation::<TokioChildProcess>)?,
)
.await?;

Now the service owns the process and there is generally no way (that I'm aware of) to get this ownership back. The only way of shutting down the server is to call cancel. Based on experiments with my own server implementation, this however seems to just drop the tokio process which in turns calls SIGKILL (on Unix at least). However the protocol spec states (link):

For the stdio transport, the client SHOULD initiate shutdown by:

  1. First, closing the input stream to the child process (the server)
  2. Waiting for the server to exit, or sending SIGTERM if the server does not exit within a reasonable time
  3. Sending SIGKILL if the server does not exit within a reasonable time after SIGTERM

The server MAY initiate shutdown by closing its output stream to the client and exiting.

So we kinda directly jump to step 3, i.e. the last resort.

Describe the solution you'd like
I think the high-level Rust API is fine, but I think it should result in a proper shutdown with all three steps.

Describe alternatives you've considered
One can work around this by abusing step 2 in the shutdown sequence. Before you pass the tokio process to the serve (which will use serve_client_with_ct) one can get the PID. With that, one can use libc or nix to send SIGTERM. However this has two drawbacks:

Additional context
This is relevant for version 0.3.0.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions