This tutorial provides a comprehensive guide to building MCP (Model Context Protocol) servers in Rust, progressing from simple examples to production-ready enterprise applications.
- Introduction to MCP
- Getting Started
- Basic Examples (1-5)
- Intermediate Examples (6-10)
- Advanced Examples (11-15)
- Enterprise Examples (16-20)
- Best Practices
- Production Deployment
The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs (Large Language Models). It enables AI-powered tools and workflows by solving the M×N integration problem through a unified protocol.
- Tools: Functions that LLMs can call to perform actions
- Resources: Data sources that LLMs can access (documents, databases, APIs)
- Prompts: Reusable prompt templates
- Sampling: LLM text generation capabilities
Rust provides several advantages for MCP server development:
- Memory Safety: Prevents common bugs without garbage collection overhead
- Performance: Comparable to C/C++ with high-level ergonomics
- Concurrency: Excellent async/await support with Tokio
- Type Safety: Catches errors at compile time
- Ecosystem: Rich crate ecosystem for JSON, HTTP, databases, etc.
Official MCP Documentation:
- MCP Official Website - Core concepts and overview
- MCP Specification - Complete protocol specification
- MCP GitHub Repository - Official implementations and examples
Rust MCP Toolkit (rmcp):
- A Coder's Guide to the Official Rust MCP Toolkit - Comprehensive guide to
rmcp - Official Rust MCP SDK - Official Rust implementation
- rmcp Documentation - API reference and examples
Rust Learning Resources:
- The Rust Programming Language - Official Rust book
- Rust by Example - Learn Rust through examples
- Tokio Tutorial - Async programming in Rust
- Rust 1.70+ installed
- Basic understanding of Rust async programming
- Familiarity with JSON-RPC concepts
# Clone the tutorial repository
git clone <repository-url>
cd mcp-rust-tutorial
# Run any example
cargo run --bin example_01_hello_worldKey dependencies used throughout the tutorial:
[dependencies]
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tracing = "0.1"
tracing-subscriber = "0.3"
uuid = { version = "1.0", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }Project Setup and Cargo:
- The Cargo Book - Complete guide to Rust's package manager
- Managing Dependencies - How to add and manage crates
- Cargo.toml Reference - Complete manifest file reference
Essential Crates Documentation:
- Tokio Documentation - Async runtime and utilities
- Serde Documentation - Serialization/deserialization framework
- Tracing Documentation - Application-level tracing framework
- UUID Documentation - Unique identifier generation
- Chrono Documentation - Date and time handling
The simplest possible MCP server with a single greeting tool.
Key Concepts:
- Basic MCP server structure
- Tool definitions with JSON schema
- Simple JSON-RPC message handling
Features Demonstrated:
- Single tool (
greeting) with parameter validation - Basic error handling
- JSON schema for input validation
Rust Concepts Explained:
1. Struct Definition and Implementation Blocks
pub struct HelloWorldServer; // Unit struct - no fields needed
impl HelloWorldServer { // Implementation block for methods
// Methods go here
}- Unit Struct:
HelloWorldServeris a unit struct (no fields), perfect for stateless servers pubKeyword: Makes the struct public, allowing external access- Implementation Block:
impldefines methods associated with the struct
2. Vector Creation and Initialization
pub fn list_tools(&self) -> Vec<Tool> {
vec![Tool { /* ... */ }] // vec! macro creates a vector
}vec!Macro: Creates a vector with initial elements- Return Type:
Vec<Tool>specifies a vector of Tool structs &selfParameter: Immutable reference to the struct instance
3. Serde JSON Integration
use serde::{Deserialize, Serialize}; // Import traits
#[derive(Serialize, Deserialize, Debug)] // Derive macros
pub struct GreetingRequest {
pub name: String,
}- Derive Macros: Automatically implement traits for serialization
- Serialize/Deserialize: Convert Rust structs to/from JSON
- Debug Trait: Enables printing with
{:?}format
4. JSON Schema with serde_json::json! Macro
input_schema: serde_json::json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the person to greet"
}
},
"required": ["name"]
}),json!Macro: Creates JSON values at compile time- Type Safety: Rust ensures the JSON structure is valid
- Schema Definition: Defines expected input structure for tools
5. String Handling
name: "greeting".to_string(), // Convert &str to String- String vs &str:
Stringis owned,&stris borrowed .to_string(): Converts string literals to owned String types
Real Code from Example:
1. Data Structures - The Foundation
// Step 1: Define request/response structures for type safety
#[derive(Serialize, Deserialize, Debug)]
pub struct GreetingRequest {
pub name: String, // The parameter clients will send
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GreetingResponse {
pub message: String, // The formatted response we'll return
}Pedagogical Note: This demonstrates Rust's approach to data modeling. Instead of working with raw JSON, we define strongly-typed structures that the compiler can validate. The derive macros automatically implement serialization/deserialization.
2. Server Structure - Clean Architecture
// Step 2: Create the server handler (stateless in this simple example)
pub struct HelloWorldServer;
impl HelloWorldServer {
pub fn new() -> Self {
Self // Unit struct constructor
}
// Define what tools this server provides
pub fn list_tools(&self) -> Vec<Tool> {
vec![Tool {
name: "greeting".to_string(),
description: "Generate a personalized greeting message".to_string(),
input_schema: serde_json::json!({
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the person to greet"
}
},
"required": ["name"]
}),
}]
}
}Pedagogical Note: This shows the MCP pattern of separating concerns - the server structure holds state (none in this case), while methods handle specific protocol operations.
3. Tool Implementation - Business Logic
// Step 3: Implement the actual tool logic
pub fn call_tool(&self, name: &str, arguments: Value) -> Result<Value, String> {
match name {
"greeting" => {
// Parse incoming JSON into our typed structure
let request: GreetingRequest = serde_json::from_value(arguments)
.map_err(|e| format!("Failed to parse arguments: {}", e))?;
// Execute business logic (create greeting)
let response = GreetingResponse {
message: format!("Hello, {}! Welcome to MCP with Rust!", request.name),
};
// Convert back to JSON for MCP protocol
serde_json::to_value(response)
.map_err(|e| format!("Failed to serialize response: {}", e))
}
_ => Err(format!("Unknown tool: {}", name)),
}
}Pedagogical Note: This demonstrates the complete data flow: JSON → Rust struct → business logic → Rust struct → JSON. The ? operator provides clean error propagation.
4. Async Main Function - Program Entry Point
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize logging for debugging
tracing_subscriber::fmt::init();
println!("🚀 Starting Hello World MCP Server");
println!("📝 Available tools: greeting");
let server = HelloWorldServer::new();
// Simple JSON-RPC message loop (production would use rmcp crate)
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
let stdin = stdin();
let mut stdout = stdout();
let mut reader = BufReader::new(stdin);
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line).await {
Ok(0) => break, // EOF
Ok(_) => {
// Process JSON-RPC message...
}
Err(e) => {
eprintln!("Error reading input: {}", e);
break;
}
}
}
Ok(())
}Pedagogical Note: The #[tokio::main] attribute transforms the main function into an async runtime. This example shows basic I/O handling, though real MCP servers would use the rmcp crate for protocol handling.
Run Example:
cargo run --bin example_01_hello_worldBasic Rust Concepts:
- Structs and Methods - Rust Book chapter on structs
- Error Handling - Result types and error propagation
- Traits - Defining shared behavior
- Derive Macros - Automatic trait implementations
JSON and Serialization:
- Serde Tutorial - Working with derive macros
- JSON Schema - Understanding JSON schema validation
- Working with JSON in Rust - Practical JSON handling
MCP Protocol Basics:
- JSON-RPC 2.0 Specification - Understanding the underlying protocol
- MCP Client-Server Communication - How clients communicate with servers
Building upon the hello world example with mathematical operations and comprehensive error handling.
Key Concepts:
- Multiple operations in a single tool
- Custom error types
- Parameter validation
- Robust error handling patterns
Features Demonstrated:
- Mathematical operations (add, subtract, multiply, divide)
- Division by zero protection
- Input validation and error messages
Rust Concepts Explained:
1. Result Type and Error Handling
fn perform_calculation(&self, request: &CalculatorRequest) -> Result<f64, CalculatorError> {
// Result<T, E> represents either success (Ok(T)) or failure (Err(E))
}- Result<T, E>: Rust's way of handling fallible operations
- Ok(value): Success case containing the result
- Err(error): Failure case containing error information
- No Exceptions: Rust uses Result instead of exceptions
2. Pattern Matching with match
match request.operation.as_str() {
"divide" => { /* division logic */ },
"add" => { /* addition logic */ },
_ => { /* default case */ }
}- Pattern Matching: Exhaustive checking of all possible values
- String Patterns: Matching on string values
- Wildcard
_: Catches all unmatched cases
3. Custom Error Types
#[derive(Debug)]
pub enum CalculatorError {
DivisionByZero,
UnsupportedOperation(String),
}
impl std::fmt::Display for CalculatorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CalculatorError::DivisionByZero => write!(f, "Division by zero is not allowed"),
CalculatorError::UnsupportedOperation(op) => write!(f, "Unsupported operation: {}", op),
}
}
}- Enum: Defines a type with multiple variants
- Associated Data:
UnsupportedOperation(String)carries additional data - Trait Implementation:
Displaytrait for user-friendly error messages
4. Conditional Logic and Early Return
if request.b == 0.0 {
Err(CalculatorError::DivisionByZero) // Early return with error
} else {
Ok(request.a / request.b) // Success case
}- Early Return: Return immediately on error conditions
- Type Safety: Compiler ensures all paths return Result type
5. Method Parameters and References
fn perform_calculation(&self, request: &CalculatorRequest) -> Result<f64, CalculatorError>
// ^self ^borrowed reference&self: Immutable reference to the struct instance&CalculatorRequest: Borrowed reference to avoid ownership transfer- Borrowing: Access data without taking ownership
6. Floating Point Operations
request.a / request.b // f64 division
request.a + request.b // f64 addition- f64 Type: 64-bit floating point numbers
- Arithmetic Operators: Standard mathematical operations
- IEEE 754: Rust follows IEEE floating point standards
Real Code from Example:
1. Structured Request/Response Types
// Define the calculator request structure with multiple parameters
#[derive(Serialize, Deserialize, Debug)]
pub struct CalculatorRequest {
pub operation: String, // The mathematical operation to perform
pub a: f64, // First number in the calculation
pub b: f64, // Second number in the calculation
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CalculatorResponse {
pub result: f64, // The result of the calculation
pub operation_performed: String, // The operation that was performed (for confirmation)
}Pedagogical Note: Notice how we model the domain with precise types. The f64 type ensures floating-point arithmetic, while the operation_performed field provides useful feedback to clients.
2. Custom Error Types - Production Pattern
// Custom error type for calculator-specific errors
#[derive(Debug)]
pub enum CalculatorError {
DivisionByZero,
UnsupportedOperation(String),
}
impl std::fmt::Display for CalculatorError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CalculatorError::DivisionByZero => write!(f, "Division by zero is not allowed"),
CalculatorError::UnsupportedOperation(op) => write!(f, "Unsupported operation: {}", op),
}
}
}
impl std::error::Error for CalculatorError {}Pedagogical Note: This demonstrates Rust's idiomatic error handling. By implementing Display and Error traits, our custom errors integrate seamlessly with Rust's error ecosystem.
3. Core Business Logic with Validation
// Private method to perform the actual calculation
fn perform_calculation(&self, request: &CalculatorRequest) -> Result<f64, CalculatorError> {
match request.operation.as_str() {
"add" => Ok(request.a + request.b),
"subtract" => Ok(request.a - request.b),
"multiply" => Ok(request.a * request.b),
"divide" => {
// Validate that we're not dividing by zero
if request.b == 0.0 {
Err(CalculatorError::DivisionByZero)
} else {
Ok(request.a / request.b)
}
}
_ => Err(CalculatorError::UnsupportedOperation(
request.operation.clone(),
)),
}
}Pedagogical Note: This shows defensive programming - we validate inputs and handle edge cases explicitly. The match expression ensures all operations are handled, with the _ wildcard catching invalid operations.
4. Tool Definition with Schema Validation
pub fn list_tools(&self) -> Vec<Tool> {
vec![Tool {
name: "calculator".to_string(),
description: "Perform basic arithmetic operations (add, subtract, multiply, divide)".to_string(),
input_schema: serde_json::json!({
"type": "object",
"properties": {
"operation": {
"type": "string",
"description": "The operation to perform",
"enum": ["add", "subtract", "multiply", "divide"] // Constrains valid values
},
"a": {
"type": "number",
"description": "First number"
},
"b": {
"type": "number",
"description": "Second number"
}
},
"required": ["operation", "a", "b"] // All parameters are mandatory
}),
}]
}Pedagogical Note: The JSON schema provides client-side validation. The enum constraint limits operations to valid values, and required ensures all parameters are provided.
5. Complete Tool Call Handler
pub fn call_tool(&self, name: &str, arguments: Value) -> Result<Value, String> {
match name {
"calculator" => {
// Parse the request
let request: CalculatorRequest = serde_json::from_value(arguments)
.map_err(|e| format!("Failed to parse arguments: {}", e))?;
// Perform the calculation
let result = self
.perform_calculation(&request)
.map_err(|e| e.to_string())?; // Convert our custom error to string
// Create the response
let response = CalculatorResponse {
result,
operation_performed: format!(
"{} {} {}",
request.a, request.operation, request.b
),
};
serde_json::to_value(response)
.map_err(|e| format!("Failed to serialize response: {}", e))
}
_ => Err(format!("Unknown tool: {}", name)),
}
}Pedagogical Note: This shows the complete error handling pipeline: JSON parsing errors, business logic errors, and serialization errors are all handled gracefully using the ? operator for clean error propagation.
Run Example:
cargo run --bin example_02_calculatorAdvanced Error Handling:
- Error Handling Patterns in Rust - Comprehensive error handling guide
- Custom Error Types - Creating your own error types
- The ? Operator - Error propagation shorthand
- thiserror crate - Convenient derive macros for error types
Pattern Matching:
- Pattern Matching - Enums and pattern matching
- Match Expressions - Detailed match syntax
- Advanced Patterns - Pattern matching techniques
Floating Point Arithmetic:
- Floating-Point Arithmetic - f32 and f64 types
- IEEE 754 Standard - Understanding floating-point representation
- Rust Numeric Types - All numeric types in Rust
Demonstrates organizing multiple related tools within a single MCP server.
Key Concepts:
- Multiple tools in one server
- Text transformation operations
- Tool organization patterns
Features Demonstrated:
- Text transformations (uppercase, lowercase, reverse, capitalize)
- Text analysis (word count, character analysis)
- Multiple tool management
Rust Concepts Explained:
1. String Methods and Transformations
text.to_uppercase() // Creates new String with uppercase characters
text.to_lowercase() // Creates new String with lowercase characters
text.trim().to_string() // Removes whitespace and converts to owned String- String Methods: Built-in methods for string manipulation
- Ownership: These methods create new String instances
- Method Chaining: Can chain multiple string operations
2. Iterator Patterns and Functional Programming
text.chars().rev().collect() // Reverse characters using iterators
text.split_whitespace() // Split into words
.map(|word| capitalize_word(word)) // Transform each word
.collect::<Vec<_>>() // Collect into vector
.join(" ") // Join back with spaces- Iterators: Lazy evaluation for efficient processing
- Functional Style: Transform data through method chains
- Closures:
|word|is a closure (anonymous function) - Collect: Materialize iterator results into collections
3. Character Processing
text.chars().any(|c| c.is_uppercase()) // Check if any char is uppercase
text.chars().any(|c| c.is_numeric()) // Check if any char is numeric
text.chars().count() // Count characters- Character Iterator:
.chars()creates iterator over Unicode scalar values - Predicate Functions:
.any()tests conditions across elements - Unicode Support: Full Unicode character support
4. String Splitting and Processing
text.split_whitespace().count() // Count words
text.lines().count() // Count lines- Split Methods: Various ways to break strings apart
- Whitespace Handling: Automatic handling of spaces, tabs, newlines
5. Complex String Manipulation
fn capitalize_words(&self, text: &str) -> String {
text.split_whitespace()
.map(|word| {
let mut chars = word.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
})
.collect::<Vec<_>>()
.join(" ")
}- Option Handling:
chars.next()returnsOption<char> - Pattern Matching: Handle Some/None cases safely
- String Concatenation: Using
+operator for strings - Type Annotations:
collect::<String>()specifies collection type
Run Example:
cargo run --bin example_03_text_processorString Processing and Iterators:
- String Types in Rust - String vs &str and UTF-8 handling
- Iterator Trait - Working with iterators
- Functional Programming in Rust - Closures and iterators
- Processing Collections - Practical iterator examples
Unicode and Character Processing:
- Unicode in Rust - Unicode support
- UTF-8 Everywhere - Understanding UTF-8 encoding
- char type - Unicode scalar values
Functional Programming Patterns:
- Closures - Anonymous functions
- Higher-Order Functions - Functions that operate on functions
- Method Chaining - Building processing pipelines
Implementation details would be based on the actual example_04 file
Demonstrates MCP resources for providing document access to LLMs.
Key Concepts:
- MCP Resources (not just tools)
- Document storage and retrieval
- Search functionality
- URI-based resource identification
Features Demonstrated:
- Document collection with metadata
- Search by title, content, author, and tags
- Resource URIs (
document://doc_id) - Resource reading by URI
Rust Concepts Explained:
1. HashMap for Data Storage
use std::collections::HashMap;
struct ResourceProviderServer {
documents: HashMap<String, Document>, // Key-value storage
}- HashMap<K, V>: Hash table for O(1) average lookups
- Generic Types:
Kis key type,Vis value type - Ownership: HashMap owns its key-value pairs
2. Iterator Transformations
self.documents
.values() // Iterator over values only
.map(|doc| Resource { /* transform */ }) // Transform each document
.collect() // Materialize into Vec.values(): Iterate over HashMap values, ignoring keys.map(): Transform each element using a closure- Lazy Evaluation: Operations are deferred until
.collect()
3. Option Types and Pattern Matching
name: Some(doc.title.clone()), // Wrap in Some variant
description: Some(format!("...")), // Option<String>
mime_type: Some("text/plain".to_string()),- Option: Represents optional values (Some(T) or None)
- Some(value): Present value variant
- Explicit Optionality: Rust forces handling of missing values
4. String Formatting and Interpolation
format!("document://{}", doc.id) // String interpolation
format!("Document by {} - Tags: {}",
doc.author, doc.tags.join(", ")) // Multiple parametersformat!Macro: Type-safe string formatting{}Placeholders: Positional parameter substitution- Display Trait: Uses
Displayimplementation for formatting
5. Vector Operations
doc.tags.join(", ") // Join vector elements with separator.join(): Concatenate vector elements with delimiter- String Collections: Working with
Vec<String>
6. Cloning for Ownership
name: Some(doc.title.clone()), // Clone the string.clone(): Create owned copy of data- Ownership Transfer: Move vs clone for memory management
- Trade-offs: Cloning uses memory but avoids borrowing issues
7. Struct Field Access
Resource {
uri: format!("document://{}", doc.id),
name: Some(doc.title.clone()),
description: Some(format!("Document by {} - Tags: {}",
doc.author, doc.tags.join(", "))),
mime_type: Some("text/plain".to_string()),
}- Struct Literals: Creating instances with named fields
- Field Access: Using dot notation to access struct fields
- Constructor Pattern: Building complex objects step by step
Real Code from Example:
1. Rich Domain Models - Document Structure
// Structure representing a comprehensive document resource
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Document {
pub id: String,
pub title: String,
pub content: String,
pub author: String,
pub created_at: String,
pub tags: Vec<String>,
}
// MCP Resource representation
#[derive(Serialize, Deserialize, Debug)]
pub struct Resource {
pub uri: String, // Unique identifier (e.g., "document://doc1")
pub name: Option<String>, // Human-readable name
pub description: Option<String>, // Detailed description
pub mime_type: Option<String>, // Content type
}Pedagogical Note: Notice the rich metadata model. Documents have structured information, while Resources provide the MCP protocol interface. The Clone trait allows documents to be duplicated when needed.
2. Server with In-Memory Storage
pub struct ResourceProviderServer {
// In-memory document storage for this example
// In a real application, this might be a database connection
documents: HashMap<String, Document>,
}
impl ResourceProviderServer {
pub fn new() -> Self {
let mut documents = HashMap::new();
// Add comprehensive sample documents for testing
documents.insert("doc1".to_string(), Document {
id: "doc1".to_string(),
title: "Introduction to Model Context Protocol".to_string(),
content: "The Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context to LLMs...".to_string(),
author: "MCP Team".to_string(),
created_at: "2024-01-01T00:00:00Z".to_string(),
tags: vec!["MCP".to_string(), "Protocol".to_string(), "AI".to_string()],
});
// ... more documents
Self { documents }
}
}Pedagogical Note: This shows a common pattern - initializing with sample data for testing. The HashMap provides O(1) lookups by document ID.
3. Resource Listing - Iterator Transformation
// List all available resources
pub fn list_resources(&self) -> Vec<Resource> {
self.documents
.values() // Iterator over HashMap values
.map(|doc| Resource { // Transform each Document into Resource
uri: format!("document://{}", doc.id),
name: Some(doc.title.clone()),
description: Some(format!(
"Document by {} - Tags: {}",
doc.author,
doc.tags.join(", ") // Join vector elements with separator
)),
mime_type: Some("text/plain".to_string()),
})
.collect() // Materialize iterator into Vec
}Pedagogical Note: This demonstrates functional programming in Rust. The transformation pipeline efficiently converts internal document representation to MCP resource format.
4. Resource Reading with URI Parsing
// Read a specific resource by URI
pub fn read_resource(&self, uri: &str) -> Result<Value, String> {
// Parse the URI to extract the document ID
if let Some(doc_id) = uri.strip_prefix("document://") {
if let Some(document) = self.documents.get(doc_id) {
// Return the document content as a resource
Ok(serde_json::json!({
"contents": [{
"uri": uri,
"mimeType": "text/plain",
"text": document.content
}]
}))
} else {
Err(format!("Document not found: {}", doc_id))
}
} else {
Err(format!("Invalid document URI: {}", uri))
}
}Pedagogical Note: This shows URI parsing and validation. The strip_prefix method safely extracts the document ID, with proper error handling for malformed URIs.
5. Advanced Search Implementation
// Helper method to search documents by query
fn search_documents(&self, query: &str, limit: Option<usize>) -> Vec<&Document> {
let query_lower = query.to_lowercase();
let mut matches: Vec<&Document> = self
.documents
.values()
.filter(|doc| {
doc.title.to_lowercase().contains(&query_lower)
|| doc.content.to_lowercase().contains(&query_lower)
|| doc.author.to_lowercase().contains(&query_lower)
|| doc.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower))
})
.collect();
// Sort by relevance (simple scoring based on title matches)
matches.sort_by(|a, b| {
let a_score = if a.title.to_lowercase().contains(&query_lower) { 2 } else { 0 }
+ if a.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower)) { 1 } else { 0 };
let b_score = if b.title.to_lowercase().contains(&query_lower) { 2 } else { 0 }
+ if b.tags.iter().any(|tag| tag.to_lowercase().contains(&query_lower)) { 1 } else { 0 };
b_score.cmp(&a_score) // Sort by highest score first
});
if let Some(limit) = limit {
matches.into_iter().take(limit).collect()
} else {
matches
}
}Pedagogical Note: This demonstrates advanced iterator usage with filtering, scoring, and sorting. The search algorithm combines multiple fields with relevance scoring - title matches score higher than tag matches.
6. Tool Integration with Resources
pub fn call_tool(&self, name: &str, arguments: Value) -> Result<Value, String> {
match name {
"search_documents" => {
let request: SearchRequest = serde_json::from_value(arguments)
.map_err(|e| format!("Failed to parse arguments: {}", e))?;
let matches = self.search_documents(&request.query, request.limit);
let response = SearchResponse {
total_count: matches.len(),
matches: matches
.into_iter()
.map(|doc| DocumentSummary {
id: doc.id.clone(),
title: doc.title.clone(),
author: doc.author.clone(),
uri: format!("document://{}", doc.id),
tags: doc.tags.clone(),
})
.collect(),
};
serde_json::to_value(response)
.map_err(|e| format!("Failed to serialize response: {}", e))
}
_ => Err(format!("Unknown tool: {}", name)),
}
}Pedagogical Note: This shows how tools and resources work together. The search tool returns resource URIs that can then be read using the resource protocol.
Run Example:
cargo run --bin example_05_resource_providerData Structures and Collections:
- HashMap - Key-value storage
- Collections - Vectors, strings, and hash maps
- Vec - Dynamic arrays
- BTreeMap vs HashMap - Choosing the right collection
Option Types and Error Handling:
- Option Type - Handling missing values
- Result and Option - Practical examples
- Combinators - map, and_then, unwrap_or
Search Algorithms and String Processing:
- String Searching - Built-in string search methods
- Sorting - Custom sorting with closures
- Search Algorithms - Understanding search techniques
MCP Resources:
- MCP Resources Specification - Official resource protocol
- URI Schemes - Understanding URI structure
- MIME Types - Content type specification
Production-ready configuration management using files, environment variables, and command-line arguments.
Key Concepts:
- Multi-source configuration (files, env vars, CLI args)
- Tool enablement/disablement
- Runtime parameter configuration
- Configuration validation
Features Demonstrated:
- JSON configuration files
- Environment variable overrides
- Command-line argument processing
- Feature flags for tools
Rust Concepts Explained:
1. Environment Variable Access
use std::env;
if let Ok(server_name) = env::var("MCP_SERVER_NAME") {
config.server_name = server_name;
}std::env: Standard library module for environment accessenv::var(): ReturnsResult<String, VarError>if letPattern: Convenient pattern matching for Result types- Error Handling: Gracefully handle missing environment variables
2. Command Line Argument Processing
let args: Vec<String> = env::args().collect();
for i in 0..args.len() {
match args[i].as_str() {
"--server-name" if i + 1 < args.len() => {
config.server_name = args[i + 1].clone();
}
_ => {}
}
}env::args(): Iterator over command line arguments.collect(): Convert iterator to Vec- Index Bounds Checking: Ensure safe array access
- Pattern Guards:
ifconditions in match arms
3. Configuration Structure with Defaults
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ServerConfig {
pub server_name: String,
pub enabled_tools: Vec<String>,
pub tool_configs: HashMap<String, ToolConfig>,
}
impl Default for ServerConfig {
fn default() -> Self {
// Set sensible defaults
}
}- Derive Macros: Multiple traits derived automatically
- Clone Trait: Enable config copying
- Default Trait: Provide sensible default values
- Nested Structures: Complex configuration hierarchies
4. File I/O and JSON Parsing
if let Ok(config_content) = std::fs::read_to_string("server_config.json") {
if let Ok(file_config) = serde_json::from_str::<ServerConfig>(&config_content) {
config = file_config;
}
}- File Operations: Reading entire files to strings
- Nested Error Handling: Multiple fallible operations
- Type Annotations:
from_str::<ServerConfig>specifies target type - Graceful Degradation: Continue with defaults if file missing
5. Configuration Validation
fn validate_configuration(config: &ServerConfig) -> Result<(), Box<dyn std::error::Error>> {
if config.server_name.is_empty() {
return Err("Server name cannot be empty".into());
}
// More validation...
Ok(())
}- Trait Objects:
Box<dyn std::error::Error>for any error type - Early Return: Validation with immediate error reporting
.into(): Convert string literals to error types
6. Feature Flags and Conditional Logic
for tool_name in &config.enabled_tools {
if let Some(tool_config) = self.get_tool_config(tool_name) {
if !tool_config.enabled {
continue; // Skip disabled tools
}
// Process enabled tool
}
}- Reference Iteration:
&config.enabled_toolsborrows the vector - Option Handling: Safe access to potentially missing values
- Control Flow:
continueto skip loop iterations
Configuration Priority:
- Command-line arguments (highest)
- Environment variables
- Configuration files
- Default values (lowest)
Run Example:
# With environment variables
export MCP_SERVER_NAME="My Custom Server"
export MCP_MAX_CONNECTIONS=50
cargo run --bin example_06_configurable_server
# With command line args
cargo run --bin example_06_configurable_server -- --server-name "CLI Server"Configuration Management:
- Environment Variables - std::env module documentation
- Configuration Patterns - Builder pattern for configuration
- config crate - Popular configuration library
- clap crate - Command line argument parsing
File I/O and JSON Parsing:
- File I/O - Reading and writing files
- std::fs - File system operations
- JSON Processing - Working with JSON data
- Path Handling - Cross-platform path manipulation
Default Trait and Initialization:
- Default Trait - Providing default values
- Derive Default - Automatic default implementations
- Builder Pattern - Constructing complex objects
Production Configuration:
- The Twelve-Factor App - Configuration best practices
- dotenv crate - Loading environment variables from files
- figment crate - Advanced configuration management
Secure file system operations with comprehensive safety controls.
Key Concepts:
- Path validation and sanitization
- Directory traversal prevention
- File size limits
- Extension filtering
- Permission management
Features Demonstrated:
- Safe file reading/writing
- Directory listing with metadata
- File information retrieval
- Configurable security policies
Security Features:
- Allowed directory restrictions
- File extension whitelisting
- Path canonicalization
- Size limit enforcement
- Read-only mode support
Run Example:
cargo run --bin example_07_file_operationsImplementation details would be based on the actual example_08 file
SQLite database integration with connection pooling and migrations.
Key Concepts:
- Database connection pooling
- SQL migrations
- CRUD operations
- Prepared statements
- Error handling for database operations
Features Demonstrated:
- User management (create, read, update, delete)
- Database migrations
- Search with pagination
- Connection pool management
- Operation logging
Rust Concepts Explained:
1. Async/Await Programming
async fn create_user(&self, arguments: Value) -> Result<Value, String> {
// async function returns Future<Output = Result<Value, String>>
let result = sqlx::query_as::<_, (i64,)>(...)
.await?; // await the future and propagate errors
}async fn: Declares an asynchronous functionawait: Suspends execution until future completes- Non-blocking: Other tasks can run while waiting for I/O
- Error Propagation:
?operator works with async functions
2. Connection Pooling with SqlitePool
use sqlx::SqlitePool;
pub struct DatabaseServer {
pool: SqlitePool, // Shared connection pool
}
let pool = SqlitePool::connect("sqlite:./data/example.db").await?;- Connection Pooling: Reuse database connections efficiently
- Async Operations: All database calls are async
- Resource Management: Pool handles connection lifecycle
- Concurrent Access: Multiple tasks can share the pool safely
3. Prepared Statements and Parameter Binding
sqlx::query_as::<_, (i64,)>(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?) RETURNING id"
)
.bind(&request.name) // Bind first parameter
.bind(&request.email) // Bind second parameter
.bind(request.age) // Bind third parameter- SQL Injection Protection: Parameters are safely escaped
- Type Safety: Compile-time checking of SQL types
- Parameter Binding:
.bind()method for each placeholder - Query Compilation: Statements are prepared once and reused
4. Tuple Destructuring and Type Annotations
sqlx::query_as::<_, (i64,)>(...) // Returns tuple with single i64
let result = result.0; // Extract the ID from tuple- Type Hints:
<_, (i64,)>specifies return type - Tuple Types:
(i64,)is a single-element tuple - Destructuring: Access tuple elements by index
5. Error Handling with map_err
.fetch_one(&self.pool)
.await
.map_err(|e| format!("Failed to create user: {}", e))?;- Error Transformation: Convert database errors to strings
.map_err(): Transform error type while preserving success value- Error Context: Add meaningful error messages
- Chaining: Combine with
?operator for propagation
6. JSON Deserialization
let request: CreateUserRequest = serde_json::from_value(arguments)?;- Type Inference: Rust infers the target type from annotation
- Automatic Validation: Serde validates JSON structure
- Error Propagation:
?converts serde errors to function error type
7. Database Migrations
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
"#,
)
.execute(&self.pool)
.await?;- Raw String Literals:
r#"..."#preserves formatting and escaping - DDL Operations: Data Definition Language for schema changes
- Idempotent Migrations:
IF NOT EXISTSfor safe re-runs
Real Code from Example:
1. Database Configuration and Connection Setup
use sqlx::{Sqlite, SqlitePool};
#[derive(Debug, Serialize, Deserialize)]
pub struct DatabaseConfig {
pub database_url: String,
pub max_connections: u32,
}
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
database_url: "sqlite:./data/example.db".to_string(),
max_connections: 10,
}
}
}
pub struct DatabaseServer {
pool: SqlitePool, // Connection pool for efficient database access
config: DatabaseConfig,
}Pedagogical Note: Connection pooling is crucial for database performance. SQLx provides async connection pooling out of the box, allowing multiple concurrent database operations.
2. Async Database Initialization with Migrations
impl DatabaseServer {
pub async fn new(config: DatabaseConfig) -> Result<Self, sqlx::Error> {
// Create the data directory if it doesn't exist
if let Some(parent) = std::path::Path::new(&config.database_url.replace("sqlite:", "")).parent() {
std::fs::create_dir_all(parent).map_err(|e| sqlx::Error::Io(e))?;
}
// Create connection pool with configuration
let pool = SqlitePool::connect_with(
sqlx::sqlite::SqliteConnectOptions::new()
.filename(config.database_url.replace("sqlite:", ""))
.create_if_missing(true)
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal),
)
.await?;
let server = Self { pool, config };
// Run database migrations
server.run_migrations().await?;
Ok(server)
}
async fn run_migrations(&self) -> Result<(), sqlx::Error> {
// Create users table if it doesn't exist
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
"#,
)
.execute(&self.pool)
.await?;
println!("✅ Database migrations completed successfully");
Ok(())
}
}Pedagogical Note: This shows production-ready database setup with migrations, WAL mode for better concurrency, and proper error handling. The create_if_missing option simplifies deployment.
3. Type-Safe Database Operations
#[derive(Serialize, Deserialize, Debug)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
pub age: Option<i32>,
}
#[derive(Serialize, Deserialize, Debug, sqlx::FromRow)]
pub struct User {
pub id: i64,
pub name: String,
pub email: String,
pub age: Option<i32>,
pub created_at: String,
}
async fn create_user(&self, arguments: Value) -> Result<Value, String> {
let request: CreateUserRequest = serde_json::from_value(arguments)
.map_err(|e| format!("Failed to parse arguments: {}", e))?;
// Insert user and return the generated ID
let result = sqlx::query_as::<_, (i64,)>(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?) RETURNING id"
)
.bind(&request.name)
.bind(&request.email)
.bind(request.age)
.fetch_one(&self.pool)
.await
.map_err(|e| format!("Failed to create user: {}", e))?;
let user_id = result.0;
// Fetch the complete user record
let user = sqlx::query_as::<_, User>(
"SELECT id, name, email, age, created_at FROM users WHERE id = ?"
)
.bind(user_id)
.fetch_one(&self.pool)
.await
.map_err(|e| format!("Failed to fetch created user: {}", e))?;
serde_json::to_value(&user)
.map_err(|e| format!("Failed to serialize user: {}", e))
}Pedagogical Note: The sqlx::FromRow derive macro automatically maps SQL rows to Rust structs. Parameter binding prevents SQL injection attacks while maintaining type safety.
4. Advanced Query with Pagination
#[derive(Serialize, Deserialize, Debug)]
pub struct SearchUsersRequest {
pub query: Option<String>,
pub page: Option<u32>,
pub page_size: Option<u32>,
}
async fn search_users(&self, arguments: Value) -> Result<Value, String> {
let request: SearchUsersRequest = serde_json::from_value(arguments)?;
let page = request.page.unwrap_or(1);
let page_size = request.page_size.unwrap_or(10).min(100); // Limit max page size
let offset = (page - 1) * page_size;
let users = if let Some(query) = &request.query {
// Search with filtering
sqlx::query_as::<_, User>(
"SELECT id, name, email, age, created_at FROM users
WHERE name LIKE ? OR email LIKE ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?"
)
.bind(format!("%{}%", query))
.bind(format!("%{}%", query))
.bind(page_size as i32)
.bind(offset as i32)
.fetch_all(&self.pool)
.await
} else {
// Get all users with pagination
sqlx::query_as::<_, User>(
"SELECT id, name, email, age, created_at FROM users
ORDER BY created_at DESC
LIMIT ? OFFSET ?"
)
.bind(page_size as i32)
.bind(offset as i32)
.fetch_all(&self.pool)
.await
}
.map_err(|e| format!("Failed to search users: {}", e))?;
// Get total count for pagination
let total_count: (i64,) = if let Some(query) = &request.query {
sqlx::query_as(
"SELECT COUNT(*) FROM users WHERE name LIKE ? OR email LIKE ?"
)
.bind(format!("%{}%", query))
.bind(format!("%{}%", query))
.fetch_one(&self.pool)
.await
} else {
sqlx::query_as("SELECT COUNT(*) FROM users")
.fetch_one(&self.pool)
.await
}
.map_err(|e| format!("Failed to count users: {}", e))?;
let response = serde_json::json!({
"users": users,
"page": page,
"page_size": page_size,
"total_count": total_count.0,
"total_pages": (total_count.0 as f64 / page_size as f64).ceil() as u32
});
Ok(response)
}Pedagogical Note: This demonstrates real-world database patterns: pagination, search filtering, and count queries. The LIKE operator provides simple text searching, while LIMIT/OFFSET handles pagination.
5. Transaction Support for Data Integrity
async fn update_user(&self, arguments: Value) -> Result<Value, String> {
let request: UpdateUserRequest = serde_json::from_value(arguments)?;
// Start a database transaction
let mut tx = self.pool.begin().await
.map_err(|e| format!("Failed to start transaction: {}", e))?;
// Update the user within the transaction
let rows_affected = sqlx::query(
"UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?"
)
.bind(&request.name)
.bind(&request.email)
.bind(request.age)
.bind(request.id)
.execute(&mut *tx)
.await
.map_err(|e| format!("Failed to update user: {}", e))?
.rows_affected();
if rows_affected == 0 {
// Rollback transaction if user not found
tx.rollback().await
.map_err(|e| format!("Failed to rollback transaction: {}", e))?;
return Err("User not found".to_string());
}
// Fetch the updated user
let user = sqlx::query_as::<_, User>(
"SELECT id, name, email, age, created_at FROM users WHERE id = ?"
)
.bind(request.id)
.fetch_one(&mut *tx)
.await
.map_err(|e| format!("Failed to fetch updated user: {}", e))?;
// Commit the transaction
tx.commit().await
.map_err(|e| format!("Failed to commit transaction: {}", e))?;
serde_json::to_value(&user)
.map_err(|e| format!("Failed to serialize user: {}", e))
}Pedagogical Note: Transactions ensure data integrity. If any operation fails, the entire transaction is rolled back. The mut *tx syntax allows using the transaction reference with SQLx queries.
Run Example:
cargo run --bin example_09_databaseDatabase Programming in Rust:
- SQLx Book - Comprehensive async SQL toolkit
- Database Best Practices - SQLx FAQ and patterns
- Async Programming Book - Complete async programming guide
- diesel crate - Alternative ORM approach
SQL and Database Concepts:
- SQLite Tutorial - SQLite-specific features and syntax
- SQL Injection Prevention - Security best practices
- Database Transactions - ACID properties and isolation
- Connection Pooling - Understanding connection management
Async Rust and Tokio:
- Tokio Tutorial - Complete async runtime guide
- Async/Await in Rust - Official announcement and explanation
- Futures and Streams - Core async concepts
- Error Handling in Async - Async-specific error patterns
Production Database Patterns:
- Database Migrations - Schema versioning with SQLx CLI
- Query Performance - SQL performance tuning
- Connection Pool Tuning - Optimizing connection pools
Implementation details would be based on the actual example_10 file
Comprehensive system monitoring with metrics collection, health checks, and alerting.
Key Concepts:
- Real-time metrics collection
- Health check orchestration
- Threshold-based alerting
- Historical data management
- Time-series data handling
Features Demonstrated:
- System metrics (CPU, memory, disk, network)
- Service health checks
- Alert management (creation, filtering, clearing)
- Metrics history with circular buffering
- Configurable alert thresholds
Monitoring Capabilities:
- CPU and memory usage tracking
- Network activity monitoring
- Service availability checks
- Alert threshold configuration
- Historical trend analysis
Run Example:
cargo run --bin example_11_monitoringAsync background task processing with priority queues and worker management.
Key Concepts:
- Priority-based task scheduling
- Background worker processes
- Channel-based communication
- Graceful shutdown handling
- Task retry mechanisms
Features Demonstrated:
- Task prioritization (Low, Normal, High, Critical)
- Async task execution
- Worker lifecycle management
- Task status tracking
- Error handling and logging
Rust Concepts Explained:
1. Generic Functions with Trait Bounds
pub async fn add_task<F>(
&self,
priority: TaskPriority,
task: F,
description: String,
) -> Result<u64, String>
where
F: Fn() -> Result<String, String> + Send + 'static,- Generic Parameters:
<F>introduces a type parameter - Trait Bounds:
Fn() + Send + 'staticconstrains the type - Closure Traits:
Fn()means the closure can be called multiple times - Thread Safety:
Sendallows moving between threads - Lifetime:
'staticmeans no borrowed references with shorter lifetimes
2. Channels for Inter-Task Communication
use tokio::sync::mpsc;
let (sender, mut receiver) = mpsc::unbounded_channel::<TaskItem>();- Multi-Producer Single-Consumer: Multiple senders, one receiver
- Unbounded Channel: No limit on queued messages
- Generic Channel:
<TaskItem>specifies message type - Async Communication: Non-blocking message passing
3. Enums with Ordering
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TaskPriority {
Low = 1,
Normal = 2,
High = 3,
Critical = 4,
}- Discriminant Values: Explicit numeric values for ordering
- Derive Traits: Automatic comparison implementation
- Ordering:
PartialOrdandOrdenable sorting - Copy Semantics: Lightweight enum copying
4. Boxed Closures and Dynamic Dispatch
type TaskFunction = Box<dyn Fn() -> Result<String, String> + Send>;
pub struct TaskItem {
task: TaskFunction, // Dynamically dispatched function
}- Trait Objects:
dyn Fn()for runtime polymorphism - Heap Allocation:
Box<>stores closures on the heap - Type Aliases:
type TaskFunctioncreates readable type names - Send Trait: Ensures thread safety for cross-thread transfer
5. Background Task Spawning
tokio::spawn(async move {
let mut buffer = VecDeque::new();
while let Some(task) = receiver.recv().await {
buffer.push_back(task);
buffer.sort_by(|a, b| b.priority.cmp(&a.priority));
// Process tasks...
}
});- Task Spawning:
tokio::spawncreates concurrent task - Move Semantics:
async movetransfers ownership into closure - Deque Operations:
VecDequefor efficient queue operations - Custom Sorting: Priority-based task ordering
6. Error Handling in Channels
self.sender.send(task_item)
.map_err(|_| "Task queue is shut down".to_string())?;- Channel Errors: Send fails when receiver is dropped
- Error Mapping: Convert channel error to string
- Graceful Degradation: Meaningful error messages
7. Async Control Flow
while let Some(task) = receiver.recv().await {
// Process each task as it arrives
let task_id = task.id;
// Execute the task and handle the result
match (task.task)() {
Ok(result) => println!("Task {} completed: {}", task_id, result),
Err(error) => println!("Task {} failed: {}", task_id, error),
}
}- Async Loops:
while letwith.awaitfor stream processing - Pattern Matching: Handle task execution results
- Non-blocking: Other tasks can run during await points
Real Code from Example:
1. Type System for Async Tasks
// Type alias for task functions
// This represents a task that can be executed asynchronously
// Tasks are boxed functions that return a Result
type Task = Box<dyn Fn() -> Result<String, String> + Send + 'static>;
// Enum: TaskPriority with explicit ordering
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TaskPriority {
Low = 1,
Normal = 2,
High = 3,
Critical = 4,
}
// Task container with metadata
pub struct TaskItem {
id: u64,
priority: TaskPriority,
task: Task,
description: String,
}Pedagogical Note: The type alias makes complex types readable. The Send + 'static bounds ensure tasks can be moved between threads safely. The Ord trait enables priority-based sorting.
2. Queue Structure with Channel Communication
use tokio::sync::{mpsc, Mutex, Notify};
use std::collections::VecDeque;
pub struct TaskQueue {
sender: mpsc::UnboundedSender<TaskItem>, // Send tasks to worker
shutdown_notify: Arc<Notify>, // Coordinate shutdown
next_task_id: Arc<Mutex<u64>>, // Thread-safe ID generation
}
impl TaskQueue {
pub fn new() -> Self {
// Create an unbounded channel for task communication
let (sender, receiver) = mpsc::unbounded_channel::<TaskItem>();
// Create a notification mechanism for graceful shutdown
let shutdown_notify = Arc::new(Notify::new());
let shutdown_notify_worker = shutdown_notify.clone();
// Initialize the task ID counter
let next_task_id = Arc::new(Mutex::new(1u64));
// Spawn the background worker task
tokio::spawn(async move {
Self::worker_loop(receiver, shutdown_notify_worker).await;
});
Self { sender, shutdown_notify, next_task_id }
}
}Pedagogical Note: This shows the async ecosystem in action. mpsc::unbounded_channel provides async communication, Notify enables graceful shutdown signaling, and Arc<Mutex<>> provides thread-safe shared state.
3. Advanced Async Task Management
pub async fn add_task<F>(
&self,
priority: TaskPriority,
task: F,
description: String,
) -> Result<u64, String>
where
F: Fn() -> Result<String, String> + Send + 'static,
{
// Generate a unique ID for this task
let mut next_id = self.next_task_id.lock().await;
let task_id = *next_id;
*next_id += 1;
drop(next_id); // Release the lock early
// Create the task item
let task_item = TaskItem::new(task_id, priority, Box::new(task), description.clone());
// Send the task to the worker
match self.sender.send(task_item) {
Ok(_) => {
info!("Queued task {}: {} (priority: {:?})", task_id, description, priority);
Ok(task_id)
}
Err(_) => {
error!("Failed to queue task: worker has shut down");
Err("Task queue is shut down".to_string())
}
}
}Pedagogical Note: Notice the careful lock management - we acquire the mutex, increment the counter, then immediately drop the lock to minimize contention. The channel send operation can fail if the receiver is dropped.
4. Priority-Based Worker Loop
async fn worker_loop(
mut receiver: mpsc::UnboundedReceiver<TaskItem>,
shutdown_notify: Arc<Notify>,
) {
// Use a priority queue to ensure high-priority tasks are executed first
let mut task_buffer: VecDeque<TaskItem> = VecDeque::new();
info!("Task queue worker started");
loop {
// Use tokio::select! to handle both incoming tasks and shutdown signals
tokio::select! {
// Handle incoming tasks
task_option = receiver.recv() => {
match task_option {
Some(task) => {
// Insert the task in priority order
Self::insert_task_by_priority(&mut task_buffer, task);
// Process all available tasks in the buffer
Self::process_task_buffer(&mut task_buffer).await;
}
None => {
// Channel closed, no more tasks will arrive
warn!("Task channel closed, worker shutting down");
break;
}
}
}
// Handle shutdown signal
_ = shutdown_notify.notified() => {
info!("Shutdown signal received, processing remaining tasks");
// Process any remaining tasks
Self::process_task_buffer(&mut task_buffer).await;
// Process any remaining tasks in the channel
while let Ok(task) = receiver.try_recv() {
Self::insert_task_by_priority(&mut task_buffer, task);
}
Self::process_task_buffer(&mut task_buffer).await;
info!("Worker shutdown complete");
break;
}
}
}
}Pedagogical Note: tokio::select! is crucial for async programming - it allows handling multiple async operations concurrently. The worker processes tasks in priority order and handles graceful shutdown.
5. Priority Queue Implementation
// Insert a task into the buffer maintaining priority order
fn insert_task_by_priority(buffer: &mut VecDeque<TaskItem>, task: TaskItem) {
// Find the correct position to insert the task based on priority
let insert_position = buffer
.iter()
.position(|existing_task| existing_task.priority < task.priority)
.unwrap_or(buffer.len());
buffer.insert(insert_position, task);
}
// Process all tasks currently in the buffer
async fn process_task_buffer(buffer: &mut VecDeque<TaskItem>) {
while let Some(task) = buffer.pop_front() {
let task_id = task.id;
// Execute the task and handle the result
match task.execute() {
Ok(result) => {
info!("Task {} completed successfully: {}", task_id, result);
}
Err(error) => {
error!("Task {} failed: {}", task_id, error);
}
}
// Add a small delay between tasks to prevent overwhelming the system
sleep(Duration::from_millis(10)).await;
}
}Pedagogical Note: This shows manual priority queue implementation using VecDeque. Tasks are inserted in priority order and processed sequentially. The small delay prevents CPU saturation.
6. Sample Task Creation and Usage
// Create a sample task function for demonstration
fn create_sample_task(
task_name: String,
work_duration_ms: u64,
should_fail: bool,
) -> Box<dyn Fn() -> Result<String, String> + Send + 'static> {
Box::new(move || {
// Simulate some work
std::thread::sleep(Duration::from_millis(work_duration_ms));
if should_fail {
Err(format!("Task '{}' failed as requested", task_name))
} else {
Ok(format!("Task '{}' completed after {}ms", task_name, work_duration_ms))
}
})
}
// Usage in main function
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let task_queue = TaskQueue::new();
// Add a high-priority task
task_queue.add_task(
TaskPriority::High,
create_sample_task("High Priority Task".to_string(), 100, false),
"Critical system maintenance".to_string(),
).await?;
// Add a critical priority task (should be processed first)
task_queue.add_task(
TaskPriority::Critical,
create_sample_task("Critical Task".to_string(), 75, false),
"Emergency response".to_string(),
).await?;
Ok(())
}Pedagogical Note: This demonstrates closure creation with move semantics. The move keyword transfers ownership of variables into the closure, making it 'static. The higher-level API makes task creation simple.
Run Example:
cargo run --bin example_12_task_queueAdvanced Async Programming:
- Tokio Select Macro - Concurrent async operations
- Channels and Message Passing - Inter-task communication
- Spawning Tasks - Background task management
- Graceful Shutdown - Clean application termination
Concurrency Patterns:
- Actor Model - Message-passing concurrency
- Work Queue Pattern - Producer-consumer problem
- Priority Queues - Efficient priority handling
- Background Jobs - Task queue design patterns
Generic Programming and Trait Bounds:
- Generics - Generic types and functions
- Trait Bounds - Constraining generic types
- Send and Sync - Thread safety traits
- Lifetime Parameters - Managing references
Production Task Queue Systems:
- sidekiq.rs - Background job processing
- faktory crate - Language-agnostic job server
- celery-rs - Distributed task queue
- Queue Design Patterns - Scalable queue architecture
Production-ready authentication with JWT tokens, password hashing, and session management.
Key Concepts:
- Secure password hashing (SHA-256 for demo, use bcrypt/Argon2 in production)
- JWT-like token management
- Role-based access control
- Account lockout protection
- Session lifecycle management
Features Demonstrated:
- User registration with validation
- Secure authentication
- Token generation and validation
- Account lockout after failed attempts
- Role-based permissions
Rust Concepts Explained:
1. Thread-Safe Shared State
use std::sync::{Arc, RwLock};
pub struct AuthService {
users: Arc<RwLock<HashMap<String, User>>>,
active_tokens: Arc<RwLock<HashMap<String, TokenInfo>>>,
}- Arc (Atomically Reference Counted): Enables shared ownership across threads
- RwLock: Multiple readers OR single writer lock
- Thread Safety: Safe concurrent access to shared data
- Interior Mutability: Modify data behind shared references
2. Enums for Type Safety
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum UserRole {
Admin,
User,
Guest,
}
#[derive(Debug)]
pub enum AuthError {
UserNotFound,
InvalidPassword,
AccountLocked,
TokenExpired,
}- Type Safety: Compile-time guarantees for valid values
- Pattern Matching: Exhaustive handling of all cases
- Serialization: Convert to/from JSON with serde
- Error Variants: Structured error handling
3. DateTime Handling with Chrono
use chrono::{DateTime, Duration, Utc};
expires_at: Utc::now() + Duration::hours(24),
last_login: Some(Utc::now()),- UTC Timestamps: Timezone-aware datetime handling
- Duration Arithmetic: Add/subtract time periods
- Type Safety: Compile-time checks for datetime operations
- Serialization: JSON-compatible datetime formats
4. Password Hashing and Security
use sha2::{Digest, Sha256};
fn hash_password(password: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(password.as_bytes());
hex::encode(hasher.finalize())
}- Cryptographic Hashing: One-way password transformation
- Byte Operations: Convert strings to bytes for hashing
- Hex Encoding: Convert binary hash to string representation
- Security Note: Use bcrypt/Argon2 in production
5. Token Generation and Validation
fn generate_token(&self) -> String {
format!("{}_{}",
Uuid::new_v4().to_string().replace('-', ""),
Utc::now().timestamp()
)
}
fn is_token_valid(&self, token: &str) -> bool {
if let Ok(tokens) = self.active_tokens.read() {
if let Some(token_info) = tokens.get(token) {
return token_info.expires_at > Utc::now();
}
}
false
}- UUID Generation: Cryptographically secure random IDs
- String Manipulation: Format and modify token strings
- Lock Acquisition: Safe access to shared data
- Nested Pattern Matching: Handle multiple Option/Result types
6. Account Lockout Logic
if user.failed_login_attempts >= 3 {
if let Some(lockout_until) = user.locked_until {
if Utc::now() < lockout_until {
return Err(AuthError::AccountLocked);
}
}
}- Conditional Logic: Complex security rule implementation
- Option Handling: Safe access to potentially missing values
- Time Comparisons: Validate lockout periods
- Early Return: Immediate rejection of locked accounts
7. RwLock Usage Patterns
// Reading data
let users = self.users.read().unwrap();
if let Some(user) = users.get(username) { /* ... */ }
// Writing data
let mut users = self.users.write().unwrap();
users.insert(username.to_string(), new_user);- Read Lock: Multiple concurrent readers
- Write Lock: Exclusive access for modifications
- Lock Acquisition:
.read()and.write()methods - Panic Handling:
.unwrap()for lock poisoning (use proper error handling in production)
Security Features:
- Password strength validation
- Failed attempt tracking
- Account lockout mechanism
- Token expiration handling
- Secure session management
Run Example:
cargo run --bin example_13_auth_serviceSecurity and Cryptography:
- Password Hashing - OWASP password storage guidelines
- bcrypt crate - Secure password hashing (recommended over SHA-256)
- argon2 crate - Modern password hashing algorithm
- jsonwebtoken crate - JWT implementation for Rust
Thread Safety and Concurrency:
- Arc and Rc - Reference counting smart pointers
- Mutex and RwLock - Shared state concurrency
- Interior Mutability - Mutating immutable values
- Thread Safety - Send and Sync traits
Authentication Patterns:
- JWT Best Practices - JSON Web Token security
- Session Management - Secure session handling
- Rate Limiting - Preventing brute force attacks
- OAuth 2.0 - Industry standard authorization
DateTime and Temporal Logic:
- Chrono Documentation - Date and time handling
- Time Zones - Understanding temporal complexity
- UTC vs Local Time - Best practices for timestamps
Production Authentication:
- OWASP Authentication Guide - Comprehensive security cheat sheets
- Auth0 Blog - Authentication and security articles
- Security Headers - HTTP security best practices
Multi-channel notification system with templates and delivery tracking.
Key Concepts:
- Multi-channel delivery (Email, SMS, Webhook, Push)
- Template-based messaging
- Subscription management
- Delivery tracking and retry logic
- Background worker processes
Features Demonstrated:
- Notification templates with variables
- User subscription management
- Multi-channel delivery
- Delivery status tracking
- Retry mechanisms for failed deliveries
Supported Channels:
- Email notifications
- SMS messaging
- Webhook callbacks
- Push notifications
- In-app notifications
Run Example:
cargo run --bin example_14_notification_serviceETL (Extract, Transform, Load) pipeline with data processing and transformation.
Key Concepts:
- Data transformation pipelines
- ETL operations
- Data validation
- Pipeline composition
- Error handling in data flows
Features Demonstrated:
- Data record processing
- Transformation operations (filter, map, enrich)
- Pipeline chaining
- Error tracking and statistics
- Data validation
Transformation Types:
- Filtering by field values
- Mathematical transformations
- Data enrichment
- Statistical operations
Run Example:
cargo run --bin example_15_data_pipelineFull-text search engine with indexing and relevance scoring.
Key Concepts:
- Document indexing
- Full-text search
- Relevance scoring
- Search result ranking
- Index management
Features Demonstrated:
- Document indexing with metadata
- Word-based search indexing
- Tag-based searching
- Relevance scoring algorithms
- Search result pagination
Run Example:
cargo run --bin example_16_search_serviceBlockchain concepts including blocks, transactions, and proof-of-work.
Key Concepts:
- Blockchain data structures
- Transaction management
- Hash-based security
- Proof-of-work mining
- Chain validation
Features Demonstrated:
- Block creation and mining
- Transaction processing
- Hash calculation with SHA-256
- Proof-of-work algorithm
- Balance tracking
Run Example:
cargo run --bin example_17_blockchain_integrationMachine learning model serving with inference capabilities.
Key Concepts:
- Model management
- Inference pipelines
- Batch prediction
- Model versioning
- Performance tracking
Features Demonstrated:
- Model registration and activation
- Single and batch predictions
- Model versioning
- Inference statistics
- Model lifecycle management
Run Example:
cargo run --bin example_18_ml_model_serverService mesh gateway with routing and load balancing.
Key Concepts:
- Service discovery
- Load balancing strategies
- Request routing
- Health checking
- Gateway patterns
Features Demonstrated:
- Service registration
- Round-robin load balancing
- Route mapping
- Health status monitoring
- Request/response tracking
Load Balancing Strategies:
- Round Robin
- Weighted Round Robin
- Random selection
Run Example:
cargo run --bin example_19_microservice_gatewayComplete enterprise application combining authentication, monitoring, caching, and APIs.
Key Concepts:
- Enterprise architecture patterns
- Component integration
- Caching strategies
- API management
- Production patterns
Features Demonstrated:
- User management with sessions
- Multi-layer caching
- API endpoint routing
- Metrics collection
- Enterprise security patterns
Rust Concepts Explained:
1. Complex Generic Structures
pub struct Cache<T: Clone> {
entries: Arc<RwLock<HashMap<String, CacheEntry<T>>>>,
}
struct CacheEntry<T> {
value: T,
expires_at: DateTime<Utc>,
}- Generic Structures:
<T: Clone>makes cache work with any cloneable type - Trait Bounds:
Cloneconstraint enables value duplication - Nested Generics:
CacheEntry<T>uses the same generic parameter - Composition: Complex data structures built from simpler ones
2. Advanced Arc and RwLock Patterns
pub struct EnterpriseServer {
users: Arc<RwLock<HashMap<Uuid, User>>>,
sessions: Arc<RwLock<HashMap<Uuid, Session>>>,
user_cache: Cache<User>,
metrics: Arc<RwLock<Metrics>>,
}- Multiple Shared Resources: Each field is independently lockable
- Lock Granularity: Fine-grained locking prevents contention
- Type Composition: Combine primitive and custom types
- Resource Management: Automatic cleanup with RAII
3. TTL (Time-To-Live) Implementation
pub fn get(&self, key: &str) -> Option<T> {
if let Ok(entries) = self.entries.read() {
if let Some(entry) = entries.get(key) {
if Utc::now() < entry.expires_at {
return Some(entry.value.clone());
}
}
}
None
}- Expiration Logic: Check current time against expiration
- Automatic Cleanup: Expired entries are ignored
- Clone Semantics: Return owned copies of cached values
- Thread Safety: Multiple readers can access cache concurrently
4. Builder Pattern and Fluent APIs
impl<T: Clone> Cache<T> {
pub fn new() -> Self { /* ... */ }
pub fn set(&self, key: String, value: T, ttl_seconds: u64) {
let expires_at = Utc::now() + Duration::seconds(ttl_seconds as i64);
// Insert with expiration...
}
}- Method Chaining: Fluent interface design
- Duration Calculations: Compute expiration times
- Type Conversion: Safe casting between numeric types
- Immutable API: Methods take
&selffor concurrent access
5. Metrics Collection and Aggregation
#[derive(Debug, Default)]
pub struct Metrics {
request_count: u64,
total_response_time: u64,
active_sessions: u32,
cache_hits: u64,
cache_misses: u64,
}
impl Metrics {
pub fn average_response_time(&self) -> f64 {
if self.request_count > 0 {
self.total_response_time as f64 / self.request_count as f64
} else {
0.0
}
}
}- Default Trait: Zero-initialized metrics
- Numeric Calculations: Average computation with division
- Type Casting: Convert integers to floating point
- Division by Zero: Safe handling of edge cases
6. Session Management
pub fn create_session(&self, user_id: Uuid) -> Result<Uuid, String> {
let session = Session {
id: Uuid::new_v4(),
user_id,
created_at: Utc::now(),
expires_at: Utc::now() + Duration::hours(24),
last_accessed: Utc::now(),
};
if let Ok(mut sessions) = self.sessions.write() {
sessions.insert(session.id, session);
Ok(session.id)
} else {
Err("Failed to create session".to_string())
}
}- Struct Initialization: Named field syntax
- UUID Generation: Unique session identifiers
- Duration Arithmetic: Session expiration calculation
- Error Handling: Graceful failure with meaningful messages
7. Advanced Pattern Matching
match request.path.as_str() {
"/api/users" => match request.method.as_str() {
"GET" => self.list_users(&request),
"POST" => self.create_user(&request),
_ => self.method_not_allowed(),
},
path if path.starts_with("/api/users/") => {
let user_id = path.strip_prefix("/api/users/").unwrap();
self.get_user(user_id, &request)
},
_ => self.not_found(),
}- Nested Matching: Match patterns inside match arms
- Pattern Guards:
ifconditions in match arms - String Methods:
.starts_with()and.strip_prefix() - Route Parsing: Extract parameters from URL paths
Enterprise Features:
- Session-based authentication
- TTL-based caching
- API rate limiting concepts
- Comprehensive monitoring
- Production-ready error handling
Run Example:
cargo run --bin example_20_enterprise_serverThe examples in this tutorial demonstrate a comprehensive range of Rust language features and patterns essential for MCP development:
1. Ownership and Borrowing
- Ownership Transfer: Moving values between functions
- Borrowing:
&for immutable references,&mutfor mutable references - Lifetimes: Ensuring references are valid (
'staticlifetime) - RAII: Automatic resource cleanup when values go out of scope
2. Type System and Safety
- Strong Typing: Compile-time type checking prevents runtime errors
- Option: Safe handling of potentially missing values
- Result<T, E>: Explicit error handling without exceptions
- Enums: Type-safe unions with pattern matching
- Generics: Code reuse with type parameters and trait bounds
3. Pattern Matching
- match Expressions: Exhaustive handling of all cases
- if let: Convenient pattern matching for single cases
- while let: Pattern matching in loops
- Pattern Guards: Additional conditions in match arms
1. Smart Pointers
- Box: Heap allocation for owned data
- Arc: Atomic reference counting for shared ownership
- Rc: Reference counting for single-threaded sharing
2. Interior Mutability
- RwLock: Multiple readers or single writer
- Mutex: Mutual exclusion for shared mutable state
- Cell and RefCell: Single-threaded interior mutability
1. Async/Await
- async fn: Asynchronous function declarations
- await: Suspending execution for async operations
- Future: Lazy computations that can be awaited
- Tokio: Async runtime for network and I/O operations
2. Channels and Communication
- mpsc: Multi-producer, single-consumer channels
- unbounded_channel: No limit on queued messages
- Message Passing: Safe inter-task communication
3. Task Management
- tokio::spawn: Creating concurrent tasks
- Background Workers: Long-running async tasks
- Graceful Shutdown: Coordinated task termination
1. Error Propagation
- ? Operator: Automatic error propagation
- map_err(): Transform error types
- unwrap_or(): Provide default values
- Early Return: Exit functions on error conditions
2. Custom Error Types
- Error Enums: Structured error variants
- Display Trait: User-friendly error messages
- Error Trait: Standard error handling interface
- Nested Errors: Wrapping underlying errors
1. Iterators
- Iterator Trait: Lazy sequence processing
- map(): Transform elements
- filter(): Select elements
- collect(): Materialize results
- fold() and reduce(): Aggregation operations
2. Closures
- Fn Traits: Different closure types (Fn, FnMut, FnOnce)
- Capture: Borrowing or moving variables into closures
- Generic Functions: Accept closures as parameters
1. Serde Integration
- Serialize/Deserialize: Automatic JSON conversion
- Derive Macros: Code generation for traits
- Field Attributes: Control serialization behavior
- Custom Serialization: Manual implementation when needed
2. Data Structures
- HashMap: Key-value storage with O(1) lookup
- Vec: Dynamic arrays with growth
- VecDeque: Double-ended queues
- BTreeMap: Ordered key-value storage
1. String Types
- String: Owned, mutable UTF-8 text
- &str: Borrowed string slices
- String Conversion:
.to_string(),.into(), format!() - Unicode Support: Full UTF-8 character handling
2. Text Operations
- Splitting:
.split(),.split_whitespace(),.lines() - Transformation:
.to_uppercase(),.to_lowercase(),.trim() - Searching:
.contains(),.starts_with(),.find() - Formatting:
format!()macro with type safety
1. SQLx Integration
- Connection Pooling: Efficient database resource management
- Prepared Statements: SQL injection prevention
- Async Database: Non-blocking database operations
- Type Safety: Compile-time SQL type checking
2. File Operations
- Path Handling: Safe file system navigation
- Error Handling: Graceful I/O failure handling
- Async I/O: Non-blocking file operations
1. Input Validation
- JSON Schema: Structure validation
- Type Checking: Compile-time safety
- Sanitization: Safe string processing
- Bounds Checking: Array access safety
2. Cryptography
- Hashing: Password security (SHA-256, recommend bcrypt/Argon2)
- Random Generation: Secure UUID creation
- Token Management: Session and authentication tokens
1. Configuration Management
- Environment Variables: Runtime configuration
- Config Files: Structured settings
- Defaults: Fallback values
- Validation: Configuration checking
2. Monitoring and Metrics
- Structured Logging: Tracing integration
- Performance Metrics: Request timing and counting
- Health Checks: Service status monitoring
- Resource Tracking: Memory and CPU usage
3. Testing and Quality
- Unit Tests: Function-level testing
- Integration Tests: Component interaction testing
- Property Testing: Random input validation
- Benchmarking: Performance measurement
This comprehensive coverage of Rust concepts provides a solid foundation for building robust, safe, and efficient MCP servers. Each concept builds upon the others to create production-ready applications.
- Use Custom Error Types: Define specific error types for different failure modes
- Propagate Errors Properly: Use
?operator andResulttypes consistently - Provide Meaningful Messages: Include context in error messages
- Log Errors Appropriately: Use structured logging for debugging
#[derive(Debug)]
pub enum McpError {
ValidationError(String),
DatabaseError(String),
AuthenticationError(String),
InternalError(String),
}
impl std::fmt::Display for McpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
McpError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
McpError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
// ... other variants
}
}
}Error Handling Best Practices:
- Error Handling in Rust - Comprehensive error handling guide
- thiserror and anyhow - Modern error handling crates
- Error Handling Patterns - Common error patterns
- Failure vs thiserror vs anyhow - Comparing error handling approaches
- Input Validation: Always validate inputs with proper schemas
- Path Safety: Use path canonicalization to prevent directory traversal
- Rate Limiting: Implement rate limiting for production deployments
- Authentication: Use proper authentication mechanisms
- Secure Defaults: Default to secure configurations
Security Best Practices:
- OWASP Top 10 - Most critical security risks
- Rust Security Book - Security-focused Rust programming
- Input Validation - Comprehensive input validation guide
- Path Traversal Prevention - Understanding path traversal attacks
Cryptography and Authentication:
- RustCrypto - Cryptographic algorithms in Rust
- Security Audit Tools - Rust security tools
- Secure Coding Guidelines - French cybersecurity agency Rust guide
- Async/Await: Use async programming for I/O operations
- Connection Pooling: Pool database and HTTP connections
- Caching: Implement appropriate caching strategies
- Resource Limits: Set memory and connection limits
- Monitoring: Monitor performance metrics
Performance Optimization:
- The Rust Performance Book - Comprehensive performance guide
- Benchmarking in Rust - Measuring performance
- Profiling Rust Applications - Performance profiling tools
- criterion.rs - Statistical benchmarking
Memory Management:
- Rust Memory Management - Ownership and borrowing
- Zero-Cost Abstractions - Rust's performance philosophy
- Memory Profiling - Memory usage analysis
Async Performance:
- Tokio Performance - Async performance monitoring
- async-std Performance - Alternative async runtime
- Async Performance Patterns - Common async pitfalls
- Modular Design: Separate concerns into different modules
- Configuration: Make servers configurable for different environments
- Testing: Write comprehensive unit and integration tests
- Documentation: Document APIs and configuration options
- Logging: Use structured logging throughout
Use hierarchical configuration with environment-specific overrides:
// Load configuration in order of priority
pub fn load_config() -> Result<Config, ConfigError> {
let mut config = Config::default();
// 1. Load from config file
if let Ok(config_path) = env::var("CONFIG_FILE") {
config = Config::from_file(&config_path)?;
}
// 2. Override with environment variables
config.override_from_env()?;
// 3. Override with command line arguments
config.override_from_args()?;
Ok(config)
}- Structured Logging: Use JSON logging for production
- Metrics Collection: Collect performance and business metrics
- Health Checks: Implement comprehensive health checks
- Distributed Tracing: Use tracing for request correlation
- Alerting: Set up alerts for critical issues
- Containerization: Use Docker for consistent deployments
- Service Mesh: Consider service mesh for microservices
- Load Balancing: Distribute load across multiple instances
- Blue-Green Deployment: Enable zero-downtime deployments
- Circuit Breakers: Implement circuit breakers for resilience
# Run all tests
cargo test
# Run specific example tests
cargo test --bin example_01_hello_world
# Run integration tests
cargo test --test integration
# Check code quality
cargo clippy
cargo fmt --check# Run benchmarks
cargo bench
# Memory usage profiling
cargo run --release --bin example_11_monitoring
# Load testing with external tools
# (use tools like wrk, bombardier, or custom load tests)This tutorial demonstrates a complete progression from simple MCP servers to production-ready enterprise applications. Each example builds upon previous concepts while introducing new patterns and best practices.
The examples cover:
- Fundamentals: Basic MCP concepts and server structure
- Intermediate: Configuration, file operations, database integration
- Advanced: Monitoring, task queues, authentication, notifications
- Enterprise: Search, blockchain, ML, microservices, complete applications
For production use, focus on:
- Security best practices
- Comprehensive error handling
- Performance optimization
- Monitoring and observability
- Testing and validation
The complete source code for all examples is available in the repository, with each example being a fully functional MCP server that can be run and extended.
Production Deployment:
- Deployment Strategies - Various deployment patterns
- Docker and Rust - Efficient Docker builds for Rust
- Kubernetes for Rust - Kubernetes integration
- Health Checks - Service health monitoring
Monitoring and Observability:
- Observability in Rust - Comprehensive observability guide
- OpenTelemetry Rust - Distributed tracing
- Metrics Collection - Prometheus metrics patterns
- Structured Logging - Advanced logging with tracing
Load Testing and Benchmarking:
- Load Testing - HTTP benchmarking tool
- Artillery - Modern load testing toolkit
- Benchmarking Guide - Performance measurement best practices
- MCP Official Specification - Complete protocol specification
- Rust Async Programming - Comprehensive async guide
- Tokio Documentation - Async runtime documentation
- Serde JSON Guide - Serialization framework
- SQLx Documentation - Async SQL toolkit
- A Coder's Guide to the Official Rust MCP Toolkit - Comprehensive
rmcpguide - Rust Learning Resources - Curated learning materials
- Awesome Rust - Community-curated Rust resources
- This Week in Rust - Weekly Rust newsletter
- Rust Analyzer - Language server for IDEs
- Clippy - Linting tool
- rustfmt - Code formatting
- cargo-audit - Security vulnerability scanner
Contributions to improve examples, add new patterns, or enhance documentation are welcome. Please follow Rust best practices and include tests for new functionality.
- Fork the repository and create a feature branch
- Follow the established code style and documentation patterns
- Add tests for new functionality
- Update the tutorial documentation if needed
- Submit a pull request with a clear description of changes