Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Cleanup zombie processes for child process client - #156

Merged
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client
May 18, 2025
Merged

Cleanup zombie processes for child process client#156
4t145 merged 5 commits into
modelcontextprotocol:mainfrom
smallcloudai:cleanup-zombie-processes-for-child-process-client

Conversation

@humbertoyusta

Copy link
Copy Markdown
Contributor

Ensure child processes kill all subprocesses on drop (for transport-child-process)

Motivation and Context

Starting a tokio child processes, in several mcp servers, like using uv some-mcp-server in MacOS, will create subprocesses that will not be cleaned when the first process is killed with the .kill_on_drop(true)

Using a JobObject in Windows and a Process Group in Unix, killing the child process will also clean all subprocesses, avoiding resource leaking

How Has This Been Tested?

We (refact.ai) use this library for integrating mcp servers with our client, we recently switched from another library, so it's not in production yet.

Scenarios about starting servers like with uv were tested to avoid resource leaking, also fakeish scenarios like sh -c "npx some-mcp-server" were tested, causing subprocess leaking before, not now

Breaking Changes

TokioChildProcess::new(command) takes now tokio::process::Command entirely, instead of &mut tokio::process::Command

  • The reasion for this, is that the wrapping library used for process group and job object, requires taking the whole command instead of &mut ref
  • Example tests have been updated accordingly

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • [?] My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling (not needed)
  • I have added or updated documentation as needed

@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from 9262657 to 004acbeCompareMay 6, 2025 10:58
Using process wrap library, Process Group for Unix and Job Object for Windows
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch 2 times, most recently from 362c45b to abccf2eCompareMay 6, 2025 11:21
@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Thank you for PR, I will have a look on this!

@4t145

4t145 commented May 7, 2025

Copy link
Copy Markdown
Contributor

Looks good!

Is there a way to provide a chained call api to create a cmd? Just want to give out a more easy to use interface.

And it will be great if you can update the document like README.

…ommand
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
…command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from abccf2e to 9da3f84CompareMay 7, 2025 15:52
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Thanks for the review,

I updated README and the other example that was missing.

I couldn't find a way to provide an inline chained way, due to the fact that most std/tokio::process::Command methods like args(), envs() take and return &mut Command instead of Command, so if you chain it in one line, there's no way to get the Command again.

I checked what process-wrap does for this, but they just use command in non-inline way, or create their own structs directly.

TokioChildProcess::new({
let mut cmd = tokio::process::Command::new("npx");
cmd.arg("mcp-server-git")
cmd
})

will workfor inline way, but this is just the same as doing it before, I coulnd't find a clean way to chain it inline

@4t145
4t145 requested a review from CopilotMay 8, 2025 06:17

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR updates the TokioChildProcess API to consume a full tokio::process::Command (instead of a mutable reference) and integrates the process_wrap crate to ensure that child processes cleanup their subprocesses (using a JobObject on Windows and a Process Group on Unix). The changes span multiple examples, tests, and the core transport implementation.

  • Updated TokioChildProcess::new to take ownership of the command and chain additional configurations.
  • Adjusted various examples and tests to use the new command initialization pattern.
  • Introduced a ChildWithCleanup wrapper that utilizes process_wrap for proper child process termination.

Reviewed Changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
FileDescription
examples/simple-chat-client/src/config.rsUpdated command creation to comply with the new API
examples/simple-chat-client/Cargo.tomlChanged rmcp dependency from git to local path
examples/rig-integration/src/config/mcp.rsUpdated command initialization per new API
examples/clients/src/std_io.rsRefactored command creation and updated commented examples
examples/clients/src/everything_stdio.rsModified command creation for consistency
examples/clients/src/collection.rsUpdated command creation; note change in argument value
docs/readme/README.zh-cn.mdRefactored example to show new command initialization
crates/rmcp/tests/test_with_python.rsUpdated test command creation
crates/rmcp/tests/test_with_js.rsUpdated test command creation
crates/rmcp/src/transport/child_process.rsRefactored transport module to wrap child process with cleanup
crates/rmcp/src/lib.rsUpdated documentation to reflect the new API
crates/rmcp/Cargo.tomlAdded process-wrap dependency and updated transport features
README.mdUpdated quick-start example to use new command pattern
Comments suppressed due to low confidence (1)

examples/clients/src/collection.rs:22

  • [nitpick] Verify that changing the argument from 'mcp-server-git' to 'mcp-client-git' is intentional and consistent with the intended behavior of the example.
cmd.arg("mcp-client-git");


