An official rust Model Context Protocol SDK implementation with tokio async runtime.
rmcp = { version = "0.1", features = ["server"] }
## or dev channel
rmcp = { git = "https://github.com/modelcontextprotocol/rust-sdk", branch = "main" }
Start a client in one line:
use rmcp::{ServiceExt, transport::TokioChildProcess};
use tokio::process::Command;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = ().serve(
TokioChildProcess::new(Command::new("npx").arg("-y").arg("@modelcontextprotocol/server-everything"))?
).await?;
Ok(())
}
1. Build a transport
use tokio::io::{stdin, stdout};
let transport = (stdin(), stdout());
The transport type must implemented [Transport
] trait, which allow it send message concurrently and receive message sequentially.
There are 3 pairs of standard transport types:
transport | client | server |
---|---|---|
std IO | [child_process::TokioChildProcess ] |
[io::stdio ] |
streamable http | [streamable_http_client::StreamableHttpClientTransport ] |
[streamable_http_server::session::create_session ] |
sse | [sse_client::SseClientTransport ] |
[sse_server::SseServer ] |
IntoTransport trait
[IntoTransport
] is a helper trait that implicitly convert a type into a transport type.
These types is automatically implemented [IntoTransport
] trait
- A type that already implement both [
futures::Sink
] and [futures::Stream
] trait, or a tuple(Tx, Rx)
whereTx
is [futures::Sink
] andRx
is [futures::Stream
]. - A type that implement both [
tokio::io::AsyncRead
] and [tokio::io::AsyncWrite
] trait. or a tuple(R, W)
whereR
is [tokio::io::AsyncRead
] andW
is [tokio::io::AsyncWrite
]. - A type that implement Worker trait.
- A type that implement [
Transport
] trait.
For example, you can see how we build a transport through TCP stream or http upgrade so easily. examples
2. Build a service
You can easily build a service by using ServerHandler
or ClientHandler
.
let service = common::counter::Counter::new();
3. Serve them together
// this call will finish the initialization process
let server = service.serve(transport).await?;
4. Interact with the server
Once the server is initialized, you can send requests or notifications:
// request
let roots = server.list_roots().await?;
// or send notification
server.notify_cancelled(...).await?;
5. Waiting for service shutdown
let quit_reason = server.waiting().await?;
// or cancel it
let quit_reason = server.cancel().await?;
Use toolbox
and tool
macros to create tool quickly.
Example: Calculator Tool
Check this file.
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 difference 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()
}
}
}
The only thing you should do is to make the function's return type implement IntoCallToolResult
.
And you can just implement IntoContents
, and the return value will be marked as success automatically.
If you return a type of Result<T, E>
where T
and E
both implemented IntoContents
, it's also OK.
For many cases you need to manage several service in a collection, you can call into_dyn
to convert services into the same type.
let service = service.into_dyn();
See docs/OAUTH_SUPPORT.md for details.
See examples
client
: use client side sdkserver
: use server side sdkmacros
: macros defaultschemars
: implementJsonSchema
for all model structs
transport-io
: Server stdio transporttransport-sse-server
: Server SSE transporttransport-child-process
: Client stdio transporttransport-sse-client
: Client sse transporttransport-streamable-http-server
streamable http server transporttransport-streamable-client-server
streamable http server transport
- containerd-mcp-server - A containerd-based MCP server implementation
See docs/DEVCONTAINER.md for instructions on using Dev Container for development.