|
| 1 | +# Getting Started with Rust MCP SDK |
| 2 | + |
| 3 | +This guide walks you through setting up and running an MCP server using the `rust-mcp-sdk` crate. Let's get your server up and running in minutes! |
| 4 | + |
| 5 | +In this example, the MCP server we are building includes two MCP tools, `say_hello` and `say_goodbye`, which each take a name argument and print a personalized greeting message. |
| 6 | + |
| 7 | +## Prerequisites |
| 8 | + |
| 9 | +- Rust (install via [rustup](https://rustup.rs/)) |
| 10 | +- Cargo (comes with Rust) |
| 11 | + |
| 12 | +## Step 1: Create a new project |
| 13 | + |
| 14 | +Create a new Rust project: |
| 15 | + |
| 16 | +```bash |
| 17 | +cargo new hello-world-mcp-server |
| 18 | +cd hello-world-mcp-server |
| 19 | +``` |
| 20 | + |
| 21 | +## Step 2: Add Dependencies |
| 22 | + |
| 23 | +Next, we’ll add the Rust MCP dependencies for the toolkit, schema, and runtime support. We’ll also include `tokio` and `async-trait` for async functionality, plus `serde` and `serde_json` to enable [rust-mcp-macros](https://crates.io/crates/rust-mcp-macros) for MCP tool development. |
| 24 | + |
| 25 | +```sh |
| 26 | +# rust-mcp dependencies |
| 27 | +cargo add rust-mcp-sdk rust-mcp-schema |
| 28 | + |
| 29 | +# other required dependencies |
| 30 | +cargo add tokio async-trait serde serde_json |
| 31 | +``` |
| 32 | + |
| 33 | +This is what your Cargo.toml looks like after the dependencies are added: |
| 34 | + |
| 35 | +```toml |
| 36 | +[package] |
| 37 | +name = "hello-world-mcp-server" |
| 38 | +version = "0.1.0" |
| 39 | +edition = "2021" |
| 40 | + |
| 41 | +[dependencies] |
| 42 | +async-trait = "0.1.88" |
| 43 | +rust-mcp-schema = "0.2.1" |
| 44 | +rust-mcp-sdk = "0.1.1" |
| 45 | +serde = "1.0.219" |
| 46 | +serde_json = "1.0.140" |
| 47 | +tokio = "1.44.1" |
| 48 | +``` |
| 49 | + |
| 50 | +## Step3: update the `main()` function |
| 51 | + |
| 52 | +Update `main()` function to use Tokio's asynchronous runtime and return `SdkResult<()>` from `rust_mcp_sdk` crate: |
| 53 | + |
| 54 | +```rs |
| 55 | +// src/main.rs |
| 56 | +use rust_mcp_sdk::error::SdkResult; |
| 57 | + |
| 58 | +#[tokio::main] |
| 59 | +async fn main() -> SdkResult<()> { |
| 60 | + |
| 61 | + // The main function will be completed in the following steps. |
| 62 | + |
| 63 | + Ok(()) |
| 64 | +} |
| 65 | +``` |
| 66 | + |
| 67 | +## Step 4: Define `say_hello` and `say_goodbye` tools |
| 68 | + |
| 69 | +Create a new module in the project called `tools.rs` and include the definitions for the `say_hello` and `say_goodbye` tools within it. |
| 70 | +`mcp_tool` and `JsonSchema` macros from [rust-mcp-macros](https://crates.io/crates/rust-mcp-macros) crate makes it very simple to turn any simple struct into legitimate MCP Tool: |
| 71 | + |
| 72 | +```rust |
| 73 | +//src/tools.rs |
| 74 | +use rust_mcp_schema::{schema_utils::CallToolError, CallToolResult}; |
| 75 | +use rust_mcp_sdk::{ |
| 76 | + macros::{mcp_tool, JsonSchema}, |
| 77 | + tool_box, |
| 78 | +}; |
| 79 | + |
| 80 | + |
| 81 | +//****************// |
| 82 | +// SayHelloTool // |
| 83 | +//****************// |
| 84 | +#[mcp_tool( |
| 85 | + name = "say_hello", |
| 86 | + description = "Accepts a person's name and says a personalized \"Hello\" to that person" |
| 87 | +)] |
| 88 | +#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, JsonSchema)] |
| 89 | +pub struct SayHelloTool { |
| 90 | + /// The name of the person to greet with a "Hello". |
| 91 | + name: String, |
| 92 | +} |
| 93 | + |
| 94 | +impl SayHelloTool { |
| 95 | + pub fn call_tool(&self) -> Result<CallToolResult, CallToolError> { |
| 96 | + let hello_message = format!("Hello, {}!", self.name); |
| 97 | + Ok(CallToolResult::text_content(hello_message, None)) |
| 98 | + } |
| 99 | +} |
| 100 | + |
| 101 | +//******************// |
| 102 | +// SayGoodbyeTool // |
| 103 | +//******************// |
| 104 | +#[mcp_tool( |
| 105 | + name = "say_goodbye", |
| 106 | + description = "Accepts a person's name and says a personalized \"Goodbye\" to that person." |
| 107 | +)] |
| 108 | +#[derive(Debug, ::serde::Deserialize, ::serde::Serialize, JsonSchema)] |
| 109 | +pub struct SayGoodbyeTool { |
| 110 | + /// The name of the person to say goodbye to. |
| 111 | + name: String, |
| 112 | +} |
| 113 | +impl SayGoodbyeTool { |
| 114 | + pub fn call_tool(&self) -> Result<CallToolResult, CallToolError> { |
| 115 | + let hello_message = format!("Goodbye, {}!", self.name); |
| 116 | + Ok(CallToolResult::text_content(hello_message, None)) |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +//******************// |
| 121 | +// GreetingTools // |
| 122 | +//******************// |
| 123 | +// Generates an enum names GreetingTools, with SayHelloTool and SayGoodbyeTool variants |
| 124 | +tool_box!(GreetingTools, [SayHelloTool, SayGoodbyeTool]); |
| 125 | + |
| 126 | +``` |
| 127 | + |
| 128 | +## Step 5: Create a handler for handling MCP Messages |
| 129 | + |
| 130 | +We need to create a handler for handling MCP messages including requests and notifications coming from the MCP Client. |
| 131 | + |
| 132 | +> **Note:** [rust-mcp-sdk](https://github.com/rust-mcp-stack/rust-mcp-sdk) provides two type of handler traits that we can chose from:`mcp_server_handler` which is recommended for most use cases and `mcp_server_handler_core` which gives us more control but we will be responsible for every incoming request, notification and errors. |
| 133 | +
|
| 134 | +For this example we create a simple struct, and implement the `mcp_server_handler` for it, overriding two methods that we need : |
| 135 | + |
| 136 | +- `handle_list_tools_request()` that returns list of available tools our server supports. |
| 137 | +- `handle_call_tool_request()` that will be called when MCP Client requests our server to call a tool and return the result. |
| 138 | + |
| 139 | +Lets do that, by adding a new module to the `main.rs` and creating the module file : `handler.rs` |
| 140 | + |
| 141 | +Here is the code for `handler.rs` : |
| 142 | + |
| 143 | +```rs |
| 144 | +// src/handler.rs |
| 145 | + |
| 146 | +use async_trait::async_trait; |
| 147 | +use rust_mcp_schema::{ |
| 148 | + schema_utils::CallToolError, CallToolRequest, CallToolResult, JsonrpcErrorError, |
| 149 | + ListToolsRequest, ListToolsResult, |
| 150 | +}; |
| 151 | +use rust_mcp_sdk::{mcp_server::ServerHandler, MCPServer}; |
| 152 | + |
| 153 | +use crate::tools::GreetingTools; |
| 154 | + |
| 155 | +// Custom Handler to handle MCP Messages |
| 156 | +pub struct MyServerHandler; |
| 157 | + |
| 158 | +#[async_trait] |
| 159 | +impl ServerHandler for MyServerHandler { |
| 160 | + // Handle ListToolsRequest, return list of available tools as ListToolsResult |
| 161 | + async fn handle_list_tools_request( |
| 162 | + &self, |
| 163 | + request: ListToolsRequest, |
| 164 | + runtime: &dyn MCPServer, |
| 165 | + ) -> std::result::Result<ListToolsResult, JsonrpcErrorError> { |
| 166 | + Ok(ListToolsResult { |
| 167 | + meta: None, |
| 168 | + next_cursor: None, |
| 169 | + tools: GreetingTools::get_tools(), |
| 170 | + }) |
| 171 | + } |
| 172 | + |
| 173 | + //Handles incoming CallToolRequest and processes it using the appropriate tool. |
| 174 | + async fn handle_call_tool_request( |
| 175 | + &self, |
| 176 | + request: CallToolRequest, |
| 177 | + runtime: &dyn MCPServer, |
| 178 | + ) -> std::result::Result<CallToolResult, CallToolError> { |
| 179 | + // Attempt to convert request parameters into GreetingTools enum |
| 180 | + let tool_params: GreetingTools = |
| 181 | + GreetingTools::try_from(request.params).map_err(CallToolError::new)?; |
| 182 | + |
| 183 | + // Match the tool variant and execute its corresponding logic |
| 184 | + match tool_params { |
| 185 | + GreetingTools::SayHelloTool(say_hello_tool) => say_hello_tool.call_tool(), |
| 186 | + GreetingTools::SayGoodbyeTool(say_goodbye_tool) => say_goodbye_tool.call_tool(), |
| 187 | + } |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +``` |
| 192 | + |
| 193 | +## Step 6: Combine all components and create the Server! |
| 194 | + |
| 195 | +Now we have all the components necessary for our MCP Server , we need to update our `main()` function to setup a MCP Server using our handler and fire it up! |
| 196 | + |
| 197 | +To do that , we need to |
| 198 | + |
| 199 | +- Define our MCP Server capabilities (which includes tools only in this example) |
| 200 | +- Select a Transport (for now the only option is `std`) |
| 201 | +- Create and start the server by passing server capabilities, transport and handle |
| 202 | + |
| 203 | +Here is how final `main.rs` file looks like. |
| 204 | + |
| 205 | +```rust |
| 206 | +// src/main.rs |
| 207 | +mod handler; |
| 208 | +mod tools; |
| 209 | +use handler::MyServerHandler; |
| 210 | +use rust_mcp_schema::{ |
| 211 | + Implementation, InitializeResult, ServerCapabilities, ServerCapabilitiesTools, |
| 212 | + LATEST_PROTOCOL_VERSION, |
| 213 | +}; |
| 214 | + |
| 215 | +use rust_mcp_sdk::{ |
| 216 | + error::SdkResult, |
| 217 | + mcp_server::{server_runtime, ServerRuntime}, |
| 218 | + MCPServer, StdioTransport, TransportOptions, |
| 219 | +}; |
| 220 | + |
| 221 | +#[tokio::main] |
| 222 | +async fn main() -> SdkResult<()> { |
| 223 | + //Define server details and capabilities |
| 224 | + let server_details = InitializeResult { |
| 225 | + // server name and version |
| 226 | + server_info: Implementation { |
| 227 | + name: "Hello World MCP Server".to_string(), |
| 228 | + version: "0.1.0".to_string(), |
| 229 | + }, |
| 230 | + capabilities: ServerCapabilities { |
| 231 | + // indicates that server support mcp tools |
| 232 | + tools: Some(ServerCapabilitiesTools { list_changed: None }), |
| 233 | + ..Default::default() // Using default values for other fields |
| 234 | + }, |
| 235 | + meta: None, |
| 236 | + instructions: None, |
| 237 | + protocol_version: LATEST_PROTOCOL_VERSION.to_string(), |
| 238 | + }; |
| 239 | + |
| 240 | + // create a std transport with default options |
| 241 | + let transport = StdioTransport::new(TransportOptions::default())?; |
| 242 | + |
| 243 | + //instantiate our custom handler for handling MCP messages |
| 244 | + let handler = MyServerHandler {}; |
| 245 | + |
| 246 | + //create the MCP server |
| 247 | + let server: ServerRuntime = server_runtime::create_server(server_details, transport, handler); |
| 248 | + |
| 249 | + // Start the server |
| 250 | + server.start().await |
| 251 | +} |
| 252 | +``` |
| 253 | + |
| 254 | +## Lets test it out! |
| 255 | + |
| 256 | +To test our server, we first need to compile it by executing: |
| 257 | + |
| 258 | +```sh |
| 259 | +cargo build --release |
| 260 | +``` |
| 261 | + |
| 262 | +Generated binary will be available at `target/release/` folder |
| 263 | + |
| 264 | +> Binary name matched your project name, `hello-world-mcp-server` or `hello-world-mcp-server.exe` on windows |
| 265 | +
|
| 266 | +Now we can use the binary like any other MCP Server in your desired environment. For testing purpose, we use [@modelcontextprotocol/inspector](https://www.npmjs.com/package/@modelcontextprotocol/inspector) |
| 267 | + |
| 268 | +1- launch the mcp-inspector: |
| 269 | + |
| 270 | +```sh |
| 271 | +npx @modelcontextprotocol/inspector |
| 272 | +``` |
| 273 | + |
| 274 | +> you will get a message like: `MCP Inspector is up and running at http://localhost:5173` 🚀 |
| 275 | +
|
| 276 | +Open that address in a web browser: |
| 277 | + |
| 278 | +2- Select STDIO as the transport, enter your binary path in the Command section, and click the Connect button. You should see a message confirming that your server started successfully. |
| 279 | + |
| 280 | + |
| 281 | + |
| 282 | +3- Click **List Tools** button, to retrieve list of tools that your server provides: |
| 283 | + |
| 284 | + |
| 285 | + |
| 286 | +4- Select one the tools, and run it py passing a `name` argument and pressing the **Run Tool** button: |
| 287 | + |
| 288 | + |
0 commit comments