By Rustify Team, updated march 2026
TL;DR: The Model Context Protocol (MCP) is Anthropic's open standard for connecting AI models to external tools, databases, and APIs. Rust is the ideal runtime for MCP servers: small binaries, zero startup time, no GC pauses, and native async I/O.
- MCP lets AI assistants call tools, read resources, and use prompts without custom integrations per model
- Why Rust: < 5ms cold start vs 300–800ms for Python; 5–15 MB binary vs 50–200 MB Python environment
- Crates:
rmcp(official SDK),axumfor HTTP transport,tokiofor async- Two transports: stdio (for local tools/CLI) and HTTP+SSE (for remote/cloud deployment)
- Use cases: database query tools, file system access, API aggregators, code execution sandboxes
This article is for backend engineers and AI developers evaluating Rust as their implementation language for production MCP servers. Whether you're building internal tooling for a team's AI assistant or shipping a public MCP server on cloud infrastructure, this guide covers the full path: from project setup and tool registration to HTTP deployment and production architecture.
What Is the Model Context Protocol (MCP)?
MCP is an open standard published by Anthropic in November 2024 and announced on the Anthropic blog, defining how AI models communicate with external tools, data sources, and services. It replaces ad-hoc function-calling implementations with a unified protocol.
Before MCP, every AI integration was bespoke. If you wanted Claude to query your database, you'd implement a custom function-calling wrapper. If you then wanted GPT-4 to do the same thing, you'd reimplement it. If you added a new AI model, you'd repeat the work. Every integration was a one-off.
MCP changes this with a client/server architecture:
AI Model (Claude, GPT-4, etc.)
↕ MCP Protocol
MCP Client (Claude Desktop, IDE plugin, custom app)
↕ MCP Protocol
MCP Server (your Rust server)
↕ Native APIs
Your tools, databases, APIs, file systemsAccording to the MCP specification, an MCP server exposes three primitives:
- Tools: functions the AI can call (like "query_database", "search_files")
- Resources: data the AI can read (like "current_file_contents", "database_schema")
- Prompts: reusable prompt templates with parameters
Once you build an MCP server, any MCP-compatible AI client can use it; Claude Desktop, Cursor, VS Code extensions, custom agents. Write once, connect anywhere. The full list of supported primitives and transport options is documented in the MCP protocol reference at modelcontextprotocol.io.
Why Is Rust the Best Language for Building MCP Servers?
Rust MCP servers start in under 5ms, produce single-file binaries under 15MB, handle thousands of concurrent tool calls without GC pauses, and compile to any platform, making them ideal for both local tooling and cloud deployment. Rust engineers building MCP servers at AI companies like Anthropic, OpenAI, and Mistral earn $180K–$250K in the US, reflecting strong demand for engineers who can build reliable, low-latency AI infrastructure.
| Metric | Rust | Python | Node.js | Go |
|---|---|---|---|---|
| Cold start time | < 5ms | 300–800ms | 100–300ms | < 20ms |
| Binary size | 5–15 MB | 50–200 MB (env) | N/A (needs runtime) | 10–25 MB |
| Memory per idle server | 2–8 MB | 30–80 MB | 40–100 MB | 10–25 MB |
| GC pauses | None | None (CPython) | Occasional (V8) | Occasional |
| Cross-compilation | Excellent | Poor | Poor | Good |
For local MCP servers (running on a developer's machine alongside Claude Desktop), cold start time matters; users notice a 500ms delay when the AI calls a tool. For remote MCP servers handling many concurrent requests, memory footprint and absence of GC pauses matter.
Bottom line: Rust is the pragmatic choice for production MCP servers; it excels not just on benchmarks, but because compile-time schema validation, single-binary deployment, and sub-5ms cold starts eliminate entire categories of operational problems that Python and Node.js servers hit in production.
Rust vs Python vs Go for Building MCP Servers
| Language | Startup time | Memory (idle) | Type safety | Async support | Production readiness |
|---|---|---|---|---|---|
| Rust | < 5ms | 2–8 MB | Compile-time, exhaustive | Native (tokio) | Excellent; no runtime, single binary |
| Python | 300–800ms | 30–80 MB | Runtime only (mypy optional) | Good (asyncio) | Moderate; requires Python env, slow cold starts |
| Go | < 20ms | 10–25 MB | Compile-time (limited) | Built-in goroutines | Good; single binary, but GC pauses possible |
How Do You Set Up an MCP Server Project in Rust?
Add the rmcp crate with the server feature; it provides the protocol implementation, transport handling, and macros for declaring tools.
# Cargo.toml
[package]
name = "my-mcp-server"
version = "0.1.0"
edition = "2021"
[dependencies]
rmcp = { version = "0.1", features = ["server", "transport-io", "transport-sse-server"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
tracing = "0.1"
tracing-subscriber = "0.3"How Do You Build a Simple MCP Server With Tools?
Define your tools as Rust functions, register them with the #[tool] macro, and serve them over stdio (for local use) or HTTP+SSE (for remote use).
Here's a complete minimal MCP server that exposes two tools; a calculator and a greeting:
use rmcp::{
ServerHandler, ServiceExt,
model::{ServerCapabilities, ServerInfo},
tool,
};
use std::sync::Arc;
// The server state: can hold database connections, API clients, etc.
#[derive(Clone)]
struct MyServer {
// Add shared state here: Arc<Pool<Postgres>>, Arc<reqwest::Client>, etc.
}
// Implement the handler trait
#[rmcp::server]
impl ServerHandler for MyServer {
fn get_info(&self) -> ServerInfo {
ServerInfo {
name: "my-mcp-server".to_string(),
version: "1.0.0".to_string(),
instructions: Some("A sample MCP server with calculator and greeting tools.".to_string()),
capabilities: ServerCapabilities::builder()
.enable_tools()
.build(),
}
}
// Define a tool with the #[tool] macro
#[tool(description = "Add two numbers together")]
async fn add(
&self,
#[tool(description = "First number")] a: f64,
#[tool(description = "Second number")] b: f64,
) -> String {
format!("{}", a + b)
}
#[tool(description = "Generate a personalized greeting")]
async fn greet(
&self,
#[tool(description = "Name to greet")] name: String,
#[tool(description = "Language: en, fr, de, es")] language: Option<String>,
) -> String {
match language.as_deref().unwrap_or("en") {
"fr" => format!("Bonjour, {}!", name),
"de" => format!("Hallo, {}!", name),
"es" => format!("¡Hola, {}!", name),
_ => format!("Hello, {}!", name),
}
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let server = MyServer {};
// Stdio transport: for local tools used by Claude Desktop, Cursor, etc.
let service = server.serve(rmcp::transport::stdio()).await?;
service.waiting().await?;
Ok(())
}How Do You Connect Your MCP Server to Claude Desktop?
Add your server to Claude Desktop's claude_desktop_config.json; Claude will launch it as a subprocess and communicate via stdio.
// ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
{
"mcpServers": {
"my-server": {
"command": "/path/to/my-mcp-server",
"args": [],
"env": {
"DATABASE_URL": "postgres://localhost/mydb",
"RUST_LOG": "info"
}
}
}
}After adding this and restarting Claude Desktop, Claude will automatically discover and offer your tools. When you ask Claude to "add 3 and 7", it will call your add tool and include the result in its response.
For development, use cargo build --release and point to ./target/release/my-mcp-server. The binary is self-contained; no runtime to install.
3 spots open this month → Check if you are eligible.
We help experienced developers transition into Rust roles at €80K–€150K+ in Europe or $130K–$200K+ in the US.
How Do You Build a Real-World MCP Server: Database Query Tool?
A database query MCP server is one of the most valuable tools you can build; it lets AI models inspect your schema, run read-only queries, and reason about your data.
use rmcp::{ServerHandler, tool};
use sqlx::{PgPool, Row};
use std::sync::Arc;
#[derive(Clone)]
struct DatabaseServer {
pool: Arc<PgPool>,
}
#[rmcp::server]
impl ServerHandler for DatabaseServer {
fn get_info(&self) -> rmcp::model::ServerInfo {
rmcp::model::ServerInfo {
name: "database-mcp".to_string(),
version: "1.0.0".to_string(),
instructions: Some("Query a PostgreSQL database. Read-only access only.".to_string()),
capabilities: rmcp::model::ServerCapabilities::builder()
.enable_tools()
.enable_resources()
.build(),
}
}
#[tool(description = "List all tables in the database with their column names")]
async fn list_tables(&self) -> anyhow::Result<String> {
let rows = sqlx::query(
"SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position"
)
.fetch_all(self.pool.as_ref())
.await?;
let mut output = String::new();
let mut current_table = String::new();
for row in rows {
let table: &str = row.get("table_name");
let column: &str = row.get("column_name");
let dtype: &str = row.get("data_type");
if table != current_table {
output.push_str(&format!("\nTable: {}\n", table));
current_table = table.to_string();
}
output.push_str(&format!(" - {} ({})\n", column, dtype));
}
Ok(output)
}
#[tool(description = "Run a read-only SQL SELECT query and return results as JSON")]
async fn query(
&self,
#[tool(description = "SQL SELECT query to execute: no mutations allowed")] sql: String,
) -> anyhow::Result<String> {
// Safety: only allow SELECT statements
let trimmed = sql.trim().to_uppercase();
if !trimmed.starts_with("SELECT") {
return Err(anyhow::anyhow!("Only SELECT queries are permitted"));
}
let rows = sqlx::query(&sql)
.fetch_all(self.pool.as_ref())
.await?;
// Convert rows to JSON-like output
let results: Vec<String> = rows.iter().map(|row| {
format!("{:?}", row) // simplified: use a proper JSON serializer in production
}).collect();
Ok(format!("[{}]", results.join(", ")))
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let pool = PgPool::connect(&std::env::var("DATABASE_URL")?).await?;
let server = DatabaseServer { pool: Arc::new(pool) };
let service = server.serve(rmcp::transport::stdio()).await?;
service.waiting().await?;
Ok(())
}How Do You Deploy an MCP Server for Remote Access Over HTTP?
For shared team tools or cloud-deployed MCP servers, use the HTTP+SSE transport; it allows multiple clients to connect simultaneously and supports long-lived streaming connections.
use rmcp::{ServerHandler, transport::SseServerTransport};
use axum::{Router, routing::get};
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let server = MyServer {};
// SSE transport for HTTP deployment
let (sse_handler, router) = SseServerTransport::new_axum(
move || server.clone().serve_dyn()
);
let app = Router::new()
.route("/sse", get(sse_handler))
.merge(router);
let addr: SocketAddr = "0.0.0.0:3000".parse()?;
println!("MCP server listening on {}", addr);
axum::serve(
tokio::net::TcpListener::bind(addr).await?,
app
).await?;
Ok(())
}With this setup, any MCP client can connect to http://your-server:3000/sse. The SSE transport keeps connections alive for streaming responses; it's ideal for tools that return progressive output.
What Are the Most Common Mistakes When Building MCP Servers in Rust?
Most production MCP server failures come from four root causes: misconfigured async runtimes, silent error swallowing across transport boundaries, missing reconnection logic, and unvalidated input schemas. All of these are preventable with the right patterns.
-
Wrong async runtime configuration: Using
#[tokio::main]with default settings and then spawning blocking database calls on the async executor. Usetokio::task::spawn_blockingfor CPU-heavy or blocking I/O work, and configure the runtime withworker_threadstuned to your workload. A misconfigured runtime causes the MCP server to stall mid-request, which the AI client interprets as a timeout. -
Improper error propagation across transport boundaries: Returning a Rust
panic!or an unwrappedNoneinside a tool handler crashes the entire server process. Instead, always returnResult<String, anyhow::Error>and letrmcpserialize the error to the client. Panics in async tasks are silently dropped by Tokio unless you install a panic hook. -
Skipping reconnection logic for HTTP+SSE transport: Remote MCP servers lose connections under network partitions. Without automatic reconnection on the client side (and graceful connection teardown on the server), sessions go stale. Use
tower's retry middleware or implement a reconnect loop in your transport layer. -
Skipping schema validation on tool inputs: The
#[tool]macro generates JSON Schema from Rust types, but accepting a rawStringparameter for structured data (SQL, JSON payloads) bypasses validation entirely. Parse and validate inputs explicitly: reject malformed input with a descriptive error rather than letting it propagate to your database or external API. -
Ignoring backpressure on concurrent tool calls: MCP clients can issue concurrent tool calls. Without bounded concurrency (e.g.,
tokio::sync::Semaphore), a burst of AI-generated calls can exhaust your database connection pool or rate-limit your upstream API.
What nobody tells you: Most MCP tutorials use Python; but Rust's type system makes schema validation nearly free. When you annotate a tool parameter with a Rust type (e.g.
u32,Option<String>, a customenum),rmcpgenerates the full JSON Schema automatically at compile time. Python and Node.js SDKs do this at runtime with reflection. In practice, Rust MCP servers reject malformed AI-generated inputs before a single line of your business logic runs, with zero performance cost and no extra validation code to write.
What Does a Production MCP Server Look Like in Rust?
A production Rust MCP server has five distinct layers: typed input/output schemas, a validated tool registration pattern, a chosen transport (stdio or HTTP+SSE), structured error handling, and observable instrumentation. It's not just a single main.rs with inline handlers.
A minimal but production-grade architecture looks like this:
src/
main.rs : transport selection, server startup
server.rs : ServerHandler impl, tool registration
tools/
mod.rs
query.rs : typed input/output structs + handler logic
schema.rs : schema introspection tool
error.rs : domain error types → MCP error conversion
config.rs : env-driven config (DB URL, rate limits, auth)Each tool module defines its own input struct with serde::Deserialize and schemars::JsonSchema, ensuring the generated JSON Schema is accurate and the AI model receives precise parameter descriptions:
#[derive(Debug, Deserialize, JsonSchema)]
pub struct QueryInput {
/// SQL SELECT statement: mutations are rejected
pub sql: String,
/// Maximum rows to return (default: 100)
pub limit: Option<u32>,
}
#[tool(description = "Run a read-only SQL query")]
async fn query(&self, input: QueryInput) -> Result<String, McpError> {
validate_select_only(&input.sql)?;
let rows = self.pool.fetch_limited(&input.sql, input.limit.unwrap_or(100)).await?;
Ok(serialize_rows(rows))
}Transport is selected at startup via an environment variable: MCP_TRANSPORT=stdio for local Claude Desktop use, MCP_TRANSPORT=http for shared team infrastructure. Structured tracing spans wrap every tool call, giving you OpenTelemetry-compatible traces in production.
Who Is Building MCP Servers in Rust in 2026?
Rust is the dominant language for production MCP server infrastructure at AI-forward companies because it provides the binary portability, memory efficiency, and concurrency model that Python and Node.js cannot match at scale.
-
Anthropic: the MCP specification authors use Rust internally for performance-critical infrastructure. Their open-source reference servers include Rust examples, and the
rmcpSDK is maintained with first-party support. -
Cloudflare: Cloudflare Workers AI runs inference at the edge, and their Rust-native Workers runtime makes it a natural fit for MCP servers deployed as edge functions. Cloudflare engineers have published Rust MCP adapters for their AI Gateway.
-
Hugging Face: uses Rust in their
candleinference framework and has contributed MCP-compatible tool adapters for models hosted on the Hub. Their TGI (Text Generation Inference) server, written in Rust and Python, exposes tool-use APIs that map cleanly to MCP primitives. -
Modal: the serverless GPU platform uses Rust for its hot-path infrastructure and supports deploying MCP servers as Modal functions, with cold starts measured in milliseconds rather than seconds.
-
Independent open-source projects:
mcp-server-sqlite(Rust, 2k+ GitHub stars),mcp-filesystem(Rust rewrite of the reference Node.js server), andmcp-rs(a community SDK alternative tormcp) are all actively maintained as of early 2026.
Rust engineers with MCP server experience are listed explicitly in job postings at these companies, with compensation ranging from $180K to $250K in the US, reflecting how specialized and in-demand this skill set has become.
Frequently Asked Questions
No; MCP is an open standard and has been adopted by multiple AI applications. Cursor (the AI code editor), Continue (VS Code plugin), and a growing number of custom AI applications support MCP. Anthropic published the specification openly and is encouraging adoption across the ecosystem.
Function calling (OpenAI's API term) or tool use (Anthropic's term) are model-side concepts; they define how the model requests an action. MCP is a transport protocol; it defines how the tool server communicates with any client. MCP wraps tool use in a standardized server/client protocol so the same server works with any AI application.
For public or shared servers, add an authentication middleware to your Axum router. For local stdio servers, authentication is unnecessary; the server runs as the local user's process. As documented in the MCP protocol reference, the specification supports OAuth 2.0 for remote servers; the rmcp crate's HTTP transport can be combined with any Axum middleware for auth. The GitHub MCP SDK repo includes reference implementations showing auth patterns across multiple languages.
Return anyhow::Result<String> (or any error type) from your tool functions; the rmcp crate serializes the error into an MCP error response automatically. The AI model receives the error message and can incorporate it into its reasoning (for example, "the query failed because the table doesn't exist; let me try listing tables first").
Yes; resources are static or dynamic data the AI can read (like file contents, database schemas, configuration). As documented in the MCP specification at modelcontextprotocol.io, resources are a first-class primitive alongside tools and prompts. The rmcp crate supports resource handlers via the enable_resources() capability flag and a read_resource handler method. Resources are useful for context that the model should always have access to, rather than calling on demand.
Use stdio transport when your MCP server runs locally on a developer's machine alongside Claude Desktop or Cursor; it's the simplest setup, requires no networking configuration, and the server process is managed by the AI client. Use HTTP+SSE transport when you need multiple clients to connect simultaneously, or when you're deploying a shared team tool to cloud infrastructure. The transport selection at startup via an environment variable (MCP_TRANSPORT=stdio or MCP_TRANSPORT=http) keeps the same server binary usable in both contexts.
Without explicit concurrency limits, a burst of AI-generated tool calls can exhaust your database connection pool or hit upstream API rate limits. Use tokio::sync::Semaphore to cap concurrent tool executions; for example, a semaphore with 10 permits means at most 10 tool calls run simultaneously regardless of how many the model issues. Pair this with tower's retry and timeout middleware on the HTTP transport for remote deployments. This is the most common production scaling issue that Rust's async model makes straightforward to address without extra infrastructure.
Rust engineers with MCP server experience are explicitly listed in job postings at companies like Anthropic, Cloudflare, Hugging Face, and Modal, with compensation ranging from $180K to $250K in the US as of 2026. The premium reflects genuine scarcity: the intersection of Rust systems expertise and MCP protocol knowledge is small. Engineers who can build, deploy, and operate production MCP servers, including the error handling, schema validation, and observability patterns described in this article, represent a skill set that commands the top end of that range.
Want to Build MCP Servers in Rust Professionally?
The fastest path is structured training. Rustify's 12-week bootcamp takes you from Rust basics to building production-ready MCP servers; with live coaching, real projects, and job placement support. Rust engineers with MCP skills are commanding $180K–$250K at top AI companies.
Explore the Rustify Bootcamp →
Related Glossary Terms
- Async/Await: MCP server handlers are fully async Rust functions
- Tokio: The runtime managing concurrent MCP tool invocations
- Channel: Used for streaming tool responses and passing results between tasks
- Future: Each MCP tool handler is a future driven by the Tokio executor
- Tracing: Structured logging for observability in MCP server implementations
- Serde: Serializes and deserializes MCP JSON protocol messages
Keep Reading
- Rust Memory Safety: Why NSA and CISA Recommend Rust in 2026
- Rust in the Linux Kernel: What It Means for Systems Developers
- The Best Way to Learn Rust in 2026 (For Experienced Developers)

