mercutio
A Rust library for building MCP servers. mercutio handles the protocol (parsing messages, managing the initialization handshake, dispatching tool calls), while you handle the transport. The core is a pure state machine: feed it JSON-RPC messages, and it returns what to send back.
This sans-io design means you can run it over stdio, HTTP, WebSockets, or anything else without fighting the library.
If you'd rather not wire up your own transport, the io-* feature flags provide ready-made integrations.
Side-effect free usage
use mercutio::{McpServer, Output};
mercutio::tool_registry! {
enum MyTools {
GetWeather("get_weather", "Gets weather for a city") {
city: String,
},
}
}
let mut server = McpServer::<MyTools>::builder()
.name("my-server")
.version("1.0.0")
.build();
loop {
let line = read_line_somehow();
let msg = mercutio::parse_line(&line)?;
match server.handle(msg) {
Output::Send(msg) => send(msg.into_inner()),
Output::ToolCall { tool, responder } => {
let result = match tool {
MyTools::GetWeather(input) => {
get_weather(&input.city).map(|w| format!("{}C", w.temp))
}
};
send(responder.respond(result).into_inner());
}
Output::ProtocolError(err) => break,
Output::None => {}
}
}
Feature Flags
If you prefer not to implement the IO harness yourself, mercutio provides a few:
io-stdlib - Synchronous stdin/stdout transport using standard library I/O
io-tokio - Async stdin/stdout transport using Tokio
io-axum - HTTP transport using Axum (MCP Streamable HTTP)
io-stdlib
Runs an MCP server over stdin/stdout with newline-delimited JSON:
use std::convert::Infallible;
use mercutio::{io::stdlib::run_stdio, McpServer};
mercutio::tool_registry! {
enum MyTools {
GetWeather("get_weather", "Gets weather") { city: String },
}
}
let server = McpServer::<MyTools>::builder()
.name("my-server")
.version("1.0.0")
.build();
run_stdio(server, |tool| -> Result<String, Infallible> {
match tool {
MyTools::GetWeather(input) => Ok(format!("Weather in {}: sunny", input.city)),
}
})?;
io-tokio
Async version using Tokio. Implement MutToolHandler for stateful handlers:
use std::convert::Infallible;
use mercutio::{McpServer, ToolOutput, io::tokio::{run_stdio, MutToolHandler}};
mercutio::tool_registry! {
enum MyTools {
GetWeather("get_weather", "Gets weather") { city: String },
}
}
struct Handler;
impl MutToolHandler<MyTools> for Handler {
type Error = Infallible;
async fn handle(&mut self, tool: MyTools) -> Result<ToolOutput, Self::Error> {
match tool {
MyTools::GetWeather(input) => {
Ok(format!("Weather in {}: sunny", input.city).into())
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), mercutio::io::tokio::IoError> {
let server = McpServer::<MyTools>::builder()
.name("my-server")
.version("1.0.0")
.build();
run_stdio(server, Handler).await
}
io-axum
HTTP transport implementing MCP Streamable HTTP with session management:
use std::convert::Infallible;
use mercutio::{McpServer, io::axum::mcp_router};
mercutio::tool_registry! {
enum MyTools {
GetWeather("get_weather", "Gets weather") { city: String },
}
}
let mut builder = McpServer::<MyTools>::builder();
builder.name("my-server").version("1.0.0");
let router = mcp_router(builder, |tool: MyTools| async move {
match tool {
MyTools::GetWeather(input) => {
Ok::<_, Infallible>(format!("Weather in {}: sunny", input.city))
}
}
});
let app = axum::Router::new().nest("/mcp", router);