impl Drop for ChildWithCleanup {
fn drop(&mut self) {
let _ = self.inner.start_kill();

CopilotAIMay 8, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Consider logging or handling errors from 'start_kill()' in the Drop implementation to aid in debugging potential cleanup issues.

Copilot uses AI. Check for mistakes.
@4t145

4t145 commented May 8, 2025

Copy link
Copy Markdown
Contributor

How about create a command wrapper in rmcp? It could be something like this.

#[derive(Debug)]pubstructCommand{tokio: tokio::process::Command,}implFrom<tokio::process::Command>forCommand{fnfrom(tokio: tokio::process::Command) -> Self{Self{ tokio }}}implCommand{pubfnnew<S:AsRef<OsStr>>(program:S) -> Command{Self::from(tokio::process::Command::new(program))}pubfnarg<S:AsRef<OsStr>>(mutself,arg:S) -> Command{self.tokio.arg(arg);self}pubfnargs<I,S>(mutself,args:I) -> CommandwhereI:IntoIterator<Item = S>,S:AsRef<OsStr>,{self.tokio.args(args);self}pubfnenv<K,V>(mutself,key:K,val:V) -> CommandwhereK:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.env(key, val);self}pubfnenvs<I,K,V>(mutself,vars:I) -> CommandwhereI:IntoIterator<Item = (K,V)>,K:AsRef<OsStr>,V:AsRef<OsStr>,{self.tokio.envs(vars);self}pubfninto_child_process(self) -> std::io::Result<TokioChildProcess>{TokioChildProcess::new(self.tokio)}pubfninto_tokio(self) -> tokio::process::Command{self.tokio}pubfnas_tokio_mut(&mutself) -> &mut tokio::process::Command{&mutself.tokio}pubfnas_tokio(&self) -> &tokio::process::Command{&self.tokio}}

And we can use it like this

let transport = Command::new("node").arg("tests/test_with_js/server.js").into_child_process()?;let client = ().serve(transport).await?;

Will it be confusing for there are too many Command type?

@jokemanfire

jokemanfire commented May 9, 2025

Copy link
Copy Markdown
Member

How about add a event listener , ant use wait_pid to recycle the children process ?I think we only need to focus on the possibility of zombie like child processes during the runtime of the main process.

@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

One option for inline could be to modify examples to do like:

TokioChildProcess::new(tokio::process::Command::new("node").map(
|cmd| { cmd.arg("tests/test_with_js/client.js"); cmd }))

Another could be for people who use tap crate, using tap_mut, but it's more or less the same, we could provide that trait to command, but it would need to be imported and it's not much less verbose

use rmcp::transport::child_process::CommandTapMut;TokioChildProcess::new(tokio::process::Command::new("node").tap_mut(
|cmd| { cmd.arg("tests/test_with_js/client.js");}))

Or we could do a wrapper to command like proposed before, but there will be several Command structs then, not sure which is the best approach.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta The first approach looks great. I am okay with it.

Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
@humbertoyusta

Copy link
Copy Markdown
ContributorAuthor

Actually .map() didn't work, I thought Command::new() was giving result but it's not, so I needed to add a trait for tokio::process::Command.

I still think it's better than a full Command wrapper, but if you think otherwise let me know.

@4t145

Copy link
Copy Markdown
Contributor

@humbertoyusta I am ok with the ext trait.

Looks there's confict and ci didn't pass. So could you merge it with the main branch, for I just made a lot of changes, and fix the formatting and test ci? Thanks for your patience!

@4t1454t145 mentioned this pull request May 18, 2025
14 tasks
@humbertoyusta
humbertoyustaforce-pushed the cleanup-zombie-processes-for-child-process-client branch from ff874f0 to d92b46aCompareMay 18, 2025 10:26
@4t145

Copy link
Copy Markdown
Contributor

It's okey with the ci message, I can rewrite it when merge.

@4t145
4t145 merged commit 64995cc into modelcontextprotocol:mainMay 18, 2025
@4t145

Copy link
Copy Markdown
Contributor

And thanks for your work!

@github-actionsgithub-actionsBot mentioned this pull request Jul 2, 2025
takumi-earth pushed a commit to earthlings-dev/rmcp that referenced this pull request Jan 27, 2026
…rotocol#156)
* feat(client): cleanup of zombie processes in child process client
Using process wrap library, Process Group for Unix and Job Object for Windows
* fix: install extra dep based on feature, update examples take owned command
Install process-wrap if transport-child-process feature is enabled. Update examples to take ownership
of the command instead of mutable reference
* fix: update other examples, comments and readme to take ownership of command
Updated more examples, comments and readme to take command instead of mutable ref.
Also small fix in cargo toml of example to use local path.
* refactor: add configure command ext
Added configure command ext to tokio process command so that
you can use .configure() to use inline commands for mcp stdio client.
Added warning if start kill process fails
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@humbertoyusta@4t145@jokemanfire