Building a Lightweight MCP Server with Rust and ZeroClaw: An Efficient Agent Communication Structure
As evident in the recent article “Grit: Rewriting Git in Rust with Agents,” the Rust language is being re-evaluated for its value as a runtime for agent systems. Our team’s ZeroClaw runtime is also under development with high performance and safety as its goals, and with this in mind, we are increasingly focusing on how to efficiently build an MCP (Model Context Protocol) server.
Instead of traditional heavy Python-based servers or complex Node.js architectures, we propose a lightweight MCP server structure that leverages Rust’s concurrency and low-latency capabilities. In this article, we will write a Rust-based MCP server example that can be immediately applied in practice, while capitalizing on the advantages of the ZeroClaw architecture.
1. Combining ZeroClaw Architecture and MCP
The core of the ZeroClaw project lies in its file-based architectural design and the efficient implementation of its multi-agent communication protocol. The most crucial aspects when building an MCP server are reducing the overhead of data serialization/deserialization (Serde) and asynchronous request handling.
By utilizing Rust’s tokio runtime and serde_json, we can handle JSON-based MCP protocols with an overhead in the nanosecond range. This is a critical element, especially for large-scale log processing or monitoring systems (as experienced with [blog-api-server]).
2. Core Structure: Asynchronous Request Routing
We will build the skeleton of an MCP server using only the standard library and a few essential crates, without complex dependencies. This structure follows ZeroClaw’s agent-to-agent communication pattern and ensures safety in a multi-threaded environment, not just a single-threaded one.
2.1. Cargo.toml Configuration
First, add project dependencies.
[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hyper = { version = "0.14", features = ["full"] }
2.2. Defining MCP Message Structure
MCP fundamentally has a structure similar to JSON-RPC. We actively utilize Rust’s type system to ensure data integrity during agent-to-agent communication.
// src/types.rs
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub struct MCPRequest {
pub jsonrpc: String, // Always "2.0"
pub id: Option<String>,
pub method: String,
pub params: Option<serde_json::Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MCPResponse {
pub jsonrpc: String,
pub id: Option<String>,
pub result: Option<serde_json::Value>,
pub error: Option<MCPError>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MCPError {
pub code: i32,
pub message: String,
}
3. Implementing a Lightweight Request Handler
In line with ZeroClaw’s philosophy, we will remove unnecessary abstraction layers and write intuitive handlers. The following code demonstrates the flow of parsing incoming requests, performing appropriate processing based on the method name, and returning a response.
// src/main.rs
use hyper::{Body, Request, Response, Server, StatusCode};
use hyper::service::{make_service_fn, service_fn};
use std::convert::Infallible;
use types::{MCPRequest, MCPResponse};
async fn handle_mcp_request(req: MCPRequest) -> MCPResponse {
match req.method.as_str() {
"tools/list" => {
// Example of returning a list of tools
MCPResponse {
jsonrpc: "2.0".to_string(),
id: req.id,
result: Some(serde_json::json!({
"tools": [
{ "name": "get_weather", "description": "Get current weather" },
{ "name": "calculate", "description": "Perform math operations" }
]
})),
error: None,
}
},
"tools/call" => {
// Actual tool execution logic (calling ZeroClaw Agent, etc.)
// Parameter validation logic goes here.
println!("Agent executing with params: {:?}", req.params);
MCPResponse {
jsonrpc: "2.0".to_string(),
id: req.id,
result: Some(serde_json::json!({"status": "success"})),
error: None,
}
},
_ => {
MCPResponse {
jsonrpc: "2.0".to_string(),
id: req.id,
result: None,
error: Some(types::MCPError {
code: -32601,
message: "Method not found".to_string(),
}),
}
}
}
}
async fn http_handler(req: Request<Body>) -> Result<Response<Body>, Infallible> {
let whole_body = match hyper::body::to_bytes(req.into_body()).await {
Ok(bytes) => bytes,
Err(e) => {
eprintln!("Error reading body: {}", e);
return Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Internal Server Error"))
.unwrap());
}
};
// JSON Parsing
let mcp_req: MCPRequest = match serde_json::from_slice(&whole_body) {
Ok(req) => req,
Err(_) => {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("Invalid JSON"))
.unwrap());
}
};
// MCP Logic Processing
let mcp_res = handle_mcp_request(mcp_req).await;
// Response Conversion
let json_body = match serde_json::to_string(&mcp_res) {
Ok(json) => json,
Err(_) => "{}".to_string(),
};
Ok(Response::new(Body::from(json_body)))
}
#[tokio::main]
async fn main() {
let make_svc = make_service_fn(|_conn| {
async { Ok::<_, Infallible>(service_fn(http_handler)) }
});
let addr = ([127, 0 0, 1], 3000).into();
let server = Server::bind(&addr).serve(make_svc);
println!("MCP Server listening on http://{}", addr);
if let Err(e) = server.await {
eprintln!("Server error: {}", e);
}
}
4. Advanced Optimization: ZeroClaw Integration
The code above is a basic HTTP server. To unleash the true power of ZeroClaw, this server needs to be integrated as a task within the agent runtime.
- Channel-based Communication: Instead of directly handling HTTP requests, send requests through a
tokio::sync::mpscchannel to the internal agent queue. This aligns withZeroClaw’s asynchronous message passing structure. - Stealable Work Queue: Implement a work queue to allow agents to efficiently distribute tasks in a multi-core environment.
5. Conclusion
By writing an MCP server in Rust, you can fully leverage the benefits of high-performance runtimes like ZeroClaw. The key is not just changing the language, but designing an architecture that minimizes overhead in agent-to-agent communication. We encourage you to build upon the code provided and add your project-specific agent logic.
Note: This code focuses on practicality by simplifying error handling and logging, based on the experience gained from improving logging in blog-api-server.