* 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 |
||
|---|---|---|
| .. | ||
| README.zh-cn.md | ||
RMCP
一个基于tokio异步运行时的官方Model Context Protocol SDK实现。
使用
导入
rmcp = { version = "0.1", features = ["server"] }
## 或者开发者频道
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }
快速上手
一行代码启动客户端:
use rmcp::{ServiceExt, transport::{TokioChildProcess, ConfigureCommandExt}};
use tokio::process::Command;
let client = ().serve(TokioChildProcess::new(Command::new("npx").configure(|cmd| {
cmd.arg("-y").arg("@modelcontextprotocol/server-everything");
}))?).await?;
1. 构建传输层
use tokio::io::{stdin, stdout};
let transport = (stdin(), stdout());
传输层类型必须实现 IntoTransport trait, 这个特性允许分割成一个sink和一个stream。
对于客户端, Sink 的 Item 是 ClientJsonRpcMessage, Stream 的 Item 是 ServerJsonRpcMessage
对于服务端, Sink 的 Item 是 ServerJsonRpcMessage, Stream 的 Item 是 ClientJsonRpcMessage
这些类型自动实现了 IntoTransport trait
- 已经同时实现了
Sink和Streamtrait的类型。 - 由sink
Tx和 streamRx组成的元组:(Tx, Rx)。 - 同时实现了 [
tokio::io::AsyncRead] 和 [tokio::io::AsyncWrite] trait的类型。 - 由 [
tokio::io::AsyncRead]R和 [tokio::io::AsyncWrite]W组成的元组:(R, W)。
例如,你可以看到我们如何轻松地通过TCP流或http升级构建传输层。 examples
2. 构建服务
你可以通过 ServerHandler 或 ClientHandler 轻松构建服务
let service = common::counter::Counter::new();
3. 把他们组装到一起
// 这里会自动完成初始化流程
let server = service.serve(transport).await?;
4. 与服务交互
一旦服务初始化完成,你可以发送请求或通知:
// 请求
let roots = server.list_roots().await?;
// 或发送通知
server.notify_cancelled(...).await?;
5. 等待服务关闭
let quit_reason = server.waiting().await?;
// 或取消它
let quit_reason = server.cancel().await?;
使用宏来声明工具
使用 toolbox 和 tool 宏来快速创建工具。
请看这个文件。
use rmcp::{ServerHandler, model::ServerInfo, schemars, tool};
use super::counter::Counter;
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct SumRequest {
#[schemars(description = "the left hand side number")]
pub a: i32,
#[schemars(description = "the right hand side number")]
pub b: i32,
}
#[derive(Debug, Clone)]
pub struct Calculator;
// create a static toolbox to store the tool attributes
#[tool(tool_box)]
impl Calculator {
// async function
#[tool(description = "Calculate the sum of two numbers")]
async fn sum(&self, #[tool(aggr)] SumRequest { a, b }: SumRequest) -> String {
(a + b).to_string()
}
// sync function
#[tool(description = "Calculate the sum of two numbers")]
fn sub(
&self,
#[tool(param)]
// this macro will transfer the schemars and serde's attributes
#[schemars(description = "the left hand side number")]
a: i32,
#[tool(param)]
#[schemars(description = "the right hand side number")]
b: i32,
) -> String {
(a - b).to_string()
}
}
// impl call_tool and list_tool by querying static toolbox
#[tool(tool_box)]
impl ServerHandler for Calculator {
fn get_info(&self) -> ServerInfo {
ServerInfo {
instructions: Some("A simple calculator".into()),
..Default::default()
}
}
}
你要做的唯一事情就是确保函数的返回类型实现了 IntoCallToolResult。
你可以为返回类型实现 IntoContents,那么返回值将自动标记为成功。
如果返回类型是 Result<T, E>,其中 T 与 E 都实现了 IntoContents,那也是可以的。
管理多个服务
在很多情况下你需要在一个集合中管理多个服务,你可以调用 into_dyn 来将服务转换为相同类型。
let service = service.into_dyn();
示例
查看 examples
功能特性
client: 使用客户端sdkserver: 使用服务端sdkmacros: 宏默认
传输层
transport-io: 服务端标准输入输出传输transport-sse-server: 服务端SSE传输transport-child-process: 客户端标准输入输出传输transport-sse: 客户端SSE传输