modelcontextprotocol / modelcontextprotocol/java-sdk

StdioClientTransport should support bounded child process termination

Open
#937 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement P2 ready for work
Dominant language
Java
Stars
3.7k
Forks
1.1k
Avg merge
1d 15h
Merged PRs (30d)
9

Description

StdioClientTransport starts and manages a child MCP server process. During graceful shutdown, the SDK transport currently calls process.destroy() and waits for process.onExit().

That works when the child process responds to SIGTERM, but it can hang indefinitely if the child process ignores termination or becomes stuck. Downstream, we had to implement a custom McpClientTransport to ensure proxied MCP server processes are always cleaned up when the parent shuts down.

Current behavior

The current shutdown flow is effectively:

if (this.process != null) {
  this.process.destroy();
  return Mono.fromFuture(process.onExit());
}

There is no configurable timeout and no fallback to destroyForcibly().

Expected behavior

StdioClientTransport.closeGracefully() should support bounded process termination:

  1. Stop accepting new messages.
  2. Complete the transport sinks.
  3. Send graceful termination with process.destroy().
  4. Wait up to a configurable timeout.
  5. If the process is still alive, call process.destroyForcibly().
  6. Dispose the transport schedulers.

This guarantees that child MCP server processes do not survive parent shutdown indefinitely.

Proposed fix

Add a configurable process termination timeout to StdioClientTransport, with a sensible default.

Conceptually:

process.destroy();

return Mono.fromFuture(process.onExit())
  .timeout(processTerminationTimeout, Mono.defer(() -> {
    if (process.isAlive()) {
      process.destroyForcibly();
    }
    return Mono.fromFuture(process.onExit());
  }));

This could be exposed through the existing ServerParameters builder or a dedicated StdioClientTransport constructor/builder option.

Suggested regression test

Add a test with a child process that ignores SIGTERM or does not exit promptly.

The test should verify that:

  1. closeGracefully() completes within the configured timeout plus a small buffer.
  2. The child process is no longer alive after closeGracefully() completes.
  3. The transport schedulers and sinks are cleaned up.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at StdioClientTransport.closeGracefully() and inspect how ServerParameters configures the transport and how process.onExit() is awaited. Add a configurable termination timeout, then run or add the suggested regression test with a child process that ignores SIGTERM. Done means shutdown completes within the timeout buffer, forcibly terminates a surviving child, and cleans up schedulers and sinks.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.