rust-sdk/docs/readme/README.zh-cn.md
2025-03-27 18:52:52 -04:00

5.3 KiB
Raw Blame History

RMCP

Crates.io Version Release status docs.rs

一个干净且完整的 MCP SDK

使用

导入

rmcp = { version = "0.1", features = ["server"] }
## 或者开发者频道
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }

快速上手

你可以用一行代码启动一个SSE客户端

use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;

let client = ().serve(
    TokioChildProcess::new(Command::new("npx").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
  1. 兼具 SinkStream
  2. 一对 Sink Tx Stream Rx, 类型 (Tx, Rx) 自动实现 IntoTransport
  3. 兼具 [tokio::io::AsyncRead] 与 [tokio::io::AsyncWrite] 的
  4. 一对 Sink [tokio::io::AsyncRead] R [tokio::io::AsyncWrite] W, 类型 (R, W)自动实现 IntoTransport

示例你可以轻松创建一个TCP流来作为传输层. examples

2. 构建服务

你可以通过 ServerHandlerClientHandler 轻松构建服务

let service = common::counter::Counter::new();

如果你想用 tower, 你也可以使用 [TowerHandler] 来作为tower服务的适配器.

请参考 服务用例.

3. 把他们组装到一起

// 这里会自动完成初始化流程
let server = service.serve(transport).await?;

4. 与服务端/客户端交互

一旦你完成初始化,你可以发送请求或者发送通知

// request 
let roots = server.list_roots().await?;

// or send notification
server.notify_cancelled(...).await?;

5. 等待服务结束

let quit_reason = server.waiting().await?;
// or cancel it
let quit_reason = server.cancel().await?;

使用宏来定义工具

使用 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> ,其中 TE 都实现了 IntoContents, 那就会自动标记成功或者失败。

管理多个服务

在很多情况下你需要把不同类型的服务管理在一个集合当中,你可以调用 into_dyn 来把他们都转化成动态类型。

let service = service.into_dyn();

用例

查看 用例文件夹

Features

  • client: 使用客户端sdk
  • server: 使用服务端sdk

相关资源