
Why Rust is the Right Language for the Model Context Protocol (MCP)
From Prompt Engineering to Agentic Systems
The role of large language models in enterprise software has changed substantially. What began as an exercise in crafting clever prompts has matured into the engineering of autonomous, multi-step agentic systems — systems that plan, invoke external tools, and reason over structured data at runtime.
At the center of this shift is the Model Context Protocol (MCP), a standardized, open framework that decouples an AI host’s reasoning capabilities from the tools and data sources it depends upon. Rather than hard-coding integrations between a model and each individual service, MCP defines a shared protocol layer — comprising tool descriptions, invocation semantics, JSON-RPC messaging, and transport contracts — so that any compliant host can discover and use any compliant server without bespoke glue code.
The LLM host focuses on orchestration and reasoning. MCP servers expose domain capabilities, including file access, database queries, HTTP calls, and computational tasks. A shared protocol governs how they communicate. Choosing the right language, the right crates, and the right security posture for that protocol layer is what this article is about.
Why Rust?
Choosing an implementation language for MCP components has real consequences. MCP servers sit at the boundary between an AI agent and the systems it manipulates. They process untrusted, LLM-generated inputs, invoke real-world operations, and must remain correct even under adversarial conditions. Rust earns its place here for four practical reasons.
Safety without a garbage collector. Rust’s ownership model eliminates entire classes of memory vulnerability — use-after-free, buffer overflows, data races — at compile time, with no garbage-collector pauses to worry about. For long-running, high-throughput MCP servers, this translates directly into correctness and predictable latency.
Performance that scales. The pmcp crate, which uses hardware-level SIMD optimizations for message parsing, reports a 16x speed improvement and 50x reduction in memory usage compared to an equivalent TypeScript implementation, based on its own published benchmarks. These gains are specific to pmcp's optimizations, but they reflect what becomes possible when the runtime has no GC overhead and message parsing is taken seriously.
Errors are enforced, not optional. Rust’s Result<T, E> type requires every failure to be explicitly handled, forwarded, or deliberately discarded. You cannot accidentally ignore an error. In practice, this means avoiding .unwrap() in production code. Calling .unwrap() on a failed result causes a panic that terminates the in-flight tool invocation and breaks the server's response contract with the host. Using the ? operator instead propagates the error as a well-formed response the LLM can reason about and recover from.
A mature, purpose-built ecosystem. The Rust MCP ecosystem now includes six active crates covering everything from minimalist stdio servers to enterprise-grade HTTP deployments with OAuth 2.1 support. You are not building on experimental ground.
Choosing the Right MCP Crate
There is no single correct crate. The right choice depends on your deployment context, performance requirements, and protocol version needs. The following is a structured comparison.
rmcp — The Official Rust SDK
rmcp is the official Rust SDK for MCP, providing core implementations of protocol primitives and the stdio transport. As the reference implementation, it tracks the canonical specification most closely and is designed for clarity and interoperability.
Best suited for: Standard local servers communicating via stdio, rapid prototyping, and situations where official protocol conformance is the primary requirement.
rust-mcp-sdk — High-Performance Async Toolkit
rust-mcp-sdk is built for servers that need to serve many concurrent clients over HTTP/SSE rather than stdio. It integrates with the Axum web framework and includes DNS rebinding protection, which prevents browser-based clients from being tricked into sending requests to a local server they were not intended to reach.
Best suited for: High-concurrency, network-exposed MCP servers and teams already invested in the Axum ecosystem.
pmcp — Production-Ready with Observability
pmcp is designed for enterprise deployments where operational visibility is as important as raw performance. It bundles SIMD-accelerated message parsing, built-in metrics instrumentation, and quality gates for integration into CI/CD pipelines to enforce performance and reliability thresholds before deployment.
Best suited for: Enterprise deployments where performance SLAs must be measurable and teams running CI/CD pipelines with performance regression testing.
mcpkit — Advanced Protocol Features with Reduced Boilerplate
mcpkit targets teams building complex tool ecosystems who want to minimize repetitive configuration code. Its #[mcp_server] macro unifies tool definition and registration into a single declarative annotation, and it uses typestate validation to catch configuration errors at compile time rather than at runtime.
Notably, mcpkit implements the 2025-11-25 protocol specification (verify the current version at modelcontextprotocol.io), adding support for the following advanced features.
- Tasks: long-running, asynchronous tool invocations with progress reporting.
- Elicitation: structured mechanisms for the server to request additional information from the user mid-invocation.
- OAuth 2.1: standardized authorization flows for servers accessing protected third-party APIs on behalf of users.
Best suited for: Teams building feature-rich, multi-tool servers and deployments requiring the latest protocol capabilities.
async-mcp — Minimalist Async Implementation
async-mcp is a deliberately lightweight crate that implements the core MCP protocol with minimal dependencies. It is appropriate when you need the protocol without the infrastructure overhead that higher-level crates carry.
Best suited for: Simple client/server applications, resource-constrained or embedded environments, and cases where a minimal dependency footprint is a hard requirement.
mcp_protocol_sdk — Comprehensive Specification Compliance
mcp_protocol_sdk complies with the 2025-06-18 specification and introduces capabilities that reflect the evolving multimodal direction of AI systems, specifically the following.
- Audio content: support for audio data as a first-class content type in tool responses.
- Annotations: enhanced metadata attachable to content for richer model context.
- Argument autocompletion: enables MCP clients to provide completion suggestions for tool parameters.
Best suited for: Teams building multimodal tool servers and applications requiring argument completion in their client tooling.
Supporting Utilities
Two additional crates round out the ecosystem.
- mcp-tester: Scenario-based automated testing for MCP servers, covering edge cases and malformed requests.
- rmcp-openapi: Converts existing OpenAPI specifications into MCP tool definitions, providing a practical migration path for organizations that already maintain OpenAPI-described APIs.
Building Your First MCP Tool
The following example builds a File Explorer MCP server using rmcp. It exposes two tools — one that lists directory contents and one that reads a file — and is kept deliberately simple to focus on the MCP-specific constructs.
Cargo.toml
[package]
name = "file-explorer-mcp"
version = "0.1.0"
edition = "2021"
[dependencies]
rmcp = { version = "0.1", features = ["server", "transport-io"] }
serde = { version = "1", features = ["derive"] }
schemars = "0.8"
anyhow = "1"
tokio = { version = "1", features = ["full"] }
Server Implementation
use rmcp::{tool, tool_box, ServerHandler, transport::StdioTransport};
// ServerHandler is the trait that #[tool_box] auto-implements for FileServer.
// The import is required because the proc macro generates the implementation
// but the compiler still needs the trait in scope.
use serde::Deserialize;
use schemars::JsonSchema;
use anyhow::Result;
#[derive(Clone)]
struct FileServer;
#[derive(Deserialize, JsonSchema)]
struct PathRequest {
/// The absolute path to the target directory or file.
path: String,
}
#[tool_box]
impl FileServer {
#[tool(description = "List the contents of a directory, returning file and folder names.")]
fn list_dir(&self, #[tool(aggr)] req: PathRequest) -> Result<Vec<String>> {
Ok(std::fs::read_dir(&req.path)?
.filter_map(Result::ok)
.map(|entry| entry.file_name().to_string_lossy().to_string())
.collect())
}
#[tool(description = "Read the UTF-8 text content of a file at the given path.")]
fn read_file(&self, #[tool(aggr)] req: PathRequest) -> Result<String> {
Ok(std::fs::read_to_string(&req.path)?)
}
}
#[tokio::main]
async fn main() -> Result<()> {
let transport = StdioTransport::new();
let server = FileServer;
server.serve(transport).await?;
Ok(())
}
What Each Annotation Does
#[tool_box] — Registers all annotated methods in the impl block as discoverable MCP tools and auto-implements ServerHandler for the struct. The runtime uses this to respond to tools/list requests from the AI host.
#[tool(description = "...")] — Attaches the human-readable description passed to the LLM. This string is part of your API contract. A clear, accurate description is what allows the model to invoke the right tool at the right time.
#[derive(Deserialize, JsonSchema)] — serde::Deserialize parses incoming JSON-RPC parameters into the request struct. schemars::JsonSchema generates the JSON Schema the SDK uses for parameter validation and input-shape communication to the host.
#[tool(aggr)] — Maps the entire JSON parameters object to the annotated struct, rather than individual named parameters. This is the idiomatic pattern for structured tool inputs.
? operator — Propagates any failure from read_dir or read_to_string as a structured MCP error response. The host receives a well-formed error it can surface to the model, which can then attempt recovery or inform the user.
Connecting to Claude Desktop
Claude Desktop supports local MCP servers via the stdio transport, spawning the server as a child process and communicating over standard input and output. The configuration lives in the Claude Desktop settings file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
For development, point Claude Desktop at cargo run:
{
"mcpServers": {
"rust-file-explorer": {
"command": "cargo",
"args": [
"run",
"--manifest-path",
"/absolute/path/to/your/rust-project/Cargo.toml"
]
}
}
}
For daily use, build the release binary first to avoid recompilation on every launch:
cargo build --release
Then point Claude Desktop at the compiled binary directly:
{
"mcpServers": {
"rust-file-explorer": {
"command": "/absolute/path/to/your/rust-project/target/release/file-explorer-mcp",
"args": []
}
}
}
After editing the configuration file, completely restart Claude Desktop. A simple window close is not sufficient. The host process must be fully terminated and restarted to reload the server registry and spawn new child processes.
Once connected, Claude Desktop displays available tools and invokes them when relevant to a task. The exact UI representation may vary across application versions — refer to Anthropic’s current Claude Desktop documentation for the latest behavior.
For Cursor users, the configuration follows the same pattern using a .cursor/mcp.json file in the project root:
{
"mcpServers": {
"rust-file-explorer": {
"command": "cargo",
"args": ["run"]
}
}
}
The MCP Attack Surface
MCP gives an AI model the ability to invoke real-world operations. This creates attack surfaces that are qualitatively different from those in traditional API security. What follows covers the most significant threat categories and their mitigations.
1. Tool Poisoning and Rug Pulls
Threat: Tool descriptions are not merely documentation. They are executable context loaded directly into the model’s reasoning. An attacker who controls a tool server can embed hidden instructions inside a description to manipulate the model’s behavior. A \textit{rug pull} is a related variant — a description that appears benign at approval time is later modified to include malicious instructions, without triggering a new consent prompt.
Mitigations:
- Treat tool descriptions as code. Subject them to the same review, versioning, and testing as application logic.
- Pin tool versions and use cryptographic signing to bind tool definitions to verified, immutable versions.
- Monitor descriptions for changes between sessions and force re-approval whenever a description changes.
2. The Confused Deputy Problem
Threat: An MCP proxy acting on behalf of multiple users may connect to third-party APIs using a single, static client identity. If user-specific authorization is not correctly propagated, an attacker can access a third-party API as a different user, bypassing individual consent.
Mitigations:
- Maintain a per-client consent registry mapping specific OAuth client IDs to the users who approved them.
- Prohibit \textit{token passthrough}, which is the anti-pattern of forwarding a user’s token to a downstream API without re-validation. This eliminates the audit trail and scope enforcement that proper token exchange provides.
- Use token exchange (RFC 8693). The MCP server presents the original user’s token to the authorization server and receives a new, properly scoped downstream token, preserving user attribution at each hop.
3. SSRF via OAuth Metadata Discovery
Threat: During OAuth metadata discovery, a malicious server can supply URLs pointing to internal infrastructure, including private IP ranges, cloud metadata endpoints (e.g., 169.254.169.254), or localhost services, causing the MCP client to make unauthorized requests on its behalf.
Mitigations:
- Enforce HTTPS for all OAuth-related URLs in production environments.
- Block requests to private and reserved IP address ranges, including loopback (
127.0.0.0/8), link-local (169.254.0.0/16), and RFC 1918 private ranges. - Validate redirect targets strictly and consider routing OAuth discovery requests through an egress proxy that enforces allowlist-based network policies.
4. Prompt Injection via Sampling
Threat: The MCP sampling feature allows a server to request the AI model to generate content. A malicious server can craft sampling requests containing injected instructions that manipulate the model’s subsequent reasoning, or request that data from other servers be included in the sampling context, thereby exfiltrating information it was never intended to access.
Mitigations:
- Enforce strict context isolation on the client side. A server’s sampling requests must be limited to its own context and must not pull in data from other connected servers.
- Implement runtime monitoring to detect anomalous invocation patterns and unexpected parameter values that may indicate an injection attempt.
5. Cross-Server Data Exfiltration
Threat: In multi-tenant or multi-server deployments, a malicious server can manipulate the shared agent context such that the AI agent inadvertently acts as a data bridge — reading sensitive data from a legitimate server and passing it to the malicious one.
Mitigations:
- Establish behavioral baselines and monitor invocation graphs — records of which tools invoke which other tools — to detect anomalous cross-server data flows.
- Maintain comprehensive audit trails with strict user attribution, logging the originating user, the invoked tool, and the full parameters for every call.
6. Local Compromise and Command Injection
Threat: Local MCP servers are binaries executed directly on the user’s machine. Without sandboxing, a compromised server configuration or an exploited vulnerability can result in arbitrary code execution with the host user’s full OS privileges.
Mitigations:
- Run MCP servers in sandboxed environments — containers or VMs — using minimal base images (distroless or Alpine) with seccomp profiles to restrict available system calls.
- Implement default-deny egress network policies so a compromised server cannot reach arbitrary internet destinations.
- Display a clear, untruncated pre-connection consent dialog showing the exact commands that will be executed before any new local server is connected.
7. Session Hijacking
Threat: An attacker who obtains a valid session ID can impersonate a legitimate client, enqueue malicious tool invocations, or read responses intended for the legitimate user.
Mitigations:
- Never use session identifiers as authentication. They are continuity tokens, not identity assertions.
- Generate session IDs using a cryptographically secure random source to ensure they are non-deterministic and computationally infeasible to guess.
- Bind session IDs to user-specific context (e.g., a compound key of the form
<user_id>:<session_id>) so a stolen or guessed session ID cannot be used to impersonate a different user.
8. Scope Creep at Authorization
Threat: Granting broad OAuth scopes (e.g., files:*) at initial authorization increases the blast radius if a token is stolen. It enables lateral data access, privilege chaining, and lets an attacker invoke high-risk tools without triggering any additional authorization prompt.
Mitigations:
- Implement a progressive, least-privilege scope model — request only the minimum scopes needed at initial authorization.
- Use incremental elevation via targeted
WWW-Authenticatescope challenges when a higher-privilege operation is first attempted at runtime, deferring scope grants until they are actually required.
Final Thoughts
Rust is not the easiest starting point for MCP development. It requires a steeper initial learning curve than TypeScript or Python, and that trade-off is real. For production deployments where correctness, performance, and security are non-negotiable, however, the properties Rust offers are genuinely well-suited to what MCP demands.
A well-written Rust MCP server eliminates an entire class of memory corruption vulnerabilities at compile time and operates with more predictable resource usage than equivalent implementations in garbage-collected languages. That said, Rust does not secure your application for you. Access control, token scoping, input validation, and infrastructure hardening remain the developer’s responsibility regardless of language.
The ecosystem is ready. Whether you need the official conformance of rmcp, the enterprise observability of pmcp, the protocol cutting-edge of mcpkit, or the minimalism of async-mcp, there is a crate positioned for your use case. Start simple, avoid .unwrap() in production code from day one, and build the security perimeter before you build the features.