Skip to main content
The Rust sample uses the rmcp crate, which provides procedural macros (#[tool], #[tool_router], #[tool_handler]) to register MCP tools with minimal boilerplate. The server runs asynchronously using Tokio with a stdio transport.

Basic calculator server

Located at 03-GettingStarted/samples/rust.
1

Clone and navigate

git clone https://github.com/microsoft/mcp-for-beginners.git
cd mcp-for-beginners/03-GettingStarted/samples/rust
2

Build

cargo build
3

Run

cargo run

Cargo.toml

Cargo.toml
[package]
name = "calculator"
version = "1.0.0"
edition = "2024"

[dependencies]
rmcp = { version = "0.5.0", features = ["server", "transport-io"] }
serde = "1.0.219"
tokio = { version = "1.46.0", features = ["rt-multi-thread"] }

Server code

src/main.rs
use rmcp::{
    ServerHandler, ServiceExt,
    handler::server::{router::tool::ToolRouter, tool::Parameters},
    model::{ServerCapabilities, ServerInfo},
    schemars, tool, tool_handler, tool_router,
    transport::stdio,
};
use std::error::Error;

// Shared input schema for binary operations
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CalculatorRequest {
    pub a: f64,
    pub b: f64,
}

// The Calculator struct holds a tool router generated by the macro
#[derive(Debug, Clone)]
pub struct Calculator {
    tool_router: ToolRouter<Self>,
}

// #[tool_router] generates the routing glue for all #[tool] methods
#[tool_router]
impl Calculator {
    pub fn new() -> Self {
        Self {
            tool_router: Self::tool_router(),
        }
    }

    #[tool(description = "Adds a and b")]
    async fn add(
        &self,
        Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
    ) -> String {
        (a + b).to_string()
    }

    #[tool(description = "Subtracts b from a")]
    async fn subtract(
        &self,
        Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
    ) -> String {
        (a - b).to_string()
    }

    #[tool(description = "Multiply a with b")]
    async fn multiply(
        &self,
        Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
    ) -> String {
        (a * b).to_string()
    }

    #[tool(description = "Divides a by b")]
    async fn divide(
        &self,
        Parameters(CalculatorRequest { a, b }): Parameters<CalculatorRequest>,
    ) -> String {
        if b == 0.0 {
            "Error: Division by zero".to_string()
        } else {
            (a / b).to_string()
        }
    }
}

// #[tool_handler] wires the tool router into the MCP ServerHandler trait
#[tool_handler]
impl ServerHandler for Calculator {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            instructions: Some("A simple calculator tool".into()),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            ..Default::default()
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    // Serve over stdio and wait until the client disconnects
    let service = Calculator::new().serve(stdio()).await?;
    service.waiting().await?;
    Ok(())
}

How the macros work

MacroPurpose
#[tool_router]Applied to the impl Calculator block — generates a tool_router() method that builds a dispatch table from all #[tool] methods
#[tool(description = "...")]Marks an async method as an MCP tool and attaches a description used in the tool schema
#[tool_handler]Applied to the impl ServerHandler for Calculator block — wires ToolRouter into the MCP protocol handler
Parameters<T>Deserializes and validates the incoming JSON arguments into a typed struct

Input schema

All four tools share the same CalculatorRequest struct:
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct CalculatorRequest {
    pub a: f64,
    pub b: f64,
}
schemars::JsonSchema is derived automatically — rmcp uses this to generate the JSON Schema that clients see when they call tools/list.

Available tools

ToolInputReturns
add{ a, b }"a + b" as a string
subtract{ a, b }"a - b" as a string
multiply{ a, b }"a * b" as a string
divide{ a, b }"a / b" or "Error: Division by zero"

Testing with MCP Inspector

1

Start the server

cargo run
2

Launch the Inspector in a new terminal

npx @modelcontextprotocol/inspector
3

Connect

Configure the Inspector to launch cargo run as the server process with stdio transport, then click List Tools and call any operation.

Build docs developers (and LLMs) love