一个基于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};
use tokio::process::Command;
let client = ().serve(
TokioChildProcess::new(Command::new("npx").arg("-y").arg("@modelcontextprotocol/server-everything"))?
).await?;
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
- 已经同时实现了
Sink
和Stream
trait的类型。 - 由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
你可以通过 ServerHandler
或 ClientHandler
轻松构建服务
let service = common::counter::Counter::new();
// 这里会自动完成初始化流程
let server = service.serve(transport).await?;
一旦服务初始化完成,你可以发送请求或通知:
// 请求
let roots = server.list_roots().await?;
// 或发送通知
server.notify_cancelled(...).await?;
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传输