High-Performance MCP Server Implemented with Rust: Asynchronous Messaging Using ZeroMQ
Recently, while working on the ZeroClaw project, we identified a need to overcome the performance limitations of MCP (Model Context Protocol) servers. Traditional HTTP-based communication struggles to move beyond a single request-response pattern, and real-time communication between numerous LLM agents is prone to bottlenecks. In this post, we will cover the process of building an ultra-lightweight, ultra-fast MCP server by leveraging Rust’s powerful asynchronous processing capabilities and ZeroMQ (ØMQ).
Why ZeroMQ? (Comparison with TCP Socket Limitations)
In the previous blog-api-server architecture, we defined and used protocols directly using standard TCP streams. However, in a multi-agent environment, the following issues arise:
- Connection Management Complexity: As the number of agents grows into the dozens, managing the
Acceptloop and socket states requiresunsafecode or complex state machines. - Message Enveloping: TCP is a byte stream. We must implement Length-Prefixing directly to demarcate message boundaries, which is a common source of bugs.
ZeroMQ abstracts away the complexity of this lower socket layer while providing a faster “User Space” transport layer than TCP. Notably, using the ipc (Inter-Process Communication) protocol can completely eliminate network stack overhead for localhost communication.
Architecture Design: ZeroMQ PUB/SUB Pattern
For this implementation, we will use the Publish/Subscribe (PUB/SUB) pattern for loose coupling between agents. When one agent changes its state (Publishes), a message is immediately broadcast to other agents subscribing to that topic.
Key Dependencies (Cargo.toml)
In the Rust ecosystem, ZeroMQ is available through the zmq crate. While tokio-zmq can be used for integration with asynchronous runtimes, for pure performance, leveraging ZeroMQ’s poll functionality is often more stable.
[dependencies]
zmq = "0.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["full"] }
Implementation: High-Performance MCP Router
The code below is an example implementation of an MCP Router that acts as a simple message broker. It serves as a central hub for multiple LLM agents (teams) to communicate with each other.
1. Define Message Protocol
First, let’s define the format of messages exchanged between agents. We ensure compatibility by using JSON serialization.
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct McpMessage {
pub source_id: String,
pub target_topic: String,
pub payload: String,
pub timestamp: i64,
}
2. Asynchronous ZeroMQ Context and Socket Setup
ZeroMQ’s Context is thread-safe, so it’s common practice to create and share a single instance globally throughout the application.
use std::time::SystemTime;
use zmq::{Context, Socket, SocketType};
struct McpRouter {
context: Context,
frontend: Socket, // Agents connect here
// Can add sockets for other patterns if needed
}
impl McpRouter {
fn new() -> Result<Self, zmq::Error> {
let context = Context::new();
let frontend = context.socket(SocketType::SUB)?;
// Set to receive all messages (no filtering)
frontend.set_subscribe(b"")?;
Ok(McpRouter { context, frontend })
}
fn start(&self, endpoint: &str) -> Result<(), zmq::Error> {
self.frontend.bind(endpoint)?;
println!("[ZeroClaw Router] Listening on {}", endpoint);
let mut msg = zmq::Message::new();
loop {
// Can use poll for non-blocking receive
// Here, a simple blocking receive example
match self.frontend.recv(&mut msg) {
Ok(_) => {
let data = msg.as_str().unwrap();
// Message parsing and routing logic
if let Ok(mcp_msg) = serde_json::from_str::<McpMessage>(data) {
self.route_message(mcp_msg);
}
}
Err(e) => {
eprintln!("Receive Error: {}", e);
}
}
}
}
fn route_message(&self, msg: McpMessage) {
// Implement actual routing logic
println!("Routing from {} on topic {}", msg.source_id, msg.target_topic);
// e.g., save to database, forward to another socket, etc.
}
}
3. Agent Publisher Implementation
Now, let’s write the code for individual agents to send messages. When using tokio, you need to execute ZeroMQ’s blocking functions in a separate thread to avoid blocking the asynchronous runtime.
use std::thread;
fn spawn_publisher_agent(id: &str, endpoint: &str) {
let context = Context::new();
let sender = context.socket(SocketType::PUB).expect("Failed to create PUB socket");
sender.connect(endpoint).expect("Failed to connect to Router");
// Run in a separate thread to isolate from the Tokio runtime
thread::spawn(move || {
let counter = 0..100;
for i in counter {
let msg = McpMessage {
source_id: id.to_string(),
target_topic: "general".to_string(),
payload: format!("Message #{} from agent {}", i, id),
timestamp: SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs() as i64,
};
let json_str = serde_json::to_string(&msg).unwrap();
// ZeroMQ automatically retries or queues sending if it fails (set High-water mark if needed)
sender.send(json_str.as_bytes(), 0).expect("Failed to send");
thread::sleep(std::time::Duration::from_millis(100));
}
});
}
Performance Optimization and IPC Utilization
The true power of a Rust ZeroMQ server is unleashed when using the IPC (Inter-Process Communication) transport. While TCP involves packets traversing the network stack and looping back, IPC uses Unix Domain Sockets or Windows Named Pipes for communication at a level close to memory copying.
To use the ipc protocol, change the endpoint as follows:
// Use IPC instead of TCP
let endpoint = "ipc:///tmp/mcp_router.ipc";
router.start(endpoint);
Just as modern languages like Bun and Zig gain attention for their system-level optimizations, the combination of Rust and ZeroMQ is one of the most powerful tools for building the “system bus” for LLM applications.
Conclusion
In this post, as part of the ZeroClaw project, we explored how to build a high-performance MCP server using Rust and ZeroMQ. Beyond simple HTTP requests, this architecture offers significant advantages in scalability and performance for environments requiring real-time messaging between agents.
In the next post, we will discuss the Circuit Breaker pattern and error handling strategies to add reliability to this messaging system.
References