* 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
143 lines
4.3 KiB
Rust
143 lines
4.3 KiB
Rust
use rmcp::{
|
|
ServiceExt,
|
|
service::QuitReason,
|
|
transport::{
|
|
ConfigureCommandExt, SseServer, StreamableHttpClientTransport, TokioChildProcess,
|
|
streamable_http_server::axum::StreamableHttpServer,
|
|
},
|
|
};
|
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
|
mod common;
|
|
use common::calculator::Calculator;
|
|
|
|
const SSE_BIND_ADDRESS: &str = "127.0.0.1:8000";
|
|
const STREAMABLE_HTTP_BIND_ADDRESS: &str = "127.0.0.1:8001";
|
|
const STREAMABLE_HTTP_JS_BIND_ADDRESS: &str = "127.0.0.1:8002";
|
|
|
|
#[tokio::test]
|
|
async fn test_with_js_client() -> anyhow::Result<()> {
|
|
let _ = tracing_subscriber::registry()
|
|
.with(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "debug".to_string().into()),
|
|
)
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.try_init();
|
|
tokio::process::Command::new("npm")
|
|
.arg("install")
|
|
.current_dir("tests/test_with_js")
|
|
.spawn()?
|
|
.wait()
|
|
.await?;
|
|
|
|
let ct = SseServer::serve(SSE_BIND_ADDRESS.parse()?)
|
|
.await?
|
|
.with_service(Calculator::default);
|
|
|
|
let exit_status = tokio::process::Command::new("node")
|
|
.arg("tests/test_with_js/client.js")
|
|
.spawn()?
|
|
.wait()
|
|
.await?;
|
|
assert!(exit_status.success());
|
|
ct.cancel();
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_with_js_server() -> anyhow::Result<()> {
|
|
let _ = tracing_subscriber::registry()
|
|
.with(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "debug".to_string().into()),
|
|
)
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.try_init();
|
|
tokio::process::Command::new("npm")
|
|
.arg("install")
|
|
.current_dir("tests/test_with_js")
|
|
.spawn()?
|
|
.wait()
|
|
.await?;
|
|
let transport =
|
|
TokioChildProcess::new(tokio::process::Command::new("node").configure(|cmd| {
|
|
cmd.arg("tests/test_with_js/server.js");
|
|
}))?;
|
|
|
|
let client = ().serve(transport).await?;
|
|
let resources = client.list_all_resources().await?;
|
|
tracing::info!("{:#?}", resources);
|
|
let tools = client.list_all_tools().await?;
|
|
tracing::info!("{:#?}", tools);
|
|
|
|
client.cancel().await?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_with_js_streamable_http_client() -> anyhow::Result<()> {
|
|
let _ = tracing_subscriber::registry()
|
|
.with(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "debug".to_string().into()),
|
|
)
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.try_init();
|
|
tokio::process::Command::new("npm")
|
|
.arg("install")
|
|
.current_dir("tests/test_with_js")
|
|
.spawn()?
|
|
.wait()
|
|
.await?;
|
|
|
|
let ct = StreamableHttpServer::serve(STREAMABLE_HTTP_BIND_ADDRESS.parse()?)
|
|
.await?
|
|
.with_service(Calculator::default);
|
|
|
|
let exit_status = tokio::process::Command::new("node")
|
|
.arg("tests/test_with_js/streamable_client.js")
|
|
.spawn()?
|
|
.wait()
|
|
.await?;
|
|
assert!(exit_status.success());
|
|
ct.cancel();
|
|
Ok(())
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_with_js_streamable_http_server() -> anyhow::Result<()> {
|
|
let _ = tracing_subscriber::registry()
|
|
.with(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| "debug".to_string().into()),
|
|
)
|
|
.with(tracing_subscriber::fmt::layer())
|
|
.try_init();
|
|
tokio::process::Command::new("npm")
|
|
.arg("install")
|
|
.current_dir("tests/test_with_js")
|
|
.spawn()?
|
|
.wait()
|
|
.await?;
|
|
|
|
let transport = StreamableHttpClientTransport::from_uri(format!(
|
|
"http://{STREAMABLE_HTTP_JS_BIND_ADDRESS}/mcp"
|
|
));
|
|
|
|
let mut server = tokio::process::Command::new("node")
|
|
.arg("tests/test_with_js/streamable_server.js")
|
|
.spawn()?;
|
|
|
|
// waiting for server up
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await;
|
|
|
|
let client = ().serve(transport).await?;
|
|
let resources = client.list_all_resources().await?;
|
|
tracing::info!("{:#?}", resources);
|
|
let tools = client.list_all_tools().await?;
|
|
tracing::info!("{:#?}", tools);
|
|
let quit_reason = client.cancel().await?;
|
|
server.kill().await?;
|
|
assert!(matches!(quit_reason, QuitReason::Cancelled));
|
|
Ok(())
|
|
}
|