Minimal mcp server using rmcp 0.3
rmcp was updated July 16 2025 – this article looks as how to use the latest custom procedural macro attributes and build a minimalist example:
#[tool_router]
#[tool_handler]
Note : They replace #[tool(tool_box)] from earlier versions

Let’s build using the official Rust SDK for the Model Context Protocol
[package]
name = "mcp-calculator-2"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.98"
rmcp = { version = "0.3.0", features = ["server", "transport-io", "transport-sse-server", "transport-streamable-http-server"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tokio = { version = "1.46.1", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
/// main.rs
/// ProtocolVersion::V_2025_03_26
/// rmcp = { version = "0.3.0", features = ["server", "transport-io", "transport-sse-server", "transport-streamable-http-server"] }
use rmcp::{
ServerHandler, ServiceExt,
handler::server::router::tool::ToolRouter,
model::{ProtocolVersion, ServerCapabilities, ServerInfo}, tool, tool_handler, tool_router,
transport::stdio,
};
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let service = HelloWorld::new()
.serve(stdio())
.await
.inspect_err(|e| eprintln!("{e}"))?;
service.waiting().await?;
Ok(())
}
#[derive(Debug, Clone)]
pub struct HelloWorld {
counter: Arc<Mutex<i32>>,
tool_router: ToolRouter<Self>,
}
/// not #[tool(tool_box)] !! as previously used
#[tool_router]
impl HelloWorld {
pub fn new() -> Self {
Self {
counter: Arc::new(Mutex::new(0)),
tool_router: Self::tool_router(),
}
}
#[tool(description = "Increment the counter")]
async fn increment(&self) -> String {
let mut counter = self.counter.lock().await;
*counter += 1;
counter.to_string()
}
#[tool(description = "Get the current counter value")]
async fn get_value(&self) -> String {
let counter = self.counter.lock().await;
counter.to_string()
}
#[tool(description = "Say Something")]
fn echo(&self) -> String {
"hello from your first MCP server".to_string()
}
}
/// not #[tool(tool_box)] !! as previously used
#[tool_handler]
impl ServerHandler for HelloWorld {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: ProtocolVersion::V_2025_03_26,
instructions: Some(
"This server provides a counter tool that can increment and get current value"
.to_string(),
),
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
}
}

Why tool_router: ToolRouter<Self>
??